import csvParser from 'csvtojson' import isObject from 'isobject' import { getSchema, tryToImportWithDelete, tryToImportWithoutDelete, updateCycleStartsForAllCycleDays } from '../../db' import getColumnNamesForCsv from './get-csv-column-names' import { LocalDate } from 'js-joda' import labels from '../../i18n/en/settings' export default async function importCsv(csv, deleteFirst) { const parseFuncs = { bool: val => { if (val.toLowerCase() === 'true') return true if (val.toLowerCase() === 'false') return false return val }, int: parseNumberIfPossible, float: parseNumberIfPossible, double: parseNumberIfPossible, string: val => val } function parseNumberIfPossible(val) { // Number and parseFloat catch different cases of weirdness, // so we test them both if (isNaN(Number(val)) || isNaN(parseFloat(val))) return val return Number(val) } const schema = getSchema() const config = { ignoreEmpty: true, colParser: getColumnNamesForCsv().reduce((acc, colName) => { const path = colName.split('.') const dbType = getDbType(schema.CycleDay, path) acc[colName] = item => { if (item === '') return null return parseFuncs[dbType](item) } return acc }, {}) } const cycleDays = await csvParser(config) .fromString(csv) .on('header', validateHeaders) //remove symptoms where all fields are null putNullForEmptySymptoms(cycleDays) throwIfFutureData(cycleDays) if (deleteFirst) { tryToImportWithDelete(cycleDays) } else { tryToImportWithoutDelete(cycleDays) } updateCycleStartsForAllCycleDays() } function validateHeaders(headers) { const expectedHeaders = getColumnNamesForCsv() if (!headers.every(header => { return expectedHeaders.indexOf(header) > -1 })) { const msg = `Expected CSV column titles to be ${expectedHeaders.join()}` throw new Error(msg) } } function putNullForEmptySymptoms(data) { data.forEach(replaceWithNullIfAllPropertiesAreNull) function replaceWithNullIfAllPropertiesAreNull(obj) { Object.keys(obj).forEach((key) => { if (!isObject(obj[key])) return if (Object.values(obj[key]).every(val => val === null)) { obj[key] = null return } replaceWithNullIfAllPropertiesAreNull(obj[key]) }) } } function getDbType(modelProperties, path) { const schema = getSchema() if (path.length === 1) return modelProperties[path[0]].type const modelName = modelProperties[path[0]].objectType return getDbType(schema[modelName], path.slice(1)) } function throwIfFutureData(cycleDays) { const today = LocalDate.now().toString() for (const i in cycleDays) { const day = cycleDays[i] // notes are allowed for future dates but everything else isn't if (day.date > today && Object.keys(day).some(symptom => symptom != 'date' && symptom != 'note')) { throw new Error(labels.import.errors.futureEdit) } } }