keystone
Version:
Web Application Framework and Admin GUI / Content Management System built on Express.js and Mongoose
340 lines (268 loc) • 8.76 kB
JavaScript
/**
* Creates multiple items in one or more Lists
*/
var _ = require('lodash');
var async = require('async');
var utils = require('keystone-utils');
var debug = require('debug')('keystone:core:createItems');
var MONGO_ID_REGEXP = /^[0-9a-fA-F]{8}[0-9a-fA-F]{6}[0-9a-fA-F]{4}[0-9a-fA-F]{6}$/;
function isMongoId (value) {
return MONGO_ID_REGEXP.test(value);
}
function createItems (data, ops, callback) {
var keystone = this;
var options = {
verbose: false,
strict: true,
refs: null,
};
var dashes = '------------------------------------------------';
if (!_.isObject(data)) {
throw new Error('keystone.createItems() requires a data object as the first argument.');
}
if (_.isObject(ops)) {
_.extend(options, ops);
}
if (typeof ops === 'function') {
callback = ops;
}
var lists = _.keys(data);
var refs = options.refs || {};
var stats = {};
// logger function
function writeLog (data) {
console.log(keystone.get('name') + ': ' + data);
}
async.waterfall([
// create items
function (next) {
async.eachSeries(lists, function (key, doneList) {
var list = keystone.list(key);
var relationshipPaths = _.filter(list.fields, { type: 'relationship' }).map(function (i) { return i.path; });
if (!list) {
if (options.strict) {
return doneList({
type: 'invalid list',
message: 'List key ' + key + ' is invalid.',
});
}
if (options.verbose) {
writeLog('Skipping invalid list: ' + key);
}
return doneList();
}
if (!refs[list.key]) {
refs[list.key] = {};
}
stats[list.key] = {
singular: list.singular,
plural: list.plural,
created: 0,
warnings: 0,
};
var itemsProcessed = 0;
var totalItems = data[key].length;
if (options.verbose) {
writeLog(dashes);
writeLog('Processing list: ' + key);
writeLog('Items to create: ' + totalItems);
writeLog(dashes);
}
async.eachSeries(data[key], function (data, doneItem) {
itemsProcessed++;
// Evaluate function properties to allow generated values (excluding relationships)
_.keys(data).forEach(function (i) {
if (typeof data[i] === 'function' && relationshipPaths.indexOf(i) === -1) {
data[i] = data[i]();
if (options.verbose) {
writeLog('Generated dynamic value for [' + i + ']: ' + data[i]);
}
}
});
var doc = data.__doc = new list.model();
if (data.__ref) {
refs[list.key][data.__ref] = doc;
}
async.each(list.fieldsArray, function (field, doneField) {
// skip relationship fields on the first pass.
if (field.type !== 'relationship') {
// TODO: Validate items?
field.updateItem(doc, data, doneField);
} else {
doneField();
}
}, function (err) {
if (!err) {
if (options.verbose) {
var documentName = list.getDocumentName(doc);
writeLog('Creating item [' + itemsProcessed + ' of ' + totalItems + '] - ' + documentName);
}
doc.save(function (err) {
if (err) {
err.model = key;
err.data = data;
debug('error saving ', key);
} else {
stats[list.key].created++;
}
doneItem(err);
});
} else {
doneItem(err);
}
});
}, doneList);
}, next);
},
// link items
function (next) {
async.each(lists, function (key, doneList) {
var list = keystone.list(key);
var relationships = _.filter(list.fields, { type: 'relationship' });
if (!list || !relationships.length) {
return doneList();
}
var itemsProcessed = 0;
var totalItems = data[key].length;
if (options.verbose) {
writeLog(dashes);
writeLog('Processing relationships for: ' + key);
writeLog('Items to process: ' + totalItems);
writeLog(dashes);
}
async.each(data[key], function (srcData, doneItem) {
var doc = srcData.__doc;
var relationshipsUpdated = 0;
itemsProcessed++;
if (options.verbose) {
var documentName = list.getDocumentName(doc);
writeLog('Processing item [' + itemsProcessed + ' of ' + totalItems + '] - ' + documentName);
}
async.each(relationships, function (field, doneField) {
var fieldValue = null;
var refsLookup = null;
if (!field.path) {
writeLog('WARNING: Invalid relationship (undefined list path) [List: ' + key + ']');
stats[list.key].warnings++;
return doneField();
} else {
fieldValue = srcData[field.path];
}
if (!field.refList) {
if (fieldValue) {
writeLog('WARNING: Invalid relationship (undefined reference list) [list: ' + key + '] [path: ' + fieldValue + ']');
stats[list.key].warnings++;
}
return doneField();
}
if (!field.refList.key) {
writeLog('WARNING: Invalid relationship (undefined ref list key) [list: ' + key + '] [field.refList: ' + field.refList + '] [fieldValue: ' + fieldValue + ']');
stats[list.key].warnings++;
return doneField();
} else {
refsLookup = refs[field.refList.key];
}
if (!fieldValue) {
return doneField();
}
// populate relationships from saved refs
if (typeof fieldValue === 'function') {
relationshipsUpdated++;
var fn = fieldValue;
var argsRegExp = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var lists = fn.toString().match(argsRegExp)[1].split(',').map(function (i) { return i.trim(); });
var args = lists.map(function (i) {
return keystone.list(i);
});
var query = fn.apply(keystone, args);
query.exec(function (err, results) {
if (err) { debug('error ', err); }
if (field.many) {
doc.set(field.path, results || []);
} else {
doc.set(field.path, (results && results.length) ? results[0] : undefined);
}
doneField(err);
});
} else if (_.isArray(fieldValue)) {
if (field.many) {
var refsArr = _.compact(fieldValue.map(function (ref) {
return isMongoId(ref) ? ref : refsLookup && refsLookup[ref] && refsLookup[ref].id;
}));
if (options.strict && refsArr.length !== fieldValue.length) {
return doneField({
type: 'invalid ref',
srcData: srcData,
message: 'Relationship ' + list.key + '.' + field.path + ' contains an invalid reference.',
});
}
relationshipsUpdated++;
doc.set(field.path, refsArr);
doneField();
} else {
return doneField({
type: 'invalid data',
srcData: srcData,
message: 'Single-value relationship ' + list.key + '.' + field.path + ' provided as an array.',
});
}
} else if (typeof fieldValue === 'string') {
var refItem = isMongoId(fieldValue) ? fieldValue : refsLookup && refsLookup[fieldValue] && refsLookup[fieldValue].id;
if (!refItem) {
return options.strict ? doneField({
type: 'invalid ref',
srcData: srcData,
message: 'Relationship ' + list.key + '.' + field.path + ' contains an invalid reference: "' + fieldValue + '".',
}) : doneField();
}
relationshipsUpdated++;
doc.set(field.path, field.many ? [refItem] : refItem);
doneField();
} else if (fieldValue && fieldValue.id) {
relationshipsUpdated++;
doc.set(field.path, field.many ? [fieldValue.id] : fieldValue.id);
doneField();
} else {
return doneField({
type: 'invalid data',
srcData: srcData,
message: 'Relationship ' + list.key + '.' + field.path + ' contains an invalid data type.',
});
}
}, function (err) {
if (err) {
debug('error ', err);
return doneItem(err);
}
if (options.verbose) {
writeLog('Populated ' + utils.plural(relationshipsUpdated, '* relationship', '* relationships') + '.');
}
if (relationshipsUpdated) {
doc.save(doneItem);
} else {
doneItem();
}
});
}, doneList);
}, next);
},
], function (err) {
if (err) {
console.error(err);
if ('stack' in err) {
console.trace(err.stack);
}
return callback && callback(err);
}
var msg = '\nSuccessfully created:\n';
_.forEach(stats, function (list) {
msg += '\n* ' + utils.plural(list.created, '* ' + list.singular, '* ' + list.plural);
if (list.warnings) {
msg += '\n ' + utils.plural(list.warnings, '* warning', '* warnings');
}
});
stats.message = msg + '\n';
callback(null, stats);
});
}
module.exports = createItems;