UNPKG

@codetanzania/ewea-common

Version:
1,685 lines (1,541 loc) 120 kB
'use strict'; const eweaInternals = require('@codetanzania/ewea-internals'); const constants = require('@lykmapipo/constants'); const lodash = require('lodash'); const common = require('@lykmapipo/common'); const env = require('@lykmapipo/env'); const mongooseCommon = require('@lykmapipo/mongoose-common'); const mongooseLocaleSchema = require('mongoose-locale-schema'); const async = require('async'); const file = require('@lykmapipo/file'); const path = require('path'); const phone = require('@lykmapipo/phone'); const logger = require('@lykmapipo/logger'); const geoTools = require('@lykmapipo/geo-tools'); const predefine = require('@lykmapipo/predefine'); const permission = require('@lykmapipo/permission'); const emisStakeholder = require('@codetanzania/emis-stakeholder'); const DEFAULT_PREDEFINE_NAME = env.getString( 'DEFAULT_PREDEFINE_NAME', 'Unknown' ); const DEFAULT_PREDEFINE_COLOR = env.getString( 'DEFAULT_PREDEFINE_COLOR', '#6D9EEB' ); const DEFAULT_PREDEFINE_WEIGHT = env.getNumber( 'DEFAULT_PREDEFINE_WEIGHT', 1000 ); const DEFAULT_PREDEFINE_RELATION = { _id: null, strings: { name: mongooseLocaleSchema.localizedValuesFor({ en: DEFAULT_PREDEFINE_NAME }), abbreviation: mongooseLocaleSchema.localizedAbbreviationsFor({ en: DEFAULT_PREDEFINE_NAME }), color: DEFAULT_PREDEFINE_COLOR, }, numbers: { weight: DEFAULT_PREDEFINE_WEIGHT }, }; const DEFAULT_UNIT_NAME = env.getString( 'DEFAULT_UNIT_NAME', eweaInternals.PREDEFINE_UNIT_NAME ); const DEFAULT_ADMINISTRATIVELEVEL_NAME = env.getString( 'DEFAULT_ADMINISTRATIVELEVEL_NAME', eweaInternals.PREDEFINE_ADMINISTRATIVELEVEL_NAME ); const DEFAULT_FEATURETYPE_NAME = env.getString( 'DEFAULT_FEATURETYPE_NAME', eweaInternals.PREDEFINE_FEATURETYPE_NAME ); const DEFAULT_EVENTINDICATOR_NAME = env.getString( 'DEFAULT_EVENTINDICATOR_NAME', eweaInternals.PREDEFINE_EVENTINDICATOR_NAME ); const DEFAULT_EVENTTOPIC_NAME = env.getString( 'DEFAULT_EVENTTOPIC_NAME', eweaInternals.PREDEFINE_EVENTTOPIC_NAME ); const DEFAULT_EVENTLEVEL_NAME = env.getString( 'DEFAULT_EVENTLEVEL_NAME', eweaInternals.PREDEFINE_EVENTLEVEL_NAME ); const DEFAULT_EVENTSEVERITY_NAME = env.getString( 'DEFAULT_EVENTSEVERITY_NAME', eweaInternals.PREDEFINE_EVENTSEVERITY_NAME ); const DEFAULT_EVENTCERTAINTY_NAME = env.getString( 'DEFAULT_EVENTCERTAINTY_NAME', eweaInternals.PREDEFINE_EVENTCERTAINTY_NAME ); const DEFAULT_EVENTSTATUS_NAME = env.getString( 'DEFAULT_EVENTSTATUS_NAME', eweaInternals.PREDEFINE_EVENTSTATUS_NAME ); const DEFAULT_EVENTURGENCY_NAME = env.getString( 'DEFAULT_EVENTURGENCY_NAME', eweaInternals.PREDEFINE_EVENTURGENCY_NAME ); const DEFAULT_EVENTRESPONSE_NAME = env.getString( 'DEFAULT_EVENTRESPONSE_NAME', eweaInternals.PREDEFINE_EVENTRESPONSE_NAME ); const DEFAULT_PARTYGROUP_NAME = env.getString( 'DEFAULT_PARTYGROUP_NAME', eweaInternals.PREDEFINE_PARTYGROUP_NAME ); const DEFAULT_PARTYROLE_NAME = env.getString( 'DEFAULT_PARTYROLE_NAME', eweaInternals.PREDEFINE_PARTYROLE_NAME ); const DEFAULT_EVENTGROUP_NAME = env.getString( 'DEFAULT_EVENTGROUP_NAME', eweaInternals.PREDEFINE_EVENTGROUP_NAME ); const DEFAULT_EVENTTYPE_NAME = env.getString( 'DEFAULT_EVENTTYPE_NAME', eweaInternals.PREDEFINE_EVENTTYPE_NAME ); const DEFAULT_EVENTFUNCTION_NAME = env.getString( 'DEFAULT_EVENTFUNCTION_NAME', eweaInternals.PREDEFINE_EVENTFUNCTION_NAME ); const DEFAULT_EVENTACTION_NAME = env.getString( 'DEFAULT_EVENTACTION_NAME', eweaInternals.PREDEFINE_EVENTACTION_NAME ); const DEFAULT_EVENTQUESTION_NAME = env.getString( 'DEFAULT_EVENTQUESTION_NAME', eweaInternals.PREDEFINE_EVENTQUESTION_NAME ); const DEFAULT_ADMINISTRATIVEAREA_NAME = env.getString( 'DEFAULT_ADMINISTRATIVEAREA_NAME', eweaInternals.PREDEFINE_ADMINISTRATIVEAREA_NAME ); const DEFAULT_EVENT_NUMBER = env.getString( 'DEFAULT_EVENT_NUMBER', undefined ); const DEFAULT_NAMES = common.sortedUniq([ DEFAULT_UNIT_NAME, DEFAULT_ADMINISTRATIVELEVEL_NAME, DEFAULT_FEATURETYPE_NAME, DEFAULT_EVENTINDICATOR_NAME, DEFAULT_EVENTTOPIC_NAME, DEFAULT_EVENTLEVEL_NAME, DEFAULT_EVENTSEVERITY_NAME, DEFAULT_EVENTCERTAINTY_NAME, DEFAULT_EVENTSTATUS_NAME, DEFAULT_EVENTURGENCY_NAME, DEFAULT_EVENTRESPONSE_NAME, DEFAULT_PARTYGROUP_NAME, DEFAULT_PARTYROLE_NAME, DEFAULT_EVENTGROUP_NAME, DEFAULT_EVENTTYPE_NAME, DEFAULT_EVENTFUNCTION_NAME, DEFAULT_EVENTACTION_NAME, DEFAULT_EVENTQUESTION_NAME, DEFAULT_ADMINISTRATIVEAREA_NAME, ]); // TODO update for all modules? const DEFAULT_PATHS = common.mergeObjects( { unit: { namespace: eweaInternals.PREDEFINE_NAMESPACE_UNIT }, role: { namespace: eweaInternals.PREDEFINE_NAMESPACE_PARTYROLE }, template: { namespace: eweaInternals.PREDEFINE_NAMESPACE_NOTIFICATIONTEMPLATE }, }, eweaInternals.EVENT_RELATIONS ); const DEFAULT_SEEDS_IGNORE = [ eweaInternals.PREDEFINE_NAMESPACE_FEATURETYPE, eweaInternals.PREDEFINE_NAMESPACE_EVENTINDICATOR, eweaInternals.PREDEFINE_NAMESPACE_EVENTTOPIC, eweaInternals.PREDEFINE_NAMESPACE_VEHICLE, eweaInternals.PREDEFINE_NAMESPACE_EVENTFUNCTION, eweaInternals.PREDEFINE_NAMESPACE_EVENTACTION, eweaInternals.PREDEFINE_NAMESPACE_EVENTQUESTION, eweaInternals.PREDEFINE_NAMESPACE_FEATURE, eweaInternals.PREDEFINE_NAMESPACE_EVENTACTIONCATALOGUE, eweaInternals.PREDEFINE_NAMESPACE_NOTIFICATIONTEMPLATE, ]; const DEFAULT_SEEDS = lodash.mapValues( lodash.omit(eweaInternals.PREDEFINE_DEFAULTS, ...DEFAULT_SEEDS_IGNORE), (defaultValue, namespace) => { return { _id: mongooseCommon.objectIdFor(eweaInternals.MODEL_NAME_PREDEFINE, namespace, defaultValue), namespace, strings: { name: mongooseLocaleSchema.localizedValuesFor({ en: defaultValue }), abbreviation: mongooseLocaleSchema.localizedAbbreviationsFor({ en: defaultValue }), color: DEFAULT_PREDEFINE_COLOR, }, numbers: { weight: DEFAULT_PREDEFINE_WEIGHT }, booleans: { default: true, system: true }, }; } ); // TODO: move to internal or common or dispatch? // TODO: use constants const COMMON_VEHICLESTATUSES = { Waiting: { weight: 1, name: 'Waiting', abbreviation: 'WTN' }, Enroute: { weight: 2, name: 'Enroute', abbreviation: 'ERT' }, Canceled: { weight: DEFAULT_PREDEFINE_WEIGHT, name: 'Canceled', abbreviation: 'CNL', }, AtPickup: { weight: 4, name: 'At Pickup', abbreviation: 'APU' }, FromPickup: { weight: 5, name: 'From Pickup', abbreviation: 'FPU' }, AtDropoff: { weight: 6, name: 'At Dropoff', abbreviation: 'ADO' }, FromDropoff: { weight: 7, name: 'From Dropoff', abbreviation: 'FDO' }, Completed: { weight: 8, name: 'Completed', abbreviation: 'CPT' }, Idle: { weight: DEFAULT_PREDEFINE_WEIGHT, name: 'Idle', abbreviation: 'IDL' }, }; const COMMON_VEHICLESTATUS_SEEDS = lodash.mapValues( COMMON_VEHICLESTATUSES, ({ weight, name, abbreviation }) => { const namespace = eweaInternals.PREDEFINE_NAMESPACE_VEHICLESTATUS; return { _id: mongooseCommon.objectIdFor(eweaInternals.MODEL_NAME_PREDEFINE, namespace, name), namespace, strings: { name: mongooseLocaleSchema.localizedValuesFor({ en: name }), abbreviation: mongooseLocaleSchema.localizedValuesFor({ en: abbreviation || name }), }, numbers: { weight: weight || DEFAULT_PREDEFINE_WEIGHT }, booleans: { system: true }, }; } ); // TODO: move to ewea-dispatch const dispatchStatusFor = (optns) => { // ensure options const options = common.mergeObjects(optns); // defaults let dispatch = COMMON_VEHICLESTATUS_SEEDS.Waiting; let vehicle = COMMON_VEHICLESTATUS_SEEDS.Idle; // dispatched if (options.dispatchedAt) { dispatch = COMMON_VEHICLESTATUS_SEEDS.Enroute; vehicle = COMMON_VEHICLESTATUS_SEEDS.Enroute; } // canceled if (options.canceledAt) { dispatch = COMMON_VEHICLESTATUS_SEEDS.Canceled; vehicle = COMMON_VEHICLESTATUS_SEEDS.Idle; } // arrived at pickup if (options.pickup && options.pickup.arrivedAt) { dispatch = COMMON_VEHICLESTATUS_SEEDS.AtPickup; vehicle = COMMON_VEHICLESTATUS_SEEDS.Enroute; } // dispatched from pickup if (options.pickup && options.pickup.dispatchedAt) { dispatch = COMMON_VEHICLESTATUS_SEEDS.FromPickup; vehicle = COMMON_VEHICLESTATUS_SEEDS.Enroute; } // arrived at dropoff if (options.dropoff && options.dropoff.arrivedAt) { dispatch = COMMON_VEHICLESTATUS_SEEDS.AtDropoff; vehicle = COMMON_VEHICLESTATUS_SEEDS.Enroute; } // dispatched from dropoff if (options.dropoff && options.dropoff.dispatchedAt) { dispatch = COMMON_VEHICLESTATUS_SEEDS.FromDropoff; vehicle = COMMON_VEHICLESTATUS_SEEDS.Enroute; } // resolved/completed if (options.resolvedAt || options.completedAt) { dispatch = COMMON_VEHICLESTATUS_SEEDS.Completed; vehicle = COMMON_VEHICLESTATUS_SEEDS.Idle; } return { dispatch, vehicle }; }; // TODO: move to ewea-case const COMMON_CASESTAGES = { Screening: { weight: 1, name: 'Screening', abbreviation: 'SCRN' }, Suspect: { weight: 2, name: 'Suspect', abbreviation: 'SUSP' }, Probable: { weight: 3, name: 'Probable', abbreviation: 'PROB' }, Confirmed: { weight: 4, name: 'Confirmed', abbreviation: 'CNFD' }, Recovered: { weight: 5, name: 'Recovered', abbreviation: 'REC' }, Followup: { weight: 6, name: 'Followup', abbreviation: 'FOL' }, Died: { weight: 7, name: 'Died', abbreviation: 'DD' }, }; const COMMON_CASESTAGE_SEEDS = lodash.mapValues( COMMON_CASESTAGES, ({ weight, name, abbreviation }) => { const namespace = eweaInternals.PREDEFINE_NAMESPACE_CASESTAGE; return { _id: mongooseCommon.objectIdFor(eweaInternals.MODEL_NAME_PREDEFINE, namespace, name), namespace, strings: { name: mongooseLocaleSchema.localizedValuesFor({ en: name }), abbreviation: mongooseLocaleSchema.localizedValuesFor({ en: abbreviation || name }), }, numbers: { weight: weight || DEFAULT_PREDEFINE_WEIGHT }, booleans: { system: true }, }; } ); const COMMON_CASESEVERITIES = { Asymptomatic: { weight: 0, name: 'Asymptomatic', abbreviation: 'ASY' }, Mild: { weight: 2, name: 'Mild', abbreviation: 'MIL' }, Moderate: { weight: 3, name: 'Moderate', abbreviation: 'MOD' }, Severe: { weight: 4, name: 'Severe', abbreviation: 'SEV' }, Critical: { weight: 5, name: 'Critical', abbreviation: 'CRT' }, }; const COMMON_CASESEVERITY_SEEDS = lodash.mapValues( COMMON_CASESEVERITIES, ({ weight, name, abbreviation }) => { const namespace = eweaInternals.PREDEFINE_NAMESPACE_CASESEVERITY; return { _id: mongooseCommon.objectIdFor(eweaInternals.MODEL_NAME_PREDEFINE, namespace, name), namespace, strings: { name: mongooseLocaleSchema.localizedValuesFor({ en: name }), abbreviation: mongooseLocaleSchema.localizedValuesFor({ en: abbreviation || name }), }, numbers: { weight: weight || DEFAULT_PREDEFINE_WEIGHT }, booleans: { system: true }, }; } ); // TODO move to ewea-case const caseSeverityFor = (optns) => { // ensure options const { score } = common.mergeObjects(optns); // special if (score === 0) { return COMMON_CASESEVERITY_SEEDS.Asymptomatic; } // mild if (score > 0 && score <= 2) { return COMMON_CASESEVERITY_SEEDS.Mild; } // moderate if (score > 2 && score <= 3) { return COMMON_CASESEVERITY_SEEDS.Moderate; } // severe if (score > 3 && score <= 4) { return COMMON_CASESEVERITY_SEEDS.Severe; } // critical if (score > 4) { return COMMON_CASESEVERITY_SEEDS.Critical; } // return default return DEFAULT_SEEDS[eweaInternals.PREDEFINE_NAMESPACE_CASESEVERITY]; }; /** * @function connect * @name connect * @description Ensure database connection * @param {Function} done callback to invoke on success or error * @returns {Error} connection error if failed * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.1.0 * @version 0.1.0 * @static * @public * @example * * connect(error => { ... }); */ const connect = (done) => { return mongooseCommon.connect((error) => { if (!error && !env.isTest()) { file.createModels(); } return done(error); }); }; /** * @function syncIndexes * @name syncIndexes * @description Synchronize model database indexes * @param {Function} done callback to invoke on success or error * @returns {Error|object} error if failed else sync results * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.1.0 * @version 0.1.0 * @static * @public * @example * * syncIndexes(error => { ... }); */ const syncIndexes = (done) => async.waterfall([connect, mongooseCommon.syncIndexes], done); /** * @function pathFor * @name pathFor * @description Derive path from application base * @param {...string} [paths] valid path to derive from base * @returns {string} derived path * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 * @static * @public * @example * * pathFor('seeds'); * => /home/ewea/seeds */ const pathFor = (...paths) => { const base = env.getString('BASE_PATH', process.cwd()); const path$1 = path.join(base, ...paths); return path$1; }; /** * @function dataPathFor * @name dataPathFor * @description Derive data path from application base of given file name * @param {string} fileName valid file name * @returns {string} valid data path * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 * @static * @public * @example * * dataPathFor('events.csv'); * => /home/ewea/data/events.csv */ const dataPathFor = (fileName) => { const path$1 = env.getString('DATA_PATH', pathFor('data')); return path.resolve(path$1, fileName); }; /** * @function seedPathFor * @name seedPathFor * @description Derive seed path from application base of given file name * @param {string} fileName valid file name * @returns {string} valid seed path * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 * @static * @public * @example * * seedPathFor('events.json'); * => /home/ewea/seeds/events.json */ const seedPathFor = (fileName) => { const path$1 = env.getString('SEED_PATH', pathFor('seeds')); return path.resolve(path$1, fileName); }; /** * @function csvPathFor * @name csvPathFor * @description Derive csv seed path of given model name * @param {string} modelName valid model name or predefine namespace * @returns {string} valid csv seed path * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 * @static * @public * @example * * csvPathFor('events'); * => /home/ewea/data/events.csv */ const csvPathFor = (modelName) => { const fileName = `${common.pluralize(lodash.toLower(modelName))}.csv`; const csvPath = dataPathFor(fileName); return csvPath; }; /** * @function shapeFilePathFor * @name shapeFilePathFor * @description Derive shapefile seed path of given model name * @param {string} modelName valid model name or predefine namespace * @returns {string} valid shapefile seed path * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 * @static * @public * @example * * shapeFilePathFor('events'); * => /home/ewea/data/events.shp */ const shapeFilePathFor = (modelName) => { const fileName = `${common.pluralize(lodash.toLower(modelName))}.shp`; const shapeFilePath = dataPathFor(fileName); return shapeFilePath; }; /** * @function geoJsonPathFor * @name geoJsonPathFor * @description Derive geojson seed path of given model name * @param {string} modelName valid model name or predefine namespace * @returns {string} valid geojson seed path * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 * @static * @public * @example * * geoJsonPathFor('events'); * => /home/ewea/data/events.geojson */ const geoJsonPathFor = (modelName) => { const fileName = `${common.pluralize(lodash.toLower(modelName))}.geojson`; const geoJsonFilePath = dataPathFor(fileName); return geoJsonFilePath; }; /** * @function jsonPathFor * @name jsonPathFor * @description Derive json seed path of given model name * @param {string} modelName valid model name or predefine namespace * @returns {string} valid json seed path * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 * @static * @public * @example * * jsonPathFor('events'); * => /home/ewea/seeds/events.json */ const jsonPathFor = (modelName) => { const fileName = `${common.pluralize(lodash.toLower(modelName))}.json`; const jsonFilePath = dataPathFor(fileName); return jsonFilePath; }; /** * @function transformSeedKeys * @name transformSeedKeys * @description Transform and normalize seed keys * @param {object} seed valid seed * @returns {object} transformed seed * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 * @static * @public * @example * * transformSeedKeys({ Name: 'John Doe' }); * => { name: 'John Doe' } */ const transformSeedKeys = (seed) => { // copy seed const data = common.mergeObjects(seed); // normalize keys const transformed = lodash.mapKeys(data, (value, key) => { // key to lower // TODO: camelize? let path = lodash.toLower(lodash.trim(key)); // key to path path = common.join(lodash.split(path, ' '), '.'); // return normalized key return path; }); // return return transformed; }; /** * @function transformGeoFields * @name transformGeoFields * @description Transform and normalize seed geo fields * @param {object} seed valid seed * @returns {object} transformed seed * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.6.0 * @version 0.1.0 * @static * @public * @example * * transformGeoFields({ point: '1,2' }); * => { point: { type: 'Point', coordinates: [ 1, 2 ] } } */ const transformGeoFields = (seed) => { // copy seed const transformed = common.mergeObjects(seed); // allowed geo fields const fields = { location: 'location', centroid: 'centroid', point: 'point', 'geos.point': 'geos.point', circle: 'polygon', 'geos.circle': 'geos.polygon', polygon: 'polygon', 'geos.polygon': 'geos.polygon', geometry: 'geometry', }; // transform geo fields lodash.forEach(fields, (seedPath, originalPath) => { // parse coordinates to geometry try { const geometry = geoTools.parseCoordinateString(seed[originalPath]); if (geometry) { transformed[seedPath] = geometry; } } catch (e) { // ignore on error } }); // otherwise tranform longitude and latitude if (transformed.longitude && transformed.latitude) { transformed.point = { type: 'Point', coordinates: [ Number(transformed.longitude), Number(transformed.latitude), ], }; } // return return transformed; }; /** * @function transformOtherFields * @name transformOtherFields * @description Transform and normalize other seed fields * @param {object} seed valid seed * @returns {object} transformed seed * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.6.0 * @version 0.1.0 * @static * @public * @example * * transformOtherFields({ action: '...' }); * => { name: '...', action: '...' } */ const transformOtherFields = (seed) => { // copy seed const transformed = common.mergeObjects(seed); // ensure event action catalogue.name from action if (lodash.isEmpty(transformed.name) && !lodash.isEmpty(transformed.action)) { transformed.name = transformed.action; } // ensure weight from level & order const weight = lodash.toNumber(transformed.level || transformed.order); if (!lodash.isNaN(weight)) { transformed.weight = weight; transformed.numbers = common.mergeObjects(transformed.numbers, { weight }); } // return return transformed; }; /** * @function applyTransformsOn * @name applyTransformsOn * @description Transform and normalize seed * @param {object|object[]} seed valid seed(s) * @param {...Function} [transformers] transform to apply on seed * @returns {object} transformed seed * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 * @static * @public * @example * * applyTransformsOn({ Name: 'John Doe' }); * => { name: 'John Doe' } */ const applyTransformsOn = (seed, ...transformers) => { // copy seed let data = common.compact([].concat(seed)); data = lodash.map(data, (value) => { // copy value let transformed = common.mergeObjects(value); // ensure transformers const baseTransformers = [ transformSeedKeys, transformGeoFields, transformOtherFields, ]; const transforms = common.compact(baseTransformers.concat(transformers)); // apply transform sequentially lodash.forEach(transforms, (applyTransformOn) => { transformed = lodash.isFunction(applyTransformOn) ? applyTransformOn(transformed) : common.mergeObjects(transformed); }); // return transformed return transformed; }); // return data = lodash.isArray(seed) ? data : lodash.first(data); return data; }; /** * @function transformToPredefineSeed * @name transformToPredefineSeed * @description Transform and normalize given seed to predefine seed * @param {object} seed valid seed * @returns {object} valid predefine seed * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 * @static * @public * @example * * transformToPredefineSeed({ Name: 'John Doe' }); * => { strings: { name: { en : 'John Doe' } } } */ const transformToPredefineSeed = (seed) => { // copy seed const data = common.mergeObjects(seed); // normalize to predefine let predefine$1 = predefine.transformToPredefine(data); predefine$1.raw = data; // transform relations // TODO: honor exist populate option // TODO: handle parent of administrative area using level const populate = {}; lodash.forEach(eweaInternals.PREDEFINE_RELATIONS, (value, key) => { const hasRelation = key && seed[key]; if (hasRelation) { const options = common.mergeObjects(value); const path = `relations.${options.path || key}`; const modelName = options.ref || eweaInternals.MODEL_NAME_PREDEFINE; const namespaces = common.compact([].concat(options.namespace)); const array = options.array || false; const vals = common.sortedUniq(lodash.split(seed[key], ',')); const match = modelName === eweaInternals.MODEL_NAME_PREDEFINE ? { 'strings.name.en': { $in: vals }, namespace: { $in: namespaces } } : { name: { $in: vals } }; // honour administrative area seed hierarchy const handleAdministrativeArea = seed.namespace === eweaInternals.PREDEFINE_NAMESPACE_ADMINISTRATIVEAREA && key === 'parent' && seed.level; const ignore = handleAdministrativeArea ? { model: modelName, path: 'relations.level', match: { 'strings.name.en': { $in: [seed.level] }, namespace: { $in: [eweaInternals.PREDEFINE_NAMESPACE_ADMINISTRATIVELEVEL] }, }, array: false, } : {}; populate[path] = { model: modelName, match, array, ignore }; } }); predefine$1.populate = populate; // return predefine$1 = lodash.omit(predefine$1, ...[...lodash.keys(eweaInternals.PREDEFINE_RELATIONS), 'relations']); return predefine$1; }; /** * @function transformToPartySeed * @name transformToPartySeed * @description Transform and normalize given seed to party seed * @param {object} seed valid seed * @returns {object} valid party seed * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.6.0 * @version 0.1.0 * @static * @public * @example * * transformToPartySeed({ Name: 'John Doe' }); * => { name: 'John Doe' } */ const transformToPartySeed = (seed) => { // copy seed let data = common.mergeObjects(seed); // generate seed object id const shouldGenerateId = !lodash.get(data, '_id') && common.areNotEmpty(data.mobile, data.email); if (shouldGenerateId) { lodash.set( data, '_id', mongooseCommon.objectIdFor(eweaInternals.MODEL_NAME_PARTY, phone.toE164(data.mobile), lodash.toLower(data.email)) ); } // ensure default password if (lodash.isEmpty(data.password)) { data.password = env.getString( 'DEFAULT_HASHED_PASSWORD', // TODO: dynamically hash DEFAULT_PASSWORD '$2a$10$rwpL/BhU8xY4fkf8SG7fHugF4PCioTJqy8BLU7BZ8N0YV.8Y1dXem' ); } // ensure confirmed time data.confirmedAt = new Date(); // clear lock data.failedAttempts = 0; data.lockedAt = null; data.unlockedAt = null; data.unlockToken = null; data.unlockSentAt = null; data.unlockTokenExpiryAt = null; // transform relations const populate = {}; lodash.forEach(eweaInternals.PARTY_RELATIONS, (value, key) => { const hasRelation = key && data[key]; if (hasRelation) { const options = common.mergeObjects(value); const path = `${options.path || key}`; const modelName = options.ref || eweaInternals.MODEL_NAME_PREDEFINE; const namespaces = common.compact([].concat(options.namespace)); const array = options.array || false; const vals = common.sortedUniq(lodash.split(data[key], ',')); const match = modelName === eweaInternals.MODEL_NAME_PREDEFINE ? { 'strings.name.en': { $in: vals }, namespace: { $in: namespaces } } : { name: { $in: vals } }; populate[path] = { model: modelName, match, array }; } }); data.populate = populate; // return data = lodash.omit(data, ...[...lodash.keys(eweaInternals.PARTY_RELATIONS), 'relations', 'namespace']); return data; }; /** * @function transformToEventSeed * @name transformToEventSeed * @description Transform and normalize given seed to event seed * @param {object} seed valid seed * @returns {object} valid event seed * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.6.0 * @version 0.1.0 * @static * @public * @example * * transformToEventSeed({ Name: 'John Doe' }); * => { name: 'John Doe' } */ const transformToEventSeed = (seed) => { // copy seed let data = common.mergeObjects(seed); // generate seed object id if (!lodash.get(data, '_id') && data.number) { lodash.set(data, '_id', mongooseCommon.objectIdFor(eweaInternals.MODEL_NAME_EVENT, data.number)); } // transform relations const populate = {}; lodash.forEach(eweaInternals.EVENT_RELATIONS, (value, key) => { const hasRelation = key && data[key]; if (hasRelation) { const options = common.mergeObjects(value); const path = `${options.path || key}`; const modelName = options.ref || eweaInternals.MODEL_NAME_PREDEFINE; const namespaces = common.compact([].concat(options.namespace)); const array = options.array || false; const vals = common.sortedUniq(lodash.split(data[key], ',')); const match = modelName === eweaInternals.MODEL_NAME_PREDEFINE ? { 'strings.name.en': { $in: vals }, namespace: { $in: namespaces } } : { name: { $in: vals } }; populate[path] = { model: modelName, match, array }; } }); data.populate = populate; // return data = lodash.omit(data, ...[...lodash.keys(eweaInternals.EVENT_RELATIONS), 'relations', 'namespace']); return data; }; /** * @function transformToVehicleDispatchSeed * @name transformToVehicleDispatchSeed * @description Transform and normalize given seed to vehicle dispatch seed * @param {object} seed valid seed * @returns {object} valid vehicle dispatch seed * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.14.0 * @version 0.1.0 * @static * @public * @example * * transformToVehicleDispatchSeed({ Name: 'John Doe' }); * => { name: 'John Doe' } */ const transformToVehicleDispatchSeed = (seed) => { // copy seed let data = common.mergeObjects(seed); // generate seed object id if (!lodash.get(data, '_id') && data.number) { lodash.set(data, '_id', mongooseCommon.objectIdFor(eweaInternals.MODEL_NAME_VEHICLEDISPATCH, data.number)); } // transform relations const populate = {}; lodash.forEach(eweaInternals.VEHICLE_DISPATCH_RELATIONS, (value, key) => { const hasRelation = key && data[key]; if (hasRelation) { const options = common.mergeObjects(value); const path = `${options.path || key}`; const modelName = options.ref || eweaInternals.MODEL_NAME_PREDEFINE; const namespaces = common.compact([].concat(options.namespace)); const array = options.array || false; const vals = common.sortedUniq(lodash.split(data[key], ',')); const match = modelName === eweaInternals.MODEL_NAME_PREDEFINE ? { 'strings.name.en': { $in: vals }, namespace: { $in: namespaces } } : { name: { $in: vals } }; populate[path] = { model: modelName, match, array }; } }); data.populate = populate; // return data = lodash.omit( data, ...[...lodash.keys(eweaInternals.VEHICLE_DISPATCH_RELATIONS), 'relations', 'namespace'] ); return data; }; /** * @function transformToCaseSeed * @name transformToCaseSeed * @description Transform and normalize given seed to case seed * @param {object} seed valid seed * @returns {object} valid case seed * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.18.0 * @version 0.1.0 * @static * @public * @example * * transformToCaseSeed({ Name: 'John Doe' }); * => { name: 'John Doe' } */ const transformToCaseSeed = (seed) => { // TODO: support alias on relation doted path // copy seed let data = common.mergeObjects(seed); // generate seed object id if (!lodash.get(data, '_id') && data.number) { lodash.set(data, '_id', mongooseCommon.objectIdFor(eweaInternals.MODEL_NAME_CASE, data.number)); } // transform relations const populate = {}; lodash.forEach(eweaInternals.CASE_RELATIONS, (value, key) => { const hasRelation = key && data[key]; if (hasRelation) { const options = common.mergeObjects(value); const path = `${options.path || key}`; const modelName = options.ref || eweaInternals.MODEL_NAME_PREDEFINE; const namespaces = common.compact([].concat(options.namespace)); const array = options.array || false; const vals = common.sortedUniq(lodash.split(data[key], ',')); const match = modelName === eweaInternals.MODEL_NAME_PREDEFINE ? { 'strings.name.en': { $in: vals }, namespace: { $in: namespaces } } : { name: { $in: vals } }; populate[path] = { model: modelName, match, array }; } }); data.populate = populate; // return data = lodash.omit(data, ...[...lodash.keys(eweaInternals.CASE_RELATIONS), 'relations', 'namespace']); return data; }; /** * @function readCsvFile * @name readCsvFile * @description Read csv seed and apply seed transforms * @param {string} path valid csv path * @param {Function[]} [transformers] transforms to apply on seed * @param {Function} done callback to invoke on next seed * @returns {object} transformed seed * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 * @static * @public * @example * * readCsvFile(path, transforms, (error, { finished, feature, next }) => { ... }); */ const readCsvFile = (path, transformers, done) => { return geoTools.readCsv({ path }, (error, { finished, feature, next }) => { let data = feature; if (!lodash.isEmpty(feature) && next && !finished) { data = applyTransformsOn(feature, ...transformers); } return done(error, { finished, feature: data, next }); }); }; /** * @function processCsvSeed * @name processCsvSeed * @description process each csv row (data) * @param {object} [optns] valid options * @param {string} [optns.Model=undefined] valid model name * @param {object} [optns.properties={}] valid extra properties to merge on each seed * @param {string} [optns.namespace=undefined] valid predefine namespace * @param {string} [optns.domain=undefined] valid predefine domain * @param {boolean} [optns.throws=false] whether to throw error * @param {Function} done callback to invoke on success or error * @returns {Function} call back function * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 * @static * @public * @example * * const options = { Model = undefined, properties = {}, namespace = undefined, throws = false } * processCsvSeed((options, done) => (error, {finished, feature, next}) => { ... }); */ const processCsvSeed = ( { Model = undefined, properties = {}, namespace = undefined, domain = undefined, throws = false, }, done ) => (error, { finished, feature, next }) => { // handle file read errors if (error) { return throws ? done(error) : done(); } // handle read finish if (finished) { return done(); } // process datas if (feature && next) { // seed data & next chunk from csv read stream const data = common.mergeObjects(properties, { namespace, domain }, feature); return Model.seed(data, next); } // request next chunk from csv read stream return next && next(); }; /** * @function seedFromCsv * @name seedFromCsv * @description Seed given model from csv file * @param {object} optns valid seed options * @param {string} [optns.modelName] valid model name * @param {string} [optns.namespace] valid predefine namespace * @param {string} [optns.domain=undefined] valid predefine domain * @param {boolean} [optns.throws=false] whether to throw error * @param {string} [optns.filePath=undefined] valid full file path for csv seed * @param {object} [optns.properties={}] extra properties to merge on each seed * @param {Function} [optns.transform] valid seed transform * @param {Function[]} [optns.transformers] valid predefine transformers * @param {Function} done callback to invoke on success or error * @returns {Error|undefined} error if fails else undefined * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.6.0 * @version 0.1.0 * @static * @public * @example * * const opts = { modelName: ..., transformers: [ ... ] }; * seedFromCsv(optns, error => { ... }); */ const seedFromCsv = (optns, done) => { // normalize options const { filePath = undefined, properties = {}, modelName = undefined, namespace = undefined, domain = undefined, throws = true, transform = (seed) => seed, transformers = [], } = common.mergeObjects(optns); // do: seed data to model if exists const Model = mongooseCommon.model(modelName); if (Model) { // prepare seed options const isPredefine = modelName === eweaInternals.MODEL_NAME_PREDEFINE && !lodash.isEmpty(namespace); const csvFilePath = filePath || csvPathFor(namespace || modelName); const appliedTransformers = isPredefine ? lodash.map([transformToPredefineSeed, ...transformers, transform], (fn) => { return (seed) => { return fn({ namespace, domain, ...seed }); }; }) : [...transformers, transform]; // seed from csv return readCsvFile( csvFilePath, appliedTransformers, processCsvSeed({ Model, properties, namespace, domain, throws }, done) ); } // backoff: no data model found return done(); }; /** * @function seedFromJson * @name seedFromJson * @description Seed given model from json file * @param {object} optns valid seed options * @param {string} [optns.modelName] valid model name * @param {string} [optns.namespace] valid predefine namespace * @param {string} [optns.domain=undefined] valid predefine domain * @param {boolean} [optns.throws=false] whether to throw error * @param {string} [optns.filePath=undefined] valid full file path for json seed * @param {object} [optns.properties={}] extra properties to merge on each seed * @param {Function} [optns.transform] valid seed transform * @param {Function[]} [optns.transformers] valid predefine transformers * @param {Function} done callback to invoke on success or error * @returns {Error|undefined} error if fails else undefined * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.6.0 * @version 0.1.0 * @static * @public * @example * * const opts = { modelName: ..., transformers: [ ... ] }; * seedFromJson(optns, error => { ... }); */ const seedFromJson = (optns, done) => { // normalize options const { filePath = undefined, properties = {}, modelName = undefined, namespace = undefined, domain = undefined, throws = false, transform = (seed) => seed, transformers = [], } = common.mergeObjects(optns); // do: seed data to model if exists const Model = mongooseCommon.model(modelName); if (Model) { // prepare seed options const isPredefine = modelName === eweaInternals.MODEL_NAME_PREDEFINE && !lodash.isEmpty(namespace); const jsonFilePath = filePath || jsonPathFor(namespace || modelName); const appliedTransformers = isPredefine ? lodash.map([transformToPredefineSeed, ...transformers, transform], (fn) => { return (seed) => { return fn({ namespace, domain, ...seed }); }; }) : [...transformers, transform]; // prepare json seed stages const path = lodash.endsWith(jsonFilePath, '.json') ? jsonFilePath : `${jsonFilePath}.json`; return geoTools.readJson({ path, throws }, (error, data) => { if (!lodash.isEmpty(data)) { const doTransform = (seed) => { const merged = common.mergeObjects(properties, { namespace, domain }, seed); return applyTransformsOn(merged, ...appliedTransformers); }; return Model.seed({ data, transform: doTransform }, done); } return done(error, data); }); } // backoff: no data model found return done(); }; /** * @function seedFromSeeds * @name seedFromSeeds * @description Seed given model from seeds file * @param {object} optns valid seed options * @param {string} [optns.modelName] valid model name * @param {boolean} [optns.throws=false] whether to throw error * @param {Function} [optns.filter=undefined] seed data filter * @param {Function} [optns.transform=undefined] seed data transformer * @param {Function} done callback to invoke on success or error * @returns {Error|undefined} error if fails else undefined * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.6.0 * @version 0.1.0 * @static * @public * @example * * const opts = { modelName: ... }; * seedFromSeeds(optns, error => { ... }); */ const seedFromSeeds = (optns, done) => { // TODO: transform relations to populate? // normalize options const { modelName = undefined, throws = false, data = undefined, filter, transform = (seed) => seed, transformers = [], } = common.mergeObjects(optns); // merge transform & transformers const doTransform = (seed) => { const merged = common.mergeObjects(seed); const appliedTransformers = common.compact( [].concat(transformers).concat(transform) ); return applyTransformsOn(merged, ...appliedTransformers); }; // do: seed data to model if seeds exists const Model = mongooseCommon.model(modelName); const canSeed = Model && lodash.isFunction(Model.seed); if (canSeed) { // filter, transform & seed const options = { data, filter, transform: doTransform }; return Model.seed(options, (error, results) => { // reply with errors if (throws) { return done(error, results); } // ignore errors return done(null, results); }); } // backoff: no data model found return done(); }; /** * @function seedPredefine * @name seedPredefine * @description Seed given predefine namespace * @param {object} optns valid seed options * @param {string} optns.namespace valid predefine namespace * @param {string} [optns.domain=undefined] valid predefine domain * @param {boolean} [optns.throws=false] whether to ignore error * @param {Function} [optns.transform] valid seed transform * @param {Function[]} [optns.transformers] valid predefine transformers * @param {Function} done callback to invoke on success or error * @returns {Error|undefined} error if fails else undefined * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 * @static * @public * @example * * seedPredefine(optns, error => { ... }); */ const seedPredefine = (optns, done) => { // TODO: default transform(namespace, domain, parent, name code) // normalize options const { modelName = eweaInternals.MODEL_NAME_PREDEFINE, namespace = undefined, domain = undefined, throws = false, transform = (seed) => seed, transformers = [], } = common.mergeObjects(optns); // prepare namespace filter const filter = (seed) => seed.namespace === namespace; // prepare options const options = { modelName, namespace, domain, throws, transform, transformers, filter, }; // prepare predefine seed stages const fromSeeds = (next) => seedFromSeeds(options, (error) => next(error)); const fromJson = (next) => seedFromJson(options, (error) => next(error)); const fromCsv = (next) => seedFromCsv(options, (error) => next(error)); const stages = [fromCsv, fromJson, fromSeeds]; // do seed predefine return async.waterfall(stages, done); }; /** * @function seedParty * @name seedParty * @description Seed given parties * @param {object} optns valid seed options * @param {string} optns.type valid party type * @param {boolean} [optns.throws=false] whether to ignore error * @param {Function} [optns.transform] valid seed transform * @param {Function[]} [optns.transformers] valid party transformers * @param {Function} done callback to invoke on success or error * @returns {Error|undefined} error if fails else undefined * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 * @static * @public * @example * * seedParty(optns, error => { ... }); */ const seedParty = (optns, done) => { // normalize options const { modelName = eweaInternals.MODEL_NAME_PARTY, type = 'Focal', throws = false, transform = (seed) => seed, transformers = [], } = common.mergeObjects(optns); // prepare type filter const filter = (seed) => seed.type === type; // prepare options const options = { modelName, namespace: type, properties: { type }, type, throws, transform, transformers: [transformToPartySeed, ...transformers], filter, }; // prepare party seed stages const fromSeeds = (next) => seedFromSeeds(options, (error) => next(error)); const fromJson = (next) => seedFromJson(options, (error) => next(error)); const fromCsv = (next) => seedFromCsv(options, (error) => next(error)); const stages = [fromCsv, fromJson, fromSeeds]; // do seed party return async.waterfall(stages, done); }; /** * @function seedEvent * @name seedEvent * @description Seed given events * @param {object} optns valid seed options * @param {boolean} [optns.throws=false] whether to ignore error * @param {Function} [optns.transform] valid seed transform * @param {Function[]} [optns.transformers] valid event transformers * @param {Function} done callback to invoke on success or error * @returns {Error|undefined} error if fails else undefined * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.3.0 * @version 0.1.0 * @static * @public * @example * * seedEvent(optns, error => { ... }); */ const seedEvent = (optns, done) => { // normalize options const { modelName = eweaInternals.MODEL_NAME_EVENT, throws = false, transform = (seed) => seed, transformers = [], } = common.mergeObjects(optns); // prepare options const options = { modelName, properties: {}, throws, transform, transformers: [transformToEventSeed, ...transformers], }; // prepare event seed stages const fromSeeds = (next) => seedFromSeeds(options, (error) => next(error)); const fromJson = (next) => seedFromJson(options, (error) => next(error)); const fromCsv = (next) => seedFromCsv(options, (error) => next(error)); const stages = [fromCsv, fromJson, fromSeeds]; // do seed event return async.waterfall(stages, done); }; /** * @function seedVehicleDispatch * @name seedVehicleDispatch * @description Seed given vehicle dispatches * @param {object} optns valid seed options * @param {boolean} [optns.throws=false] whether to ignore error * @param {Function} [optns.transform] valid seed transform * @param {Function[]} [optns.transformers] valid event transformers * @param {Function} done callback to invoke on success or error * @returns {Error|undefined} error if fails else undefined * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.14.0 * @version 0.1.0 * @static * @public * @example * * seedVehicleDispatch(optns, error => { ... }); */ const seedVehicleDispatch = (optns, done) => { // normalize options const { modelName = eweaInternals.MODEL_NAME_VEHICLEDISPATCH, throws = false, transform = (seed) => seed, transformers = [], } = common.mergeObjects(optns); // prepare options const options = { modelName, properties: {}, throws, transform, transformers: [transformToVehicleDispatchSeed, ...transformers], }; // prepare vehicle dispatch seed stages const fromSeeds = (next) => seedFromSeeds(options, (error) => next(error)); const fromJson = (next) => seedFromJson(options, (error) => next(error)); const fromCsv = (next) => seedFromCsv(options, (error) => next(error)); const stages = [fromCsv, fromJson, fromSeeds]; // do seed vehicle dispatch return async.waterfall(stages, done); }; /** * @function seedCase * @name seedCase * @description Seed given cases * @param {object} optns valid seed options * @param {boolean} [optns.throws=false] whether to ignore error * @param {Function} [optns.transform] valid seed transform * @param {Function[]} [optns.transformers] valid case transformers * @param {Function} done callback to invoke on success or error * @returns {Error|undefined} error if fails else undefined * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.18.0 * @version 0.1.0 * @static * @public * @example * * seedCase(optns, error => { ... }); */ const seedCase = (optns, done) => { // normalize options const { modelName = eweaInternals.MODEL_NAME_CASE, throws = false, transform = (seed) => seed, transformers = [], } = common.mergeObjects(optns); // prepare options const options = { modelName, properties: {}, throws, transform, transformers: [transformToCaseSeed, ...transformers], }; // prepare case seed stages const fromSeeds = (next) => seedFromSeeds(options, (error) => next(error)); const fromJson = (next) => seedFromJson(options, (error) => next(error)); const fromCsv = (next) => seedFromCsv(options, (error) => next(error)); const stages = [fromCsv, fromJson, fromSeeds]; // do seed vehicle dispatch return async.waterfall(stages, done); }; /** * @function seedPermissions * @name seedPermissions * @description Seed permissions * @param {Function} done callback to invoke on success or error * @returns {Error|undefined} error if fails else undefined * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.4.0 * @version 0.1.0 * @static * @public * @example * * seedPermissions(error => { ... }); */ const seedPermissions = (done) => { // TODO: honour wildcard for _id generation logger.debug('Start Seeding Permissions Data'); // generate object id const transform = (seed) => { const merged = common.mergeObjects( { _id: mongooseCommon.objectIdFor(eweaInternals.MODEL_NAME_PERMISSION, seed.wildcard) }, seed ); return merged; }; // prepare permissions seed stages // TODO: dashboard permission seeds const seedResourcePermissions = (next) => { const data = permission.Permission.prepareResourcesPermissions(); const options = { data, transform }; return permission.Permission.seed(options, (error) => next(error)); }; const seedPredefineNamespacePermissions = (next) => { const data = predefine.listPermissions(); const options = { data, transform }; return permission.Permission.seed(options, (error) => next(error)); }; const stages = [seedResourcePermissions, seedPredefineNamespacePermissions]; // do seed permissions return async.waterfall(stages, (error) => { logger.debug('Finish Seeding Permissions Data'); return done(error); }); }; /** * @function seedDefaults * @name seedDefaults * @description Seed default predefines * @param {Function} done callback to invoke on success or error * @returns {Error|undefined} error if fails else undefined * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.15.0 * @version 0.1.0 * @static * @public * @example * * seedDefaults(error => { ... }); */ const seedDefaults = (done) => { // TODO: honour code for _id generation logger.debug('Start Seeding Default Predefines Data'); // prepare options const m