QUnit.test('`lodash ' + command +'`', function(assert) { var done = assert.async(), start = _.after(2, _.once(done)); build(command.split(' '), function(data) { var actualId, basename = path.basename(data.outputPath, '.js'), context = createContext('amd'); context.define = function(id, factory) { actualId = id; context._ = factory(); }; context.define.amd = {}; vm.runInContext(data.source, context); assert.strictEqual(actualId, expectedId, basename); assert.ok(_.isFunction(context._), basename); start(); }); });
QUnit.test('`lodash ' + command +'`', function(assert) { var counter = 0, dirs = _.includes(command, 'c.js'), done = assert.async(), expected = /(\w+)(?=\.js$)/.exec(command)[0]; var start = _.after(2, _.once(function() { if (dirs) { fs.rmrfSync(outputPath); } process.chdir(cwd); done(); })); process.chdir(__dirname); build(command.split(' '), function(data) { var basename = path.basename(data.outputPath, '.js'); assert.strictEqual(basename, expected + (counter++ ? '.min' : ''), command); start(); }); });
render('filelist.json', (list) => { const files = JSON.parse(list), done = _.after(files.length, installModules); files.forEach((i) => render(`templates/${i}`, (data) => { mkdirp(path.dirname(i), dirErr => { if (dirErr) { console.log(chalk.bold.red(' \u2718 ') + i); throw dirErr; } fs.writeFile(i, data, writeErr => { if (writeErr) { console.log(i + chalk.bold.red(' \u2718 ')); throw writeErr; } console.log(i + chalk.bold.green(' \u2714 ')); done(); }); }); })); });
Packages.link = function (packages, callback) { shell.mkdir('-p', PACKAGE_DIR); var dirLinked = _.after(_.keys(packages).length, callback); _.forOwn(packages, function (def, packageName) { if (!def.path || !packageName) return; // Convert colons in package names to dashes for Windows packageName = packageName.replace(/:/g, '-'); var dest = PACKAGE_DIR + '/' + packageName; shell.rm('-fr', dest); var src = resolvePath(def.path); checkPathExist(src, 'Cannot find package ' + packageName + ' at ' + src); shell.ln('-s', src, dest); checkPathExist(src, 'Link failed for ' + dest); dirLinked(); }); };
"webpcss", "Process css file to generate addition css ruless to add webp compatble", function () { // Merge task-specific and/or target-specific options with these defaults. var options = this.options({ baseClass:".webp", replace_from:/\.(png|jpg|jpeg)/, replace_to:".webp" }); var done = _.after(this.files.length, this.async()); // Iterate over all specified file groups. this.files.forEach(function (f) { // Concat specified files. var src = f.src.filter(function (filepath) { // Warn on and remove invalid source files (if nonull was set). if (!grunt.file.exists(filepath)) { grunt.log.warn("Source file \"" + filepath + "\" not found."); return false; } else { return true; } }).map(function (filepath) { // Read file source. return grunt.file.read(filepath); }).join("\n"); webpcss.transform(src, options) .then(function (res) { // Write the destination file. grunt.file.write(f.dest, res); // Print a success message. grunt.log.writeln("File \"" + f.dest + "\" created."); done(); }) .catch(done); }); });
it("should all succeed, no output dir pre-exists", function (done) { var options = { source: inputFile, hostname: "localhost", port: port, selector: "#dynamic-content", outputDir: outputDir, outputDirClean: true, timeout: timeout }; var twice = _.after(2, cleanupSuccess.bind(null, done)); rimraf(outputDir); ss.run(optHelp.decorate(options), twice) .then(testSuccess.bind(null, twice)) .catch(function (e) { checkActualFiles(e.notCompleted) .then(function () { cleanup(done, e); }); }); });
Yuno.prototype.add = function (docs, opts, cb) { var self = this if (_.isFunction(opts)) cb = opts if (_.isPlainObject(docs)) docs = [docs] var errs = [] var docb = _.after(2, function () { cb(errs.length > 0 ? errs[0] : null, docs.length) }) var done = function (err) { if (err) errs.push(err) docb() } this.docstore.batch(docs.map((d) => { return { type: 'put', key: '' + d[self.keyField], value: JSON.stringify(d) } }), done) this.index.add(docs.map((d) => { return { id: d[self.keyField], tokens: self.preprocessor.process(d) } }), done) // process the docs for search indexing }
var updateAppAdmins = module.exports.updateAppAdmins = function(adminUpdates, callback) { var errors = []; var adminsUpdated = _.after(_.keys(adminUpdates).length, function() { if (errors.length > 0) { return callback({'code': 500, 'msg': errors[0].message, 'errors': errors}); } return callback(); }); _.each(adminUpdates, function(adminStatus, userId) { // TODO: Wrap this in a transaction DB.User.update({'isAdmin': adminStatus}, {'where': {'id': userId}}).complete(function(err) { if (err) { log().error({'err': err}, 'Failed to update an administrator of an app'); errors.push(err); } return adminsUpdated(); }); }); };
module.exports.close = function (cb) { var numResponses = 0 , done ; if (server) { numResponses++; } if (db) { numResponses++; } done = _.after(numResponses, cb); if (server) { server.close(done); } if (db) { db.close(done); } };
QUnit.test('`lodash exports=amd' + (command ? ' ' + command + '`' : '` using the default `moduleId`'), function(assert) { var done = assert.async(), start = _.after(2, _.once(done)); build(['template=' + path.join(templatePath, '*.jst'), 'exports=amd'].concat(command || []), function(data) { var actualId, basename = path.basename(data.outputPath, '.js'), context = createContext('amd'); context.define = function(requires, factory) { factory(_); actualId = requires[0]; }; context.define.amd = {}; vm.runInContext(data.source, context); assert.strictEqual(actualId, expectedId, basename); delete _.templates; start(); }); });
it('always honors ZERO as primary key', function(_done) { var self = this , permutations = [ 0, '0', {where: {id: 0}}, {where: {id: '0'}} ] , done = _.after(2 * permutations.length, _done); this.User.bulkCreate([{username: '******'}, {username: '******'}]).success(function() { permutations.forEach(function(perm) { self.User.find(perm).done(function(err, user) { expect(err).to.be.null; expect(user).to.be.null; done(); }).on('sql', function(s) { expect(s.indexOf(0)).not.to.equal(-1); done(); }); }); }); });
before(function (done) { var trigger = _.after(3, done); Group.find({}).remove(function (err) { expect(err).to.be(null); trigger(); }); User.find({}).remove(function (err) { expect(err).to.be(null); var user1 = { name: 'Full name', email: '*****@*****.**', username: '******', password: '******', provider: 'local' }; _user1 = new User(user1); _user1.save(function (err) { expect(err).to.be(null); trigger(); }); var user2 = { name: 'Full name', email: '*****@*****.**', username: '******', password: '******', provider: 'local' }; _user2 = new User(user2); _user2.save(function (err) { expect(err).to.be(null); trigger(); }); }); });
globalAdminClient.tenant.createTenant(tenantDisplayName, function(err, tenant) { assert.ok(!err); if (nrOfApps === 0) { return callback(tenant); } var args = [tenant]; // Gets executed once all the applications have been created var done = _.after(nrOfApps, function() { return callback.apply(callback, args); }); // Create a number of applications _.times(nrOfApps, function(n) { var appDisplayName = util.format('%s %d', tenantDisplayName, n); var host = util.format('%d.%s.grasshopper.local', n, tenantDisplayName); _generateTestApp(globalAdminClient, tenant.id, appDisplayName, host, function(app) { args.push(app); return done(); }); }); });
QUnit.test('`lodash ' + command +'`', function(assert) { var done = assert.async(); var start = _.after(2, _.once(function() { process.chdir(cwd); done(); })); process.chdir(reWildcard.test(command) ? templatePath : __dirname); build([command], function(data) { var basename = path.basename(data.outputPath, '.js'), context = createContext(); var object = { 'a': { 'people': ['fred', 'barney', 'pebbles'] }, 'b': { 'name': 'fred' }, 'c': { 'name': 'es' } }; context._ = _; vm.runInContext(data.source, context); assert.ok(_.includes(basename, 'lodash.templates'), basename); var actual = _.templates.a(object.a); assert.strictEqual(actual.replace(/[\r\n]+/g, ''), '<ul><li>fred</li><li>barney</li><li>pebbles</li></ul>', basename); assert.strictEqual(_.templates.b(object.b), 'hello fred!', basename); assert.strictEqual(_.templates.c(object.c), 'hello es', basename); assert.deepEqual(_.difference(['a', 'b', 'c', 'd', 'e'], _.keys(_.templates)), [], basename); delete _.templates; start(); }); });
var displayResult = function(result) { console.log('inside displayResult'); var lights = result.lights; var len = lights.length; var lightlist = []; var finished = _.after(len, function(data) { /* GET home page. */ router.get('/', function(req, res, next) { res.render('index', { title: 'Huepi', lights: data }); }); // console.log(JSON.stringify(result, null, 2)); }); _.forEach(lights, function(value, key) { console.log(value); var o = { id: value.id, on: value.state.on, rgb: api.getRGBfromXY(value.state.xy[0], value.state.xy[1], value.state.bri), name: value.name // h: Math.floor(value.state.hue / 182), // s: Math.floor((value.state.sat / 255) * 100), // l: Math.floor((value.state.bri / 255) * 60) }; lightlist.push(o); finished(lightlist); // api.lightStatusWithRGB(parseInt(value.id)).done(function(result) { // console.log(result.state); // o.rgb = result.state.rgb; // // }).bind(null, o); }); };
it('should be able to instantiate to distinct loggers', function (done) { var logger1 = Logger.init({ adapters: [{ type: 'callback', application: 'logger1', machine: 'staging', callback: callback1 }] }), logger2 = Logger.init({ adapters: [{ type: 'callback', application: 'logger2', machine: 'staging', callback: callback2 }] }), doneOne = _.after(4, done); logger1.debug('test1'); logger2.debug('test2'); logger1.debug('test1'); logger2.debug('test2'); function callback1(msg) { msg.application.should.equal('LOGGER1'); doneOne(); } function callback2(msg) { msg.application.should.equal('LOGGER2'); doneOne(); } });
function loadStationDetail(quality, callback) { var stationRecords = []; var stations = quality.stations; if (stations == null) { return callback(null, quality); } var done = _.after(stations.length, function() { quality.stations = stationRecords; //console.log('done load station detail : ' + JSON.stringify(quality)); return callback(null, quality); }); _.each(stations, function (station) { Station.findById(station) .select('-_id -__v') .exec(function (error, stationRecord) { if (error) { console.log("Fail to load summary : " + station); } else { stationRecords.push(stationRecord); } done(); }); }); }
var configureManagers = function(_next) { var amount = _.size(managers); if(!amount) return _next(); var next = _.after(amount, _next); _.each(managers, function(conf) { conf.exchange = conf.exchange.toLowerCase(); // make sure we the exchange is configured correctly // for trading. var invalid = exchangeChecker.cantTrade(conf); if(invalid) throw invalid; if(conf.exchange === 'bitstamp') throw 'Live trading currently broken at Bitstamp! :('; var manager = new Manager(conf); consultant.on('advice', manager.trade); manager.on('ready', next); }); }
QUnit.test('`lodash settings=...' + (command ? ' ' + command : '') + '`', function(assert) { var done = assert.async(), start = _.after(2, _.once(done)); build(['template=' + path.join(templatePath, '*.tpl'), 'settings={interpolate:/{{([\\s\\S]+?)}}/}'].concat(command || []), function(data) { var actualId, basename = path.basename(data.outputPath, '.js'), context = createContext('amd'); context.define = function(requires, factory) { factory(_); actualId = requires[0]; }; context.define.amd = {}; vm.runInContext(data.source, context); assert.strictEqual(actualId, expectedId, basename); assert.strictEqual(_.templates.f({ 'name': 'mustache' }), 'hall\xe5 mustache!', basename); delete _.templates; start(); }); });
}, function(er, files) { var deferredComplete = _.after(files.length, complete); _.each(files, function(bowerJsonPath) { var dir = path.dirname(bowerJsonPath); var bowerComponentsDir = path.join(dir, "bower_components"); gutil.log("Bower start:", bowerJsonPath); if (!fs.existsSync(bowerComponentsDir)) { fs.mkdirSync(bowerComponentsDir); gutil.log(" mkdir:", bowerComponentsDir); } var install = bower.commands.install( [], {}, {cwd: dir, directory: "bower_components"} ); install.on("log", function(log) { gutil.log("Bower/" + bowerJsonPath + ":" + log.message); }); install.on("end", function() { gutil.log("Bower complete:", bowerJsonPath); deferredComplete(); }); }); });
QUnit.test('recursive path `' + command + '`', function(assert) { var done = assert.async(); var start = _.after(2, _.once(function() { if (!isWindows) { fs.unlinkSync(quotesTemplatePath); } process.chdir(cwd); done(); })); if (index) { process.chdir(templatePath); } if (!isWindows) { // Manually create template `'".jst` to avoid issues in Windows. fs.writeFileSync(quotesTemplatePath, 'hello <%= name %>', 'utf8'); } build([command], function(data) { var basename = path.basename(data.outputPath, '.js'), context = createContext(); context._ = _; vm.runInContext(data.source, context); assert.strictEqual(_.templates.b({ 'name': 'fred' }), 'hello fred!', basename); assert.strictEqual(_.templates.c({ 'name': 'barney' }), 'hello barney', basename); assert.strictEqual(_.templates.c.c({ 'name': 'pebbles' }), 'hello pebbles!', basename); if (!isWindows) { assert.strictEqual(_.templates.c['\'"']({ 'name': 'quotes' }), 'hello quotes', basename); } delete _.templates; start(); }); });
it('always honors ZERO as primary key', function(_done) { var permutations = [ 0, '0', {where: {id: 0}}, {where: {id: '0'}} ] , done = _.after(2 * permutations.length, _done); this.User.create({name: 'jack'}).success(function (jack) { this.User.create({name: 'jill'}).success(function (jill) { permutations.forEach(function(perm) { this.User.find(perm).done(function(err, user) { expect(err).toBeNull(); expect(user).toBeNull(); done(); }).on('sql', function(s) { expect(s.indexOf(0)).not.toEqual(-1); done(); }) }.bind(this)) }.bind(this)) }.bind(this)) })
var generatePng = function(whiteboardElements, callback) { // Variables that will keep track of what the outer corners of the elements in the canvas are var left = Number.MAX_VALUE; var top = Number.MAX_VALUE; var right = Number.MIN_VALUE; var bottom = Number.MIN_VALUE; // A variable that will keep track of all the deserialized elements in the whiteboard var deserializedElements = []; var render = _.after(whiteboardElements.length, function() { // At this point we've figured out what the left-most and right-most element is. By subtracting // their X-coordinates we get the desired width of the canvas. The height can be calculated in // a similar way by using the Y-coordinates var width = right - left; var height = bottom - top; // Add a bit of padding so elements don't stick to the side width += (2 * WHITEBOARD_PADDING); height += (2 * WHITEBOARD_PADDING); // Create a canvas and pan it to the top-left corner var canvas = createCanvas(width, height); var pt = new fabric.Point(left - WHITEBOARD_PADDING, top - WHITEBOARD_PADDING); canvas.absolutePan(pt); // Don't render each element when it's added, rather render the entire Canvas once all elements // have been added. This is significantly faster canvas.renderOnAddRemove = false; // Once all elements have been added to the canvas, restore // the layer order and convert to PNG var finishRender = _.after(deserializedElements.length, function() { // Ensure each element is placed at the right index. This can only happen // once all elements have been added to the canvas canvas.getObjects().sort(function(elementA, elementB) { return elementA.index - elementB.index; }); // Render the canvas canvas.renderAll(); // Convert the canvas to a PNG file and return the data return canvas.nodeCanvas.toBuffer(callback); }); // Add each element to the canvas _.each(deserializedElements, function(deserializedElement) { canvas.add(deserializedElement); finishRender(); }); }); _.each(whiteboardElements, function(whiteboardElement) { // Canvas doesn't seem to deal terribly well with text elements that specify a prioritized list // of font family names. It seems that the only way to render custom fonts is to only specify one if (whiteboardElement.fontFamily) { whiteboardElement.fontFamily = 'HelveticaNeue-Light'; } // Deserialize the element, get its boundary and check how large // the canvas should be to display the element entirely deserializeElement(whiteboardElement, function(deserializedElement) { var bound = deserializedElement.getBoundingRect(); left = Math.min(left, bound.left); top = Math.min(top, bound.top); right = Math.max(right, bound.left + bound.width); bottom = Math.max(bottom, bound.top + bound.height); // Retain a reference to the deserialized elements. This allows for moving each element // to the right index once all alements have been added to the canvas deserializedElements.push(deserializedElement); render(); }); }); };
_.forEach(models, (definition, model) => { definition.globalName = _.upperFirst(_.camelCase(definition.globalId)); // Make sure the model has a connection. // If not, use the default connection. if (_.isEmpty(definition.connection)) { definition.connection = strapi.config.currentEnvironment.database.defaultConnection; } // Make sure this connection exists. if (!_.has(strapi.config.connections, definition.connection)) { strapi.log.error('The connection `' + definition.connection + '` specified in the `' + model + '` model does not exist.'); strapi.stop(); } // Add some informations about ORM & client connection definition.orm = 'mongoose'; definition.client = _.get(strapi.config.connections[definition.connection], 'client'); definition.associations = []; // Register the final model for Mongoose. definition.loadedModel = _.cloneDeep(definition.attributes); // Initialize the global variable with the // capitalized model name. if (!plugin) { global[definition.globalName] = {}; } if (_.isEmpty(definition.attributes)) { // Generate empty schema _.set(strapi.config.hook.settings.mongoose, 'collections.' + mongooseUtils.toCollectionName(definition.globalName) + '.schema', new instance.Schema({})); return loadedAttributes(); } // Call this callback function after we are done parsing // all attributes for relationships-- see below. const done = _.after(_.size(definition.attributes), () => { // Generate schema without virtual populate const schema = new instance.Schema(_.omitBy(definition.loadedModel, model => { return model.type === 'virtual'; })); _.set(strapi.config.hook.settings.mongoose, 'collections.' + mongooseUtils.toCollectionName(definition.globalName) + '.schema', schema); loadedAttributes(); }); // Add every relationships to the loaded model for Bookshelf. // Basic attributes don't need this-- only relations. _.forEach(definition.attributes, (details, name) => { const verbose = _.get(utilsModels.getNature(details, name, undefined, model.toLowerCase()), 'verbose') || ''; // Build associations key utilsModels.defineAssociations(model.toLowerCase(), definition, details, name); if (_.isEmpty(verbose)) { definition.loadedModel[name].type = utils(instance).convertType(details.type); } switch (verbose) { case 'hasOne': { const ref = details.plugin ? strapi.plugins[details.plugin].models[details.model].globalId : strapi.models[details.model].globalId; definition.loadedModel[name] = { type: instance.Schema.Types.ObjectId, ref }; break; } case 'hasMany': { const FK = _.find(definition.associations, {alias: name}); const ref = details.plugin ? strapi.plugins[details.plugin].models[details.collection].globalId : strapi.models[details.collection].globalId; if (FK) { definition.loadedModel[name] = { type: 'virtual', ref, via: FK.via, justOne: false }; // Set this info to be able to see if this field is a real database's field. details.isVirtual = true; } else { definition.loadedModel[name] = [{ type: instance.Schema.Types.ObjectId, ref }]; } break; } case 'belongsTo': { const FK = _.find(definition.associations, {alias: name}); const ref = details.plugin ? strapi.plugins[details.plugin].models[details.model].globalId : strapi.models[details.model].globalId; if (FK && FK.nature !== 'oneToOne' && FK.nature !== 'manyToOne' && FK.nature !== 'oneWay' && FK.nature !== 'oneToMorph') { definition.loadedModel[name] = { type: 'virtual', ref, via: FK.via, justOne: true }; // Set this info to be able to see if this field is a real database's field. details.isVirtual = true; } else { definition.loadedModel[name] = { type: instance.Schema.Types.ObjectId, ref }; } break; } case 'belongsToMany': { const FK = _.find(definition.associations, {alias: name}); const ref = details.plugin ? strapi.plugins[details.plugin].models[details.collection].globalId : strapi.models[details.collection].globalId; // One-side of the relationship has to be a virtual field to be bidirectional. if ((FK && _.isUndefined(FK.via)) || details.dominant !== true) { definition.loadedModel[name] = { type: 'virtual', ref, via: FK.via }; // Set this info to be able to see if this field is a real database's field. details.isVirtual = true; } else { definition.loadedModel[name] = [{ type: instance.Schema.Types.ObjectId, ref }]; } break; } case 'morphOne': { const FK = _.find(definition.associations, {alias: name}); const ref = details.plugin ? strapi.plugins[details.plugin].models[details.model].globalId : strapi.models[details.model].globalId; definition.loadedModel[name] = { type: 'virtual', ref, via: `${FK.via}.ref`, justOne: true }; // Set this info to be able to see if this field is a real database's field. details.isVirtual = true; break; } case 'morphMany': { const FK = _.find(definition.associations, {alias: name}); const ref = details.plugin ? strapi.plugins[details.plugin].models[details.collection].globalId : strapi.models[details.collection].globalId; definition.loadedModel[name] = { type: 'virtual', ref, via: `${FK.via}.ref` }; // Set this info to be able to see if this field is a real database's field. details.isVirtual = true; break; } case 'belongsToMorph': { definition.loadedModel[name] = { kind: String, [details.filter]: String, ref: { type: instance.Schema.Types.ObjectId, refPath: `${name}.kind` } }; break; } case 'belongsToManyMorph': { definition.loadedModel[name] = [{ kind: String, [details.filter]: String, ref: { type: instance.Schema.Types.ObjectId, refPath: `${name}.kind` } }]; break; } default: break; } done(); }); });
return new Promise(function (resolve) { var resolveAfterAllJobs = _.after(jobs.length, resolve); queue.on('completed', resolveAfterAllJobs); });
const mountModels = (models, target, plugin = false) => { if (!target) return; const loadedAttributes = _.after(_.size(models), () => { _.forEach(models, (definition, model) => { try { let collection = strapi.config.hook.settings.mongoose.collections[mongooseUtils.toCollectionName(definition.globalName)]; // Set the default values to model settings. _.defaults(definition, { primaryKey: '_id' }); // Initialize lifecycle callbacks. const preLifecycle = { validate: 'beforeCreate', findOneAndUpdate: 'beforeUpdate', findOneAndRemove: 'beforeDestroy', remove: 'beforeDestroy', update: 'beforeUpdate', find: 'beforeFetchAll', findOne: 'beforeFetch', save: 'beforeSave' }; /* Override populate path for polymorphic association. It allows us to make Upload.find().populate('related') instead of Upload.find().populate('related.item') */ const morphAssociations = definition.associations.filter(association => association.nature.toLowerCase().indexOf('morph') !== -1); if (morphAssociations.length > 0) { morphAssociations.forEach(association => { Object.keys(preLifecycle) .filter(key => key.indexOf('find') !== -1) .forEach(key => { collection.schema.pre(key, function (next) { if (this._mongooseOptions.populate && this._mongooseOptions.populate[association.alias]) { if (association.nature === 'oneToManyMorph' || association.nature === 'manyToManyMorph') { this._mongooseOptions.populate[association.alias].match = { [`${association.via}.${association.filter}`]: association.alias, [`${association.via}.kind`]: definition.globalId }; // Select last related to an entity. this._mongooseOptions.populate[association.alias].options = { sort: '-createdAt' }; } else { this._mongooseOptions.populate[association.alias].path = `${association.alias}.ref`; } } next(); }); }); }); } _.forEach(preLifecycle, (fn, key) => { if (_.isFunction(target[model.toLowerCase()][fn])) { collection.schema.pre(key, function (next) { target[model.toLowerCase()][fn](this).then(next).catch(err => strapi.log.error(err)); }); } }); const postLifecycle = { validate: 'afterCreate', findOneAndRemove: 'afterDestroy', remove: 'afterDestroy', update: 'afterUpdate', find: 'afterFetchAll', findOne: 'afterFetch', save: 'afterSave' }; // Mongoose doesn't allow post 'remove' event on model. // See https://github.com/Automattic/mongoose/issues/3054 _.forEach(postLifecycle, (fn, key) => { if (_.isFunction(target[model.toLowerCase()][fn])) { collection.schema.post(key, function (doc, next) { target[model.toLowerCase()][fn](this, doc).then(next).catch(err => { strapi.log.error(err); next(err); }); }); } }); // Add virtual key to provide populate and reverse populate _.forEach(_.pickBy(definition.loadedModel, model => { return model.type === 'virtual'; }), (value, key) => { collection.schema.virtual(key.replace('_v', ''), { ref: value.ref, localField: '_id', foreignField: value.via, justOne: value.justOne || false }); }); // Use provided timestamps if the elemnets in the array are string else use default. if (_.isArray(_.get(definition, 'options.timestamps'))) { const timestamps = { createdAt: _.isString(_.get(definition, 'options.timestamps[0]')) ? _.get(definition, 'options.timestamps[0]') : 'createdAt', updatedAt: _.isString(_.get(definition, 'options.timestamps[1]')) ? _.get(definition, 'options.timestamps[1]') : 'updatedAt' }; collection.schema.set('timestamps', timestamps); } else { collection.schema.set('timestamps', _.get(definition, 'options.timestamps') === true); } collection.schema.set('minimize', _.get(definition, 'options.minimize', false) === true); collection.schema.options.toObject = collection.schema.options.toJSON = { virtuals: true, transform: function (doc, returned, opts) { // Remover $numberDecimal nested property. Object.keys(returned) .filter(key => returned[key] instanceof mongoose.Types.Decimal128) .forEach((key, index) => { // Parse to float number. returned[key] = parseFloat(returned[key].toString()); }); morphAssociations.forEach(association => { if (Array.isArray(returned[association.alias]) && returned[association.alias].length > 0) { // Reformat data by bypassing the many-to-many relationship. switch (association.nature) { case 'oneMorphToOne': returned[association.alias] = returned[association.alias][0].ref; break; case 'manyMorphToMany': case 'manyMorphToOne': returned[association.alias] = returned[association.alias].map(obj => obj.ref); break; default: } } }); } }; // Instantiate model. const Model = instance.model(definition.globalId, collection.schema, definition.collectionName); if (!plugin) { global[definition.globalName] = Model; } // Expose ORM functions through the `target` object. target[model] = _.assign(Model, target[model]); // Push attributes to be aware of model schema. target[model]._attributes = definition.attributes; target[model].updateRelations = relations.update; } catch (err) { strapi.log.error('Impossible to register the `' + model + '` model.'); strapi.log.error(err); strapi.stop(); } }); }); // Parse every authenticated model. _.forEach(models, (definition, model) => { definition.globalName = _.upperFirst(_.camelCase(definition.globalId)); // Make sure the model has a connection. // If not, use the default connection. if (_.isEmpty(definition.connection)) { definition.connection = strapi.config.currentEnvironment.database.defaultConnection; } // Make sure this connection exists. if (!_.has(strapi.config.connections, definition.connection)) { strapi.log.error('The connection `' + definition.connection + '` specified in the `' + model + '` model does not exist.'); strapi.stop(); } // Add some informations about ORM & client connection definition.orm = 'mongoose'; definition.client = _.get(strapi.config.connections[definition.connection], 'client'); definition.associations = []; // Register the final model for Mongoose. definition.loadedModel = _.cloneDeep(definition.attributes); // Initialize the global variable with the // capitalized model name. if (!plugin) { global[definition.globalName] = {}; } if (_.isEmpty(definition.attributes)) { // Generate empty schema _.set(strapi.config.hook.settings.mongoose, 'collections.' + mongooseUtils.toCollectionName(definition.globalName) + '.schema', new instance.Schema({})); return loadedAttributes(); } // Call this callback function after we are done parsing // all attributes for relationships-- see below. const done = _.after(_.size(definition.attributes), () => { // Generate schema without virtual populate const schema = new instance.Schema(_.omitBy(definition.loadedModel, model => { return model.type === 'virtual'; })); _.set(strapi.config.hook.settings.mongoose, 'collections.' + mongooseUtils.toCollectionName(definition.globalName) + '.schema', schema); loadedAttributes(); }); // Add every relationships to the loaded model for Bookshelf. // Basic attributes don't need this-- only relations. _.forEach(definition.attributes, (details, name) => { const verbose = _.get(utilsModels.getNature(details, name, undefined, model.toLowerCase()), 'verbose') || ''; // Build associations key utilsModels.defineAssociations(model.toLowerCase(), definition, details, name); if (_.isEmpty(verbose)) { definition.loadedModel[name].type = utils(instance).convertType(details.type); } switch (verbose) { case 'hasOne': { const ref = details.plugin ? strapi.plugins[details.plugin].models[details.model].globalId : strapi.models[details.model].globalId; definition.loadedModel[name] = { type: instance.Schema.Types.ObjectId, ref }; break; } case 'hasMany': { const FK = _.find(definition.associations, {alias: name}); const ref = details.plugin ? strapi.plugins[details.plugin].models[details.collection].globalId : strapi.models[details.collection].globalId; if (FK) { definition.loadedModel[name] = { type: 'virtual', ref, via: FK.via, justOne: false }; // Set this info to be able to see if this field is a real database's field. details.isVirtual = true; } else { definition.loadedModel[name] = [{ type: instance.Schema.Types.ObjectId, ref }]; } break; } case 'belongsTo': { const FK = _.find(definition.associations, {alias: name}); const ref = details.plugin ? strapi.plugins[details.plugin].models[details.model].globalId : strapi.models[details.model].globalId; if (FK && FK.nature !== 'oneToOne' && FK.nature !== 'manyToOne' && FK.nature !== 'oneWay' && FK.nature !== 'oneToMorph') { definition.loadedModel[name] = { type: 'virtual', ref, via: FK.via, justOne: true }; // Set this info to be able to see if this field is a real database's field. details.isVirtual = true; } else { definition.loadedModel[name] = { type: instance.Schema.Types.ObjectId, ref }; } break; } case 'belongsToMany': { const FK = _.find(definition.associations, {alias: name}); const ref = details.plugin ? strapi.plugins[details.plugin].models[details.collection].globalId : strapi.models[details.collection].globalId; // One-side of the relationship has to be a virtual field to be bidirectional. if ((FK && _.isUndefined(FK.via)) || details.dominant !== true) { definition.loadedModel[name] = { type: 'virtual', ref, via: FK.via }; // Set this info to be able to see if this field is a real database's field. details.isVirtual = true; } else { definition.loadedModel[name] = [{ type: instance.Schema.Types.ObjectId, ref }]; } break; } case 'morphOne': { const FK = _.find(definition.associations, {alias: name}); const ref = details.plugin ? strapi.plugins[details.plugin].models[details.model].globalId : strapi.models[details.model].globalId; definition.loadedModel[name] = { type: 'virtual', ref, via: `${FK.via}.ref`, justOne: true }; // Set this info to be able to see if this field is a real database's field. details.isVirtual = true; break; } case 'morphMany': { const FK = _.find(definition.associations, {alias: name}); const ref = details.plugin ? strapi.plugins[details.plugin].models[details.collection].globalId : strapi.models[details.collection].globalId; definition.loadedModel[name] = { type: 'virtual', ref, via: `${FK.via}.ref` }; // Set this info to be able to see if this field is a real database's field. details.isVirtual = true; break; } case 'belongsToMorph': { definition.loadedModel[name] = { kind: String, [details.filter]: String, ref: { type: instance.Schema.Types.ObjectId, refPath: `${name}.kind` } }; break; } case 'belongsToManyMorph': { definition.loadedModel[name] = [{ kind: String, [details.filter]: String, ref: { type: instance.Schema.Types.ObjectId, refPath: `${name}.kind` } }]; break; } default: break; } done(); }); }); };
initialize: cb => { if (_.isEmpty(strapi.models) || !_.pickBy(strapi.config.connections, { connector: 'strapi-redis' })) { return cb(); } const connections = _.pickBy(strapi.config.connections, { connector: 'strapi-redis' }); if(_.size(connections) === 0) { cb(); } const done = _.after(_.size(connections), () => { cb(); }); // For each connection in the config register a new Knex connection. _.forEach(connections, (connection, name) => { // Apply defaults _.defaults(connection.settings, strapi.config.hook.settings.redis); try { const redis = new Redis(_.defaultsDeep({ port: _.get(connection.settings, 'port'), host: _.get(connection.settings, 'host'), options: { db: _.get(connection.options, 'database') || 0 } }, strapi.config.hook.settings.redis)); redis.on('error', err => { strapi.log.error(err); process.exit(0); return; }); // Utils function. // Behavior: Try to retrieve data from Redis, if null // execute callback and set the value in Redis for this serial key. redis.cache = async ({ expired = 60 * 60, serial }, cb, type) => { if (_.isEmpty(serial)) { strapi.log.warn( `Be careful, you're using cache() function of strapi-redis without serial` ); const traces = stackTrace.get(); strapi.log.warn( `> [${traces[1].getLineNumber()}] ${traces[1] .getFileName() .replace(strapi.config.appPath, '')}` ); return await cb(); } let cache = await redis.get(serial); if (!cache) { cache = await cb(); if ( cache && _.get(connection, 'options.disabledCaching') !== true ) { switch (type) { case 'json': redis.set(serial, JSON.stringify(cache), 'ex', expired); break; case 'int': default: redis.set(serial, cache, 'ex', expired); break; } } } switch (type) { case 'int': return parseInt(cache); case 'float': return _.toNumber(cache); case 'json': try { return _.isObject(cache) ? cache : JSON.parse(cache); } catch (e) { return cache; } default: return cache; } }; // Define as new connection. strapi.connections[name] = redis; // Expose global if (_.get(connection, 'options.global') !== false) { global[_.get(connection, 'options.globalName') || 'redis'] = redis; } if (_.get(connection, 'options.debug') === true) { redis.monitor((err, monitor) => { // Entering monitoring mode. monitor.on('monitor', (time, args) => { console.log(time + ': ' + util.inspect(args)); }); }); } redis.on('ready', () => { done(); }); } catch (e) { cb(e); return false; } }); }
_.forEach(models, (definition, model) => { definition.globalName = _.upperFirst(_.camelCase(definition.globalId)); _.defaults(definition, { primaryKey: 'id' }); // Define local GLOBALS to expose every models in this file. GLOBALS[definition.globalId] = {}; // Add some informations about ORM & client connection & tableName definition.orm = 'bookshelf'; definition.client = _.get(connection.settings, 'client'); // Register the final model for Bookshelf. const loadedModel = _.assign({ tableName: definition.collectionName, hasTimestamps: _.get(definition, 'options.timestamps') === true, idAttribute: _.get(definition, 'options.idAttribute', 'id'), associations: [], defaults: Object.keys(definition.attributes).reduce((acc, current) => { if (definition.attributes[current].type && definition.attributes[current].default) { acc[current] = definition.attributes[current].default; } return acc; }, {}) }, definition.options); if (_.isString(_.get(connection, 'options.pivot_prefix'))) { loadedModel.toJSON = function(options = {}) { const { shallow = false, omitPivot = false } = options; const attributes = this.serialize(options); if (!shallow) { const pivot = this.pivot && !omitPivot && this.pivot.attributes; // Remove pivot attributes with prefix. _.keys(pivot).forEach(key => delete attributes[`${PIVOT_PREFIX}${key}`]); // Add pivot attributes without prefix. const pivotAttributes = _.mapKeys(pivot, (value, key) => `${connection.options.pivot_prefix}${key}`); return Object.assign({}, attributes, pivotAttributes); } return attributes; }; } // Initialize the global variable with the // capitalized model name. if (!plugin) { global[definition.globalName] = {}; } // Call this callback function after we are done parsing // all attributes for relationships-- see below. const done = _.after(_.size(definition.attributes), () => { try { // External function to map key that has been updated with `columnName` const mapper = (params = {}) => _.mapKeys(params, (value, key) => { const attr = definition.attributes[key] || {}; return _.isPlainObject(attr) && _.isString(attr['columnName']) ? attr['columnName'] : key; }); // Update serialize to reformat data for polymorphic associations. loadedModel.serialize = function(options) { const attrs = _.clone(this.attributes); if (options && options.shallow) { return attrs; } const relations = this.relations; // Extract association except polymorphic. const associations = definition.associations .filter(association => association.nature.toLowerCase().indexOf('morph') === -1); // Extract polymorphic association. const polymorphicAssociations = definition.associations .filter(association => association.nature.toLowerCase().indexOf('morph') !== -1); polymorphicAssociations.map(association => { // Retrieve relation Bookshelf object. const relation = relations[association.alias]; if (relation) { // Extract raw JSON data. attrs[association.alias] = relation.toJSON ? relation.toJSON(options) : relation; // Retrieve opposite model. const model = association.plugin ? strapi.plugins[association.plugin].models[association.collection || association.model]: strapi.models[association.collection || association.model]; // Reformat data by bypassing the many-to-many relationship. switch (association.nature) { case 'oneToManyMorph': attrs[association.alias] = attrs[association.alias][model.collectionName]; break; case 'manyToManyMorph': attrs[association.alias] = attrs[association.alias].map(rel => rel[model.collectionName]); break; case 'oneMorphToOne': attrs[association.alias] = attrs[association.alias].related; break; case 'manyMorphToOne': case 'manyMorphToMany': attrs[association.alias] = attrs[association.alias].map(obj => obj.related); break; default: } } }); associations.map(association => { const relation = relations[association.alias]; if (relation) { // Extract raw JSON data. attrs[association.alias] = relation.toJSON ? relation.toJSON(options) : relation; } }); return attrs; } // Initialize lifecycle callbacks. loadedModel.initialize = function() { const lifecycle = { creating: 'beforeCreate', created: 'afterCreate', destroying: 'beforeDestroy', destroyed: 'afterDestroy', updating: 'beforeUpdate', updated: 'afterUpdate', fetching: 'beforeFetch', 'fetching:collection': 'beforeFetchCollection', fetched: 'afterFetch', 'fetched:collection': 'afterFetchCollection', saving: 'beforeSave', saved: 'afterSave' }; _.forEach(lifecycle, (fn, key) => { if (_.isFunction(target[model.toLowerCase()][fn])) { this.on(key, target[model.toLowerCase()][fn]); } }); // Update withRelated level to bypass many-to-many association for polymorphic relationshiips. // Apply only during fetching. this.on('fetching fetching:collection', (instance, attrs, options) => { if (_.isArray(options.withRelated)) { options.withRelated = options.withRelated.map(path => { const association = definition.associations .filter(association => association.nature.toLowerCase().indexOf('morph') !== -1) .filter(association => association.alias === path || association.via === path)[0]; if (association) { // Override on polymorphic path only. if (_.isString(path) && path === association.via) { return `related.${association.via}`; } else if (_.isString(path) && path === association.alias) { // MorphTo side. if (association.related) { return `${association.alias}.related`; } // oneToMorph or manyToMorph side. // Retrieve collection name because we are using it to build our hidden model. const model = association.plugin ? strapi.plugins[association.plugin].models[association.collection || association.model]: strapi.models[association.collection || association.model]; return { [`${association.alias}.${model.collectionName}`]: function(query) { query.orderBy('created_at', 'desc'); } }; } } return path; }); } return _.isFunction( target[model.toLowerCase()]['beforeFetchCollection'] ) ? target[model.toLowerCase()]['beforeFetchCollection'] : Promise.resolve(); }); this.on('saving', (instance, attrs, options) => { instance.attributes = mapper(instance.attributes); attrs = mapper(attrs); return _.isFunction( target[model.toLowerCase()]['beforeSave'] ) ? target[model.toLowerCase()]['beforeSave'] : Promise.resolve(); }); }; loadedModel.hidden = _.keys( _.keyBy( _.filter(definition.attributes, (value, key) => { if ( value.hasOwnProperty('columnName') && !_.isEmpty(value.columnName) && value.columnName !== key ) { return true; } }), 'columnName' ) ); GLOBALS[definition.globalId] = ORM.Model.extend(loadedModel); if (!plugin) { // Only expose as real global variable the models which // are not scoped in a plugin. global[definition.globalId] = GLOBALS[definition.globalId]; } // Expose ORM functions through the `strapi.models[xxx]` // or `strapi.plugins[xxx].models[yyy]` object. target[model] = _.assign(GLOBALS[definition.globalId], target[model]); // Push attributes to be aware of model schema. target[model]._attributes = definition.attributes; databaseUpdate.push(new Promise(async (resolve) => { // Equilize database tables const handler = async (table, attributes) => { const tableExist = await ORM.knex.schema.hasTable(table); const getType = (attribute, name) => { let type; if (!attribute.type) { // Add integer value if there is a relation const relation = definition.associations.find((association) => { return association.alias === name; }); switch (relation.nature) { case 'oneToOne': case 'manyToOne': type = definition.client === 'pg' ? 'integer' : 'int'; break; default: return null; } } else { switch (attribute.type) { case 'text': case 'json': type = 'text'; break; case 'string': case 'enumeration': case 'password': case 'email': type = 'varchar(255)'; break; case 'integer': case 'biginteger': type = definition.client === 'pg' ? 'integer' : 'int'; break; case 'float': type = definition.client === 'pg' ? 'double precision' : 'double'; break; case 'decimal': type = 'decimal'; break; case 'date': case 'time': case 'datetime': case 'timestamp': type = definition.client === 'pg' ? 'timestamp with time zone' : 'timestamp DEFAULT CURRENT_TIMESTAMP'; break; case 'timestampUpdate': type = definition.client === 'pg' ? 'timestamp with time zone' : 'timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'; break; case 'boolean': type = 'boolean'; break; default: } } return type; }; // Apply field type of attributes definition const generateColumns = (attrs, start) => { return Object.keys(attrs).reduce((acc, attr) => { const attribute = attributes[attr]; const type = getType(attribute, attr); if (type) { acc.push(`${quote}${attr}${quote} ${type}`); } return acc; }, start); }; if (!tableExist) { const columns = generateColumns(attributes, [`id ${definition.client === 'pg' ? 'SERIAL' : 'INT AUTO_INCREMENT'} NOT NULL PRIMARY KEY`]).join(',\n\r'); // Create table await ORM.knex.raw(` CREATE TABLE ${quote}${table}${quote} ( ${columns} ) `); } else { const columns = Object.keys(attributes); // Fetch existing column const columnsExist = await Promise.all(columns.map(attribute => ORM.knex.schema.hasColumn(table, attribute) )); const columnsToAdd = {}; // Get columns to add columnsExist.forEach((columnExist, index) => { const attribute = attributes[columns[index]]; if (!columnExist) { columnsToAdd[columns[index]] = attribute; } }); // Generate and execute query to add missing column if (Object.keys(columnsToAdd).length > 0) { const columns = generateColumns(columnsToAdd, []); const queries = columns.reduce((acc, attribute) => { acc.push(`ALTER TABLE ${quote}${table}${quote} ADD ${attribute};`) return acc; }, []).join('\n\r'); await ORM.knex.raw(queries); } // Execute query to update column type await Promise.all(columns.map(attribute => new Promise(async (resolve) => { const type = getType(attributes[attribute], attribute); if (type) { const changeType = definition.client === 'pg' ? `ALTER COLUMN ${quote}${attribute}${quote} TYPE ${type} USING ${quote}${attribute}${quote}::${type}` : `CHANGE ${quote}${attribute}${quote} ${quote}${attribute}${quote} ${type} `; const changeRequired = definition.client === 'pg' ? `ALTER COLUMN ${quote}${attribute}${quote} ${attributes[attribute].required ? 'SET' : 'DROP'} NOT NULL` : `CHANGE ${quote}${attribute}${quote} ${quote}${attribute}${quote} ${type} ${attributes[attribute].required ? 'NOT' : ''} NULL`; await ORM.knex.raw(`ALTER TABLE ${quote}${table}${quote} ${changeType}`); await ORM.knex.raw(`ALTER TABLE ${quote}${table}${quote} ${changeRequired}`); } resolve(); }) )); } }; const quote = definition.client === 'pg' ? '"' : '`'; // Add created_at and updated_at field if timestamp option is true if (loadedModel.hasTimestamps) { definition.attributes['created_at'] = { type: 'timestamp' }; definition.attributes['updated_at'] = { type: 'timestampUpdate' }; } // Equilize tables await handler(loadedModel.tableName, definition.attributes); // Equilize polymorphic releations const morphRelations = definition.associations.find((association) => { return association.nature.toLowerCase().includes('morphto'); }); if (morphRelations) { const attributes = { [`${loadedModel.tableName}_id`]: { type: 'integer' }, [`${morphRelations.alias}_id`]: { type: 'integer' }, [`${morphRelations.alias}_type`]: { type: 'text' }, [definition.attributes[morphRelations.alias].filter]: { type: 'text' } }; await handler(`${loadedModel.tableName}_morph`, attributes); } // Equilize many to many releations const manyRelations = definition.associations.find((association) => { return association.nature === 'manyToMany'; }); if (manyRelations && manyRelations.dominant) { const collection = manyRelations.plugin ? strapi.plugins[manyRelations.plugin].models[manyRelations.collection]: strapi.models[manyRelations.collection]; const attributes = { [`${pluralize.singular(manyRelations.collection)}_id`]: { type: 'integer' }, [`${pluralize.singular(definition.globalId.toLowerCase())}_id`]: { type: 'integer' } }; const table = _.get(manyRelations, 'collectionName') || _.map( _.sortBy( [ collection.attributes[ manyRelations.via ], manyRelations ], 'collection' ), table => { return _.snakeCase( pluralize.plural(table.collection) + ' ' + pluralize.plural(table.via) ); } ).join('__'); await handler(table, attributes); } // Remove from attributes (auto handled by bookshlef and not displayed on ctb) if (loadedModel.hasTimestamps) { delete definition.attributes['created_at']; delete definition.attributes['updated_at']; } resolve(); })); } catch (err) { strapi.log.error('Impossible to register the `' + model + '` model.'); strapi.log.error(err); strapi.stop(); } }); if (_.isEmpty(definition.attributes)) { done(); } // Add every relationships to the loaded model for Bookshelf. // Basic attributes don't need this-- only relations. _.forEach(definition.attributes, (details, name) => { const verbose = _.get( utilsModels.getNature(details, name, undefined, model.toLowerCase()), 'verbose' ) || ''; // Build associations key utilsModels.defineAssociations( model.toLowerCase(), definition, details, name ); let globalId; const globalName = details.model || details.collection || ''; // Exclude polymorphic association. if (globalName !== '*') { globalId = details.plugin ? _.get(strapi.plugins,`${details.plugin}.models.${globalName.toLowerCase()}.globalId`): _.get(strapi.models, `${globalName.toLowerCase()}.globalId`); } switch (verbose) { case 'hasOne': { const FK = details.plugin ? _.findKey( strapi.plugins[details.plugin].models[details.model].attributes, details => { if ( details.hasOwnProperty('model') && details.model === model && details.hasOwnProperty('via') && details.via === name ) { return details; } } ): _.findKey( strapi.models[details.model].attributes, details => { if ( details.hasOwnProperty('model') && details.model === model && details.hasOwnProperty('via') && details.via === name ) { return details; } } ); const columnName = details.plugin ? _.get(strapi.plugins, `${details.plugin}.models.${details.model}.attributes.${FK}.columnName`, FK): _.get(strapi.models, `${details.model}.attributes.${FK}.columnName`, FK); loadedModel[name] = function() { return this.hasOne( GLOBALS[globalId], columnName ); }; break; } case 'hasMany': { const columnName = details.plugin ? _.get(strapi.plugins, `${details.plugin}.models.${globalId.toLowerCase()}.attributes.${details.via}.columnName`, details.via): _.get(strapi.models[globalId.toLowerCase()].attributes, `${details.via}.columnName`, details.via); // Set this info to be able to see if this field is a real database's field. details.isVirtual = true; loadedModel[name] = function() { return this.hasMany(GLOBALS[globalId], columnName); }; break; } case 'belongsTo': { loadedModel[name] = function() { return this.belongsTo( GLOBALS[globalId], _.get(details, 'columnName', name) ); }; break; } case 'belongsToMany': { const collection = details.plugin ? strapi.plugins[details.plugin].models[details.collection]: strapi.models[details.collection]; const collectionName = _.get(details, 'collectionName') || _.map( _.sortBy( [ collection.attributes[ details.via ], details ], 'collection' ), table => { return _.snakeCase( pluralize.plural(table.collection) + ' ' + pluralize.plural(table.via) ); } ).join('__'); const relationship = _.clone( collection.attributes[details.via] ); // Force singular foreign key relationship.attribute = pluralize.singular( relationship.collection ); details.attribute = pluralize.singular(details.collection); // Define PK column details.column = utils.getPK(model, strapi.models); relationship.column = utils.getPK( details.collection, strapi.models ); // Sometimes the many-to-many relationships // is on the same keys on the same models (ex: `friends` key in model `User`) if ( details.attribute + '_' + details.column === relationship.attribute + '_' + relationship.column ) { relationship.attribute = pluralize.singular(details.via); } // Set this info to be able to see if this field is a real database's field. details.isVirtual = true; loadedModel[name] = function() { if ( _.isArray(_.get(details, 'withPivot')) && !_.isEmpty(details.withPivot) ) { return this.belongsToMany( GLOBALS[globalId], collectionName, relationship.attribute + '_' + relationship.column, details.attribute + '_' + details.column ).withPivot(details.withPivot); } return this.belongsToMany( GLOBALS[globalId], collectionName, relationship.attribute + '_' + relationship.column, details.attribute + '_' + details.column ); }; break; } case 'morphOne': { const model = details.plugin ? strapi.plugins[details.plugin].models[details.model]: strapi.models[details.model]; const globalId = `${model.collectionName}_morph`; loadedModel[name] = function() { return this .morphOne(GLOBALS[globalId], details.via, `${definition.collectionName}`) .query(qb => { qb.where(_.get(model, `attributes.${details.via}.filter`, 'field'), name); }); } break; } case 'morphMany': { const collection = details.plugin ? strapi.plugins[details.plugin].models[details.collection]: strapi.models[details.collection]; const globalId = `${collection.collectionName}_morph`; loadedModel[name] = function() { return this .morphMany(GLOBALS[globalId], details.via, `${definition.collectionName}`) .query(qb => { qb.where(_.get(collection, `attributes.${details.via}.filter`, 'field'), name); }); } break; } case 'belongsToMorph': case 'belongsToManyMorph': { const association = definition.associations .find(association => association.alias === name); const morphValues = association.related.map(id => { let models = Object.values(strapi.models).filter(model => model.globalId === id); if (models.length === 0) { models = Object.keys(strapi.plugins).reduce((acc, current) => { const models = Object.values(strapi.plugins[current].models).filter(model => model.globalId === id); if (acc.length === 0 && models.length > 0) { acc = models; } return acc; }, []); } if (models.length === 0) { strapi.log.error('Impossible to register the `' + model + '` model.'); strapi.log.error('The collection name cannot be found for the morphTo method.'); strapi.stop(); } return models[0].collectionName; }); // Define new model. const options = { tableName: `${definition.collectionName}_morph`, [definition.collectionName]: function () { return this .belongsTo( GLOBALS[definition.globalId], `${definition.collectionName}_id` ); }, related: function () { return this .morphTo(name, ...association.related.map((id, index) => [GLOBALS[id], morphValues[index]])); } }; GLOBALS[options.tableName] = ORM.Model.extend(options); // Set polymorphic table name to the main model. target[model].morph = GLOBALS[options.tableName]; // Hack Bookshelf to create a many-to-many polymorphic association. // Upload has many Upload_morph that morph to different model. loadedModel[name] = function () { if (verbose === 'belongsToMorph') { return this.hasOne( GLOBALS[options.tableName], `${definition.collectionName}_id` ); } return this.hasMany( GLOBALS[options.tableName], `${definition.collectionName}_id` ); }; break; } default: { break; } } done(); }); });
db.on('open', () => { // Initialize collections _.set(strapi, 'mongoose.collections', {}); const loadedAttributes = _.after(_.size(strapi.models), function () { _.forEach(strapi.models, function (definition, model) { try { let collection = strapi.mongoose.collections[mongooseUtils.toCollectionName(definition.globalName)]; // Initialize lifecycle callbacks. const preLifecycle = { validate: 'beforeCreate', save: 'beforeCreate', remove: 'beforeDestroy', update: 'beforeUpdate', find: 'beforeFetch', save: 'beforeSave' }; _.forEach(preLifecycle, function (fn, key) { if (_.isFunction(strapi.models[model.toLowerCase()][fn])) { collection.schema.pre(key, strapi.models[model.toLowerCase()][fn]); } }); const postLifecycle = { validate: 'afterCreate', save: 'afterCreate', remove: 'afterDestroy', update: 'afterUpdate', find: 'afterFetch', save: 'afterSave' }; _.forEach(postLifecycle, function (fn, key) { if (_.isFunction(strapi.models[model.toLowerCase()][fn])) { collection.schema.post(key, strapi.models[model.toLowerCase()][fn]); } }); // Add virtual key to provide populate and reverse populate _.forEach(_.pickBy(definition.loadedModel, model => { return model.type === 'virtual' }), (value, key) => { collection.schema.virtual(key.replace('_v', ''), { ref: value.ref, localField: '_id', foreignField: value.via, justOne: value.justOne || false }); }); collection.schema.set('toObject', { virtuals: true }); collection.schema.set('toJSON', { virtuals: true }); global[definition.globalName] = mongoose.model(definition.globalName, collection.schema); // Push model to strapi global variables. collection = global[definition.globalName]; // Push attributes to be aware of model schema. collection._attributes = definition.attributes; } catch (err) { strapi.log.error('Impossible to register the `' + model + '` model.'); strapi.log.error(err); strapi.stop(); } }); cb(); }); // Parse every registered model. _.forEach(strapi.models, function (definition, model) { definition.globalName = _.upperFirst(_.camelCase(definition.globalId)); // Make sure the model has a connection. // If not, use the default connection. if (_.isEmpty(definition.connection)) { definition.connection = strapi.config.defaultConnection; } // Make sure this connection exists. if (!_.has(strapi.config.connections, definition.connection)) { strapi.log.error('The connection `' + definition.connection + '` specified in the `' + model + '` model does not exist.'); strapi.stop(); } // Add some informations about ORM & client connection definition.orm = 'mongoose'; definition.client = _.get(strapi.config.connections[definition.connection], 'client'); // Register the final model for Bookshelf. definition.loadedModel = _.cloneDeep(definition.attributes); // Initialize the global variable with the // capitalized model name. global[definition.globalName] = {}; if (_.isEmpty(definition.attributes)) { // Generate empty schema _.set(strapi.mongoose.collections, mongooseUtils.toCollectionName(definition.globalName) + '.schema', mongoose.Schema({})); return loadedAttributes(); } // Call this callback function after we are done parsing // all attributes for relationships-- see below. const done = _.after(_.size(definition.attributes), function () { // Generate schema without virtual populate _.set(strapi.mongoose.collections, mongooseUtils.toCollectionName(definition.globalName) + '.schema', mongoose.Schema(_.omitBy(definition.loadedModel, model => { return model.type === 'virtual'; }))); loadedAttributes(); }); // Add every relationships to the loaded model for Bookshelf. // Basic attributes don't need this-- only relations. _.forEach(definition.attributes, function (details, name) { const verbose = _.get(utilsModels.getNature(details, name), 'verbose') || ''; // Build associations key if (!_.isEmpty(verbose)) { utilsModels.defineAssociations(globalName, definition, details, name); } else { definition.loadedModel[name].type = utils(mongoose).convertType(details.type); } let FK; switch (verbose) { case 'hasOne': definition.loadedModel[name] = { type: mongoose.Schema.Types.ObjectId, ref: _.capitalize(details.model) }; break; case 'hasMany': FK = _.find(definition.associations, { alias : name }); if (FK) { definition.loadedModel[name] = { type: 'virtual', ref: _.capitalize(details.collection), via: FK.via }; } else { definition.loadedModel[name] = [{ type: mongoose.Schema.Types.ObjectId, ref: _.capitalize(details.collection) }]; } break; case 'belongsTo': FK = _.find(definition.associations, { alias : name }); if (FK && FK.nature === 'oneToOne') { definition.loadedModel[name] = { type: 'virtual', ref: _.capitalize(details.model), via: FK.via, justOne: true }; } else { definition.loadedModel[name] = { type: mongoose.Schema.Types.ObjectId, ref: _.capitalize(details.model) }; } break; case 'belongsToMany': FK = _.find(definition.associations, { alias : name }); if (FK && _.isUndefined(details.via)) { definition.loadedModel[name] = { type: 'virtual', ref: _.capitalize(FK.collection), via: utilsModels.getVia(name, details) }; } else { definition.loadedModel[name] = [{ type: mongoose.Schema.Types.ObjectId, ref: _.capitalize(details.collection) }]; } break; default: break; } done(); }); }); });
_.forEach(strapi.models, function (definition, model) { definition.globalName = _.upperFirst(_.camelCase(definition.globalId)); // Make sure the model has a connection. // If not, use the default connection. if (_.isEmpty(definition.connection)) { definition.connection = strapi.config.defaultConnection; } // Make sure this connection exists. if (!_.has(strapi.config.connections, definition.connection)) { strapi.log.error('The connection `' + definition.connection + '` specified in the `' + model + '` model does not exist.'); strapi.stop(); } // Add some informations about ORM & client connection definition.orm = 'mongoose'; definition.client = _.get(strapi.config.connections[definition.connection], 'client'); // Register the final model for Bookshelf. definition.loadedModel = _.cloneDeep(definition.attributes); // Initialize the global variable with the // capitalized model name. global[definition.globalName] = {}; if (_.isEmpty(definition.attributes)) { // Generate empty schema _.set(strapi.mongoose.collections, mongooseUtils.toCollectionName(definition.globalName) + '.schema', mongoose.Schema({})); return loadedAttributes(); } // Call this callback function after we are done parsing // all attributes for relationships-- see below. const done = _.after(_.size(definition.attributes), function () { // Generate schema without virtual populate _.set(strapi.mongoose.collections, mongooseUtils.toCollectionName(definition.globalName) + '.schema', mongoose.Schema(_.omitBy(definition.loadedModel, model => { return model.type === 'virtual'; }))); loadedAttributes(); }); // Add every relationships to the loaded model for Bookshelf. // Basic attributes don't need this-- only relations. _.forEach(definition.attributes, function (details, name) { const verbose = _.get(utilsModels.getNature(details, name), 'verbose') || ''; // Build associations key if (!_.isEmpty(verbose)) { utilsModels.defineAssociations(globalName, definition, details, name); } else { definition.loadedModel[name].type = utils(mongoose).convertType(details.type); } let FK; switch (verbose) { case 'hasOne': definition.loadedModel[name] = { type: mongoose.Schema.Types.ObjectId, ref: _.capitalize(details.model) }; break; case 'hasMany': FK = _.find(definition.associations, { alias : name }); if (FK) { definition.loadedModel[name] = { type: 'virtual', ref: _.capitalize(details.collection), via: FK.via }; } else { definition.loadedModel[name] = [{ type: mongoose.Schema.Types.ObjectId, ref: _.capitalize(details.collection) }]; } break; case 'belongsTo': FK = _.find(definition.associations, { alias : name }); if (FK && FK.nature === 'oneToOne') { definition.loadedModel[name] = { type: 'virtual', ref: _.capitalize(details.model), via: FK.via, justOne: true }; } else { definition.loadedModel[name] = { type: mongoose.Schema.Types.ObjectId, ref: _.capitalize(details.model) }; } break; case 'belongsToMany': FK = _.find(definition.associations, { alias : name }); if (FK && _.isUndefined(details.via)) { definition.loadedModel[name] = { type: 'virtual', ref: _.capitalize(FK.collection), via: utilsModels.getVia(name, details) }; } else { definition.loadedModel[name] = [{ type: mongoose.Schema.Types.ObjectId, ref: _.capitalize(details.collection) }]; } break; default: break; } done(); }); });