|  | /** | 
        
          |  | * | 
        
          |  | * Run this script with the command: `node schema-migration.js ./example-schema-update-rules.json` | 
        
          |  | * Replace `example-schema-update-rules.json` with your own version of the file. | 
        
          |  | * | 
        
          |  | * There are three JSON operations for modifying data... | 
        
          |  | * @addFields will add new fields to the data with a value you can optionally specify | 
        
          |  | * @updateFields will map a field over to to a new key. Persisting the data value. | 
        
          |  | * @deleteFields will delete the fields you specify | 
        
          |  | * | 
        
          |  | * @TODO: make this script class based | 
        
          |  | * | 
        
          |  | */ | 
        
          |  |  | 
        
          |  |  | 
        
          |  | 'use strict'; | 
        
          |  | // Parse command line arguments to determine input file | 
        
          |  | const cmdArgs = process.argv.slice(2); | 
        
          |  | const schemaUpdateRulesFile = cmdArgs[0] ? cmdArgs[0] : 'example-schema-update-rules'; | 
        
          |  | const schemaUpdateRules = require(schemaUpdateRulesFile); | 
        
          |  |  | 
        
          |  | const mongoUrl = require('../server/datasources').db.url; | 
        
          |  | let MongoClient = require( 'mongodb' ).MongoClient; | 
        
          |  | let ObjectId = require('mongodb').ObjectID; | 
        
          |  | let _db; | 
        
          |  | let dbo; | 
        
          |  | let collectionIterator = 0; | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Open MongoDB connection | 
        
          |  | * Save connect in global variables: | 
        
          |  | * @_db | 
        
          |  | * @dbo | 
        
          |  | */ | 
        
          |  | let mongoConnection = function() { | 
        
          |  | MongoClient.connect(mongoUrl, function (err, db) { | 
        
          |  | if (err) throw err; | 
        
          |  | _db = db; | 
        
          |  | dbo = db.db('vtax'); | 
        
          |  | initialize(); | 
        
          |  | }); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Initialize | 
        
          |  | */ | 
        
          |  | let initialize = function () { | 
        
          |  | const numberCollections = schemaUpdateRules.collections.length; | 
        
          |  | for (let i = 0; i < numberCollections; i++) { | 
        
          |  | const collectionName = schemaUpdateRules.collections[i].name; | 
        
          |  | const modifiers = schemaUpdateRules.collections[i].modifiers; | 
        
          |  | getData(collectionName, modifiers, numberCollections); | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Pull down data from MongoDB | 
        
          |  | * @param collectionName | 
        
          |  | * @param modifiers | 
        
          |  | * @TODO: In future iterations, the find should iterate through documents for very large collections where local memory is a concern. | 
        
          |  | */ | 
        
          |  | let getData =  function(collectionName, modifiers, numberCollections) { | 
        
          |  | dbo.collection(collectionName).find({}).toArray(function(err, data) { | 
        
          |  | if (err) throw err; | 
        
          |  | console.log('Updating ' + collectionName + '...'); | 
        
          |  | generateQuery(data, collectionName, modifiers, numberCollections); | 
        
          |  | }); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Iterate through documents in a collection. Using our add, update, | 
        
          |  | * and delete functions to create $set and $unset rules for mongo. | 
        
          |  | * @param data | 
        
          |  | * @param collectionName | 
        
          |  | * @param modifiers | 
        
          |  | * @param numberCollections | 
        
          |  | */ | 
        
          |  | let generateQuery = function (data, collectionName, modifiers, numberCollections) { | 
        
          |  | let record; | 
        
          |  | let updateQuery = {$set: {}, $unset: {}}; | 
        
          |  | let updatePromiseArray = []; | 
        
          |  | // Loop through all records from collection | 
        
          |  | for (let i = 0; i < data.length; i++) { | 
        
          |  | record = JSON.parse(JSON.stringify(data[i])); // deep clone | 
        
          |  | updateQuery = addFields(updateQuery, modifiers); | 
        
          |  | updateQuery = updateFields(updateQuery, record, modifiers); | 
        
          |  | updateQuery = deleteFields(updateQuery, record, modifiers); | 
        
          |  | updatePromiseArray.push(update(collectionName, updateQuery, record._id)); | 
        
          |  | } | 
        
          |  | Promise.all(updatePromiseArray).then(function(values) { | 
        
          |  | collectionIterator++; | 
        
          |  | if (numberCollections === collectionIterator) cleanExit() | 
        
          |  | }); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Adds properties to be updated to the $set part of the query. | 
        
          |  | * @param record | 
        
          |  | * @param modifiers | 
        
          |  | */ | 
        
          |  | let addFields = function(updateQuery, modifiers) { | 
        
          |  | for (let key in modifiers.addFields) { | 
        
          |  | updateQuery.$set[key] = modifiers.addFields[key] | 
        
          |  | } | 
        
          |  | return updateQuery | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Adds properties to be updated to the $set part of the query. | 
        
          |  | * Also, adds the properties to be deleted to the $unset part of the query. | 
        
          |  | * @param record | 
        
          |  | * @param modifiers | 
        
          |  | */ | 
        
          |  | let updateFields = function(updateQuery, record, modifiers) { | 
        
          |  | if (modifiers.updateFields.length < 1) return | 
        
          |  | for (let key in modifiers.updateFields) { | 
        
          |  | const path = modifiers.updateFields[key]; | 
        
          |  | let addValue = readProp(record, path); | 
        
          |  | if (!addValue) addValue = null; | 
        
          |  | updateQuery.$set[key] = addValue; | 
        
          |  | updateQuery.$unset[path] = ''; | 
        
          |  | } | 
        
          |  | return updateQuery | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Adds properties to be deleted to the $unset part of the query | 
        
          |  | * @param record | 
        
          |  | * @param modifiers | 
        
          |  | * @returns {*} | 
        
          |  | */ | 
        
          |  | let deleteFields = function(updateQuery, record, modifiers) { | 
        
          |  | for (let i = 0; i < modifiers.deleteFields.length; i++) { | 
        
          |  | updateQuery.$unset[modifiers.deleteFields[i]] = ''; | 
        
          |  | } | 
        
          |  | return updateQuery | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Reads the value from the record object using the property using a string with dot notation | 
        
          |  | * @param record | 
        
          |  | * @param path | 
        
          |  | */ | 
        
          |  | let readProp = function(record, path) { | 
        
          |  | try { | 
        
          |  | let o = record | 
        
          |  | path = path.replace(/\[(\w+)\]/g, '.$1') | 
        
          |  | path = path.replace(/^\./, '') | 
        
          |  | let a = path.split('.') | 
        
          |  | while (a.length) { | 
        
          |  | let n = a.shift() | 
        
          |  | if (n in o) { | 
        
          |  | o = o[n] | 
        
          |  | } else { | 
        
          |  | return | 
        
          |  | } | 
        
          |  | } | 
        
          |  | return o | 
        
          |  | } catch (err) { | 
        
          |  | console.log('Could not read empty path: ' + path); | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Utility Function for detecting empty objects | 
        
          |  | * @param obj | 
        
          |  | * @returns {boolean} | 
        
          |  | */ | 
        
          |  | function isEmpty(obj) { | 
        
          |  | for(var key in obj) { | 
        
          |  | if(obj.hasOwnProperty(key)) | 
        
          |  | return false; | 
        
          |  | } | 
        
          |  | return true; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Update a document based on matching _id, and $set and unset rules. | 
        
          |  | * @param collectionName | 
        
          |  | * @param field | 
        
          |  | */ | 
        
          |  | let update = function(collectionName, updateQuery, _id) { | 
        
          |  | return new Promise(function(resolve, reject) { | 
        
          |  | if (!_id) { | 
        
          |  | resolve(); | 
        
          |  | return; | 
        
          |  | } | 
        
          |  | let findQuery = {}; | 
        
          |  | try { | 
        
          |  | findQuery['_id'] = ObjectId(_id); | 
        
          |  | } catch (err) { | 
        
          |  | findQuery['_id'] = _id; | 
        
          |  | } | 
        
          |  | if (isEmpty(updateQuery.$set)) delete updateQuery.$set; | 
        
          |  | if (isEmpty(updateQuery.$unset)) delete updateQuery.$unset; | 
        
          |  | dbo.collection(collectionName).updateOne(findQuery, updateQuery, {}, function (err, result) { | 
        
          |  | if (err) console.log(err); | 
        
          |  | resolve(); | 
        
          |  | }); | 
        
          |  | }); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Disconnect from database and exit script | 
        
          |  | */ | 
        
          |  | let cleanExit = function() { | 
        
          |  | _db.close(); | 
        
          |  | console.log('Done! Exiting...'); | 
        
          |  | process.exit(); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | mongoConnection(); |