Skip to content

Commit

Permalink
feat(config): Mongo Seed 2.0 (meanjs#1808)
Browse files Browse the repository at this point in the history
feat(config): Mongo Seed 2.0

Adds a more configurable and easily extended MongoDB Seed feature.

Adds additional options at the collection, and collection item level to
allow more control over how each environment config handles the seeding
feature.

Enforces seed order based on the order of the  environment's seeding configuration object.

Removes the previous SeedDB config file.

Adds chalk to messages logged to the console for readability.

Refactors the Mongo Seed configuration tests.

Adds Gulp tasks to perform Mongo Seed operations for default, prod, and
test environment configurations. Also, adds accommodating npm scripts.
  • Loading branch information
mleanos authored and cicorias committed Sep 12, 2017
1 parent 55f04d7 commit ad76247
Show file tree
Hide file tree
Showing 11 changed files with 1,083 additions and 497 deletions.
66 changes: 47 additions & 19 deletions config/env/development.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,25 +70,53 @@ module.exports = {
seedDB: {
seed: process.env.MONGO_SEED === 'true',
options: {
logResults: process.env.MONGO_SEED_LOG_RESULTS !== 'false',
seedUser: {
username: process.env.MONGO_SEED_USER_USERNAME || 'seeduser',
provider: 'local',
email: process.env.MONGO_SEED_USER_EMAIL || 'user@localhost.com',
firstName: 'User',
lastName: 'Local',
displayName: 'User Local',
roles: ['user']
logResults: process.env.MONGO_SEED_LOG_RESULTS !== 'false'
},
// Order of collections in configuration will determine order of seeding.
// i.e. given these settings, the User seeds will be complete before
// Article seed is performed.
collections: [{
model: 'User',
docs: [{
data: {
username: 'local-admin',
email: 'admin@localhost.com',
firstName: 'Admin',
lastName: 'Local',
roles: ['admin', 'user']
}
}, {
// Set to true to overwrite this document
// when it already exists in the collection.
// If set to false, or missing, the seed operation
// will skip this document to avoid overwriting it.
overwrite: true,
data: {
username: 'local-user',
email: 'user@localhost.com',
firstName: 'User',
lastName: 'Local',
roles: ['user']
}
}]
}, {
model: 'Article',
options: {
// Override log results setting at the
// collection level.
logResults: true
},
seedAdmin: {
username: process.env.MONGO_SEED_ADMIN_USERNAME || 'seedadmin',
provider: 'local',
email: process.env.MONGO_SEED_ADMIN_EMAIL || 'admin@localhost.com',
firstName: 'Admin',
lastName: 'Local',
displayName: 'Admin Local',
roles: ['user', 'admin']
}
}
skip: {
// Skip collection when this query returns results.
// e.g. {}: Only seeds collection when it is empty.
when: {} // Mongoose qualified query
},
docs: [{
data: {
title: 'First Article',
content: 'This is a seeded Article for the development environment'
}
}]
}]
}
};
34 changes: 14 additions & 20 deletions config/env/production.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,25 +91,19 @@ module.exports = {
seedDB: {
seed: process.env.MONGO_SEED === 'true',
options: {
logResults: process.env.MONGO_SEED_LOG_RESULTS !== 'false',
seedUser: {
username: process.env.MONGO_SEED_USER_USERNAME || 'seeduser',
provider: 'local',
email: process.env.MONGO_SEED_USER_EMAIL || 'user@localhost.com',
firstName: 'User',
lastName: 'Local',
displayName: 'User Local',
roles: ['user']
},
seedAdmin: {
username: process.env.MONGO_SEED_ADMIN_USERNAME || 'seedadmin',
provider: 'local',
email: process.env.MONGO_SEED_ADMIN_EMAIL || 'admin@localhost.com',
firstName: 'Admin',
lastName: 'Local',
displayName: 'Admin Local',
roles: ['user', 'admin']
}
}
logResults: process.env.MONGO_SEED_LOG_RESULTS !== 'false'
},
collections: [{
model: 'User',
docs: [{
data: {
username: 'local-admin',
email: 'admin@localhost.com',
firstName: 'Admin',
lastName: 'Local',
roles: ['admin', 'user']
}
}]
}]
}
};
54 changes: 34 additions & 20 deletions config/env/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,25 +80,39 @@ module.exports = {
seedDB: {
seed: process.env.MONGO_SEED === 'true',
options: {
logResults: process.env.MONGO_SEED_LOG_RESULTS !== 'false',
seedUser: {
username: process.env.MONGO_SEED_USER_USERNAME || 'seeduser',
provider: 'local',
email: process.env.MONGO_SEED_USER_EMAIL || 'user@localhost.com',
firstName: 'User',
lastName: 'Local',
displayName: 'User Local',
roles: ['user']
},
seedAdmin: {
username: process.env.MONGO_SEED_ADMIN_USERNAME || 'seedadmin',
provider: 'local',
email: process.env.MONGO_SEED_ADMIN_EMAIL || 'admin@localhost.com',
firstName: 'Admin',
lastName: 'Local',
displayName: 'Admin Local',
roles: ['user', 'admin']
}
}
// Default to not log results for tests
logResults: process.env.MONGO_SEED_LOG_RESULTS === 'true'
},
collections: [{
model: 'User',
docs: [{
overwrite: true,
data: {
username: 'seedadmin',
email: 'admin@localhost.com',
firstName: 'Admin',
lastName: 'Local',
roles: ['admin', 'user']
}
}, {
overwrite: true,
data: {
username: 'seeduser',
email: 'user@localhost.com',
firstName: 'User',
lastName: 'Local',
roles: ['user']
}
}]
}, {
model: 'Article',
docs: [{
overwrite: true,
data: {
title: 'Test Article',
content: 'Code coverage test article!'
}
}]
}]
}
};
2 changes: 1 addition & 1 deletion config/lib/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ var config = require('../config'),
mongooseService = require('./mongoose'),
express = require('./express'),
chalk = require('chalk'),
seed = require('./seed');
seed = require('./mongo-seed');

function seedDB() {
if (config.seedDB && config.seedDB.seed) {
Expand Down
153 changes: 153 additions & 0 deletions config/lib/mongo-seed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
'use strict';

var _ = require('lodash'),
config = require('../config'),
mongoose = require('mongoose'),
chalk = require('chalk');

exports.start = start;

function start(seedConfig) {
return new Promise(function (resolve, reject) {
seedConfig = seedConfig || {};

var options = seedConfig.options || (config.seedDB ? _.clone(config.seedDB.options, true) : {});
var collections = seedConfig.collections || (config.seedDB ? _.clone(config.seedDB.collections, true) : []);

if (!collections.length) {
return resolve();
}

var seeds = collections
.filter(function (collection) {
return collection.model;
});

// Use the reduction pattern to ensure we process seeding in desired order.
seeds.reduce(function (p, item) {
return p.then(function () {
return seed(item, options);
});
}, Promise.resolve()) // start with resolved promise for initial previous (p) item
.then(onSuccessComplete)
.catch(onError);

// Local Promise handlers

function onSuccessComplete() {
if (options.logResults) {
console.log();
console.log(chalk.bold.green('Database Seeding: Mongo Seed complete!'));
console.log();
}

return resolve();
}

function onError(err) {
if (options.logResults) {
console.log();
console.log(chalk.bold.red('Database Seeding: Mongo Seed Failed!'));
console.log(chalk.bold.red('Database Seeding: ' + err));
console.log();
}

return reject(err);
}

});
}

function seed(collection, options) {
// Merge options with collection options
options = _.merge(options || {}, collection.options || {});

return new Promise(function (resolve, reject) {
const Model = mongoose.model(collection.model);
const docs = collection.docs;

var skipWhen = collection.skip ? collection.skip.when : null;

if (!Model.seed) {
return reject(new Error('Database Seeding: Invalid Model Configuration - ' + collection.model + '.seed() not implemented'));
}

if (!docs || !docs.length) {
return resolve();
}

// First check if we should skip this collection
// based on the collection's "skip.when" option.
// NOTE: If it exists, "skip.when" should be a qualified
// Mongoose query that will be used with Model.find().
skipCollection()
.then(seedDocuments)
.then(function () {
return resolve();
})
.catch(function (err) {
return reject(err);
});

function skipCollection() {
return new Promise(function (resolve, reject) {
if (!skipWhen) {
return resolve(false);
}

Model
.find(skipWhen)
.exec(function (err, results) {
if (err) {
return reject(err);
}

if (results && results.length) {
return resolve(true);
}

return resolve(false);
});
});
}

function seedDocuments(skipCollection) {
return new Promise(function (resolve, reject) {

if (skipCollection) {
return onComplete([{ message: chalk.yellow('Database Seeding: ' + collection.model + ' collection skipped') }]);
}

var workload = docs
.filter(function (doc) {
return doc.data;
})
.map(function (doc) {
return Model.seed(doc.data, { overwrite: doc.overwrite });
});

Promise.all(workload)
.then(onComplete)
.catch(onError);

// Local Closures

function onComplete(responses) {
if (options.logResults) {
responses.forEach(function (response) {
if (response.message) {
console.log(chalk.magenta(response.message));
}
});
}

return resolve();
}

function onError(err) {
return reject(err);
}
});
}
});
}
Loading

0 comments on commit ad76247

Please sign in to comment.