From fc77ef67bddfc9de9a8dc7eba6466c266412d804 Mon Sep 17 00:00:00 2001 From: sundaycrafts Date: Sat, 25 Aug 2018 18:40:57 +0900 Subject: [PATCH 01/11] fix connectivity.js uri form --- packages/strapi-hook-mongoose/lib/utils/connectivity.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/strapi-hook-mongoose/lib/utils/connectivity.js b/packages/strapi-hook-mongoose/lib/utils/connectivity.js index e0e5b50f53..10b48d473f 100644 --- a/packages/strapi-hook-mongoose/lib/utils/connectivity.js +++ b/packages/strapi-hook-mongoose/lib/utils/connectivity.js @@ -28,7 +28,7 @@ module.exports = (scope, success, error) => { connectOptions.useNewUrlParser = true; connectOptions.dbName = scope.database.settings.database; - Mongoose.connect(`mongodb${srv ? "+srv" : ""}://${scope.database.settings.host}:${!srv ? ":" + scope.database.settings.port : ""}/`, connectOptions, function (err) { + Mongoose.connect(`mongodb${srv ? "+srv" : ""}://${scope.database.settings.host}${!srv ? ":" + scope.database.settings.port : ""}/`, connectOptions, function (err) { if (err) { console.log('⚠️ Database connection has failed! Make sure your database is running.'); return error(); From 29989db029c411705c866684b7db32a20ad8e8b0 Mon Sep 17 00:00:00 2001 From: sundaycrafts Date: Sun, 26 Aug 2018 03:26:57 +0900 Subject: [PATCH 02/11] WIP: support srv uri --- packages/strapi-hook-mongoose/lib/index.js | 670 ++++++++++----------- 1 file changed, 333 insertions(+), 337 deletions(-) diff --git a/packages/strapi-hook-mongoose/lib/index.js b/packages/strapi-hook-mongoose/lib/index.js index a0c3e5e9d4..271e0c6bf7 100755 --- a/packages/strapi-hook-mongoose/lib/index.js +++ b/packages/strapi-hook-mongoose/lib/index.js @@ -49,11 +49,12 @@ module.exports = function (strapi) { */ initialize: cb => { - _.forEach(_.pickBy(strapi.config.connections, {connector: 'strapi-hook-mongoose'}), (connection, connectionName) => { + _.forEach(_.pickBy(strapi.config.connections, {connector: 'strapi-hook-mongoose'}), async (connection, connectionName) => { const instance = new Mongoose(); - const { uri, host, port, username, password, database } = _.defaults(connection.settings, strapi.config.hook.settings.mongoose); + const { uri, host, port, username, password, database, srv } = _.defaults(connection.settings, strapi.config.hook.settings.mongoose); const uriOptions = uri ? url.parse(uri, true).query : {}; const { authenticationDatabase, ssl, debug } = _.defaults(connection.options, uriOptions, strapi.config.hook.settings.mongoose); + const isSrv = srv === 'true'; // Connect to mongo database const connectOptions = {}; @@ -73,391 +74,386 @@ module.exports = function (strapi) { connectOptions.ssl = ssl === true || ssl === 'true'; connectOptions.useNewUrlParser = true; + connectOptions.dbName = database; options.debug = debug === true || debug === 'true'; - instance.connect(uri || `mongodb://${host}:${port}/${database}`, connectOptions); - - for (let key in options) { - instance.set(key, options[key]); - } - - // Handle error - instance.connection.on('error', error => { + try { + await instance.connect(uri || `mongodb${isSrv ? "+srv" : ""}://${host}${!isSrv ? ":" + port : ""}/`, connectOptions) + } catch (err) { if (error.message.indexOf(`:${port}`)) { return cb('Make sure your MongoDB database is running...'); } cb(error); - }); + } - // Handle success - instance.connection.on('open', () => { - const mountModels = (models, target, plugin = false) => { - if (!target) return; + Object.keys(options, key => instance.set(key, options[key])) - const loadedAttributes = _.after(_.size(models), () => { - _.forEach(models, (definition, model) => { - try { - let collection = strapi.config.hook.settings.mongoose.collections[mongooseUtils.toCollectionName(definition.globalName)]; + const mountModels = (models, target, plugin = false) => { + if (!target) return; - // 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)); - }); - } - }); - - // 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('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. + const loadedAttributes = _.after(_.size(models), () => { _.forEach(models, (definition, model) => { - definition.globalName = _.upperFirst(_.camelCase(definition.globalId)); + try { + let collection = strapi.config.hook.settings.mongoose.collections[mongooseUtils.toCollectionName(definition.globalName)]; - // Make sure the model has a connection. - // If not, use the default connection. - if (_.isEmpty(definition.connection)) { - definition.connection = strapi.config.currentEnvironment.database.defaultConnection; - } + // Set the default values to model settings. + _.defaults(definition, { + primaryKey: '_id' + }); - // 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.'); + // 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)); + }); + } + }); + + // 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('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(); } + }); + }); - // Add some informations about ORM & client connection - definition.orm = 'mongoose'; - definition.client = _.get(strapi.config.connections[definition.connection], 'client'); - definition.associations = []; + // Parse every authenticated model. + _.forEach(models, (definition, model) => { + definition.globalName = _.upperFirst(_.camelCase(definition.globalId)); - // Register the final model for Mongoose. - definition.loadedModel = _.cloneDeep(definition.attributes); + // Make sure the model has a connection. + // If not, use the default connection. + if (_.isEmpty(definition.connection)) { + definition.connection = strapi.config.currentEnvironment.database.defaultConnection; + } - // Initialize the global variable with the - // capitalized model name. - if (!plugin) { - global[definition.globalName] = {}; + // 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); } - if (_.isEmpty(definition.attributes)) { - // Generate empty schema - _.set(strapi.config.hook.settings.mongoose, 'collections.' + mongooseUtils.toCollectionName(definition.globalName) + '.schema', new instance.Schema({})); + switch (verbose) { + case 'hasOne': { + const ref = details.plugin ? strapi.plugins[details.plugin].models[details.model].globalId : strapi.models[details.model].globalId; - 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); + 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; - 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; - + if (FK) { definition.loadedModel[name] = { type: 'virtual', ref, - via: `${FK.via}.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; - break; + } else { + definition.loadedModel[name] = { + type: instance.Schema.Types.ObjectId, + ref + }; } - 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; + 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}.ref` + via: FK.via }; // 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': { + } else { definition.loadedModel[name] = [{ - kind: String, - [details.filter]: String, - ref: { - type: instance.Schema.Types.ObjectId, - refPath: `${name}.kind` - } + type: instance.Schema.Types.ObjectId, + ref }]; - break; } - default: - break; + 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; - done(); - }); + 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(); }); - }; - - // Mount `./api` models. - mountModels(_.pickBy(strapi.models, { connection: connectionName }), strapi.models); - - // Mount `./plugins` models. - _.forEach(strapi.plugins, (plugin, name) => { - mountModels(_.pickBy(strapi.plugins[name].models, { connection: connectionName }), plugin.models, name); }); + }; - cb(); + // Mount `./api` models. + mountModels(_.pickBy(strapi.models, { connection: connectionName }), strapi.models); + + // Mount `./plugins` models. + _.forEach(strapi.plugins, (plugin, name) => { + mountModels(_.pickBy(strapi.plugins[name].models, { connection: connectionName }), plugin.models, name); }); + + cb(); }); }, From f7247112f68cc3a9afd6255b37236526d701b350 Mon Sep 17 00:00:00 2001 From: sundaycrafts Date: Tue, 28 Aug 2018 12:50:35 +0900 Subject: [PATCH 03/11] fix error handling --- packages/strapi-hook-mongoose/lib/index.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/strapi-hook-mongoose/lib/index.js b/packages/strapi-hook-mongoose/lib/index.js index 271e0c6bf7..bd0dad8de7 100755 --- a/packages/strapi-hook-mongoose/lib/index.js +++ b/packages/strapi-hook-mongoose/lib/index.js @@ -79,13 +79,16 @@ module.exports = function (strapi) { options.debug = debug === true || debug === 'true'; try { - await instance.connect(uri || `mongodb${isSrv ? "+srv" : ""}://${host}${!isSrv ? ":" + port : ""}/`, connectOptions) + await instance.connect( + uri || `mongodb${isSrv ? '+srv' : ''}://${host}${!isSrv ? ':' + port : ''}/`, + connectOptions + ); } catch (err) { - if (error.message.indexOf(`:${port}`)) { + if (err.message.indexOf(`:${port}`)) { return cb('Make sure your MongoDB database is running...'); } - cb(error); + cb(err); } Object.keys(options, key => instance.set(key, options[key])) From 75a83c0f212e7cb40d8ec9a0f1d3a8834ee93275 Mon Sep 17 00:00:00 2001 From: sundaycrafts Date: Wed, 29 Aug 2018 01:20:56 +0900 Subject: [PATCH 04/11] strapi supports srv uri --- packages/strapi-hook-mongoose/lib/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/strapi-hook-mongoose/lib/index.js b/packages/strapi-hook-mongoose/lib/index.js index bd0dad8de7..8f44dc0c27 100755 --- a/packages/strapi-hook-mongoose/lib/index.js +++ b/packages/strapi-hook-mongoose/lib/index.js @@ -80,7 +80,7 @@ module.exports = function (strapi) { try { await instance.connect( - uri || `mongodb${isSrv ? '+srv' : ''}://${host}${!isSrv ? ':' + port : ''}/`, + uri || `mongodb${isSrv ? '+srv' : ''}://${username}:${password}@${host}${!isSrv ? ':' + port : ''}/`, connectOptions ); } catch (err) { From ac38aaa41059da1b7adc7454413160ec6d96c402 Mon Sep 17 00:00:00 2001 From: sundaycrafts Date: Wed, 29 Aug 2018 01:33:58 +0900 Subject: [PATCH 05/11] beautify mongoose hook lib/index.js --- packages/strapi-hook-mongoose/lib/index.js | 981 +++++++++++---------- 1 file changed, 534 insertions(+), 447 deletions(-) diff --git a/packages/strapi-hook-mongoose/lib/index.js b/packages/strapi-hook-mongoose/lib/index.js index 8f44dc0c27..8b53415895 100755 --- a/packages/strapi-hook-mongoose/lib/index.js +++ b/packages/strapi-hook-mongoose/lib/index.js @@ -12,8 +12,7 @@ const Mongoose = mongoose.Mongoose; const mongooseUtils = require('mongoose/lib/utils'); // Strapi helpers for models. -const { models: utilsModels } = require('strapi-utils'); - +const { models: utilsModels } = require('strapi-utils'); // Local helpers. const utils = require('./utils/'); @@ -27,502 +26,590 @@ const relations = require('./relations'); /* eslint-disable no-case-declarations */ /* eslint-disable no-const-assign */ /* eslint-disable no-unused-vars */ -module.exports = function (strapi) { - const hook = _.merge({ +module.exports = function(strapi) { + const hook = _.merge( + { + /** + * Default options + */ - /** - * Default options - */ + defaults: { + defaultConnection: 'default', + host: 'localhost', + port: 27017, + database: 'strapi', + authenticationDatabase: '', + ssl: false, + debug: false, + }, - defaults: { - defaultConnection: 'default', - host: 'localhost', - port: 27017, - database: 'strapi', - authenticationDatabase: '', - ssl: false, - debug: false - }, + /** + * Initialize the hook + */ - /** - * Initialize the hook - */ + initialize: cb => { + _.forEach( + _.pickBy(strapi.config.connections, { connector: 'strapi-hook-mongoose' }), + async (connection, connectionName) => { + const instance = new Mongoose(); + const { uri, host, port, username, password, database, srv } = _.defaults( + connection.settings, + strapi.config.hook.settings.mongoose + ); + const uriOptions = uri ? url.parse(uri, true).query : {}; + const { authenticationDatabase, ssl, debug } = _.defaults( + connection.options, + uriOptions, + strapi.config.hook.settings.mongoose + ); + const isSrv = srv === 'true'; - initialize: cb => { - _.forEach(_.pickBy(strapi.config.connections, {connector: 'strapi-hook-mongoose'}), async (connection, connectionName) => { - const instance = new Mongoose(); - const { uri, host, port, username, password, database, srv } = _.defaults(connection.settings, strapi.config.hook.settings.mongoose); - const uriOptions = uri ? url.parse(uri, true).query : {}; - const { authenticationDatabase, ssl, debug } = _.defaults(connection.options, uriOptions, strapi.config.hook.settings.mongoose); - const isSrv = srv === 'true'; + // Connect to mongo database + const connectOptions = {}; + const options = {}; - // Connect to mongo database - const connectOptions = {}; - const options = {}; + if (!_.isEmpty(username)) { + connectOptions.user = username; - if (!_.isEmpty(username)) { - connectOptions.user = username; + if (!_.isEmpty(password)) { + connectOptions.pass = password; + } + } - if (!_.isEmpty(password)) { - connectOptions.pass = password; - } - } + if (!_.isEmpty(authenticationDatabase)) { + connectOptions.authSource = authenticationDatabase; + } - if (!_.isEmpty(authenticationDatabase)) { - connectOptions.authSource = authenticationDatabase; - } + connectOptions.ssl = ssl === true || ssl === 'true'; + connectOptions.useNewUrlParser = true; + connectOptions.dbName = database; - connectOptions.ssl = ssl === true || ssl === 'true'; - connectOptions.useNewUrlParser = true; - connectOptions.dbName = database; + options.debug = debug === true || debug === 'true'; - options.debug = debug === true || debug === 'true'; + try { + await instance.connect( + uri || + `mongodb${isSrv ? '+srv' : ''}://${username}:${password}@${host}${ + !isSrv ? ':' + port : '' + }/`, + connectOptions + ); + } catch (err) { + if (err.message.indexOf(`:${port}`)) { + return cb('Make sure your MongoDB database is running...'); + } - try { - await instance.connect( - uri || `mongodb${isSrv ? '+srv' : ''}://${username}:${password}@${host}${!isSrv ? ':' + port : ''}/`, - connectOptions - ); - } catch (err) { - if (err.message.indexOf(`:${port}`)) { - return cb('Make sure your MongoDB database is running...'); - } + cb(err); + } - cb(err); - } + Object.keys(options, key => instance.set(key, options[key])); - Object.keys(options, key => instance.set(key, options[key])) + const mountModels = (models, target, plugin = false) => { + if (!target) return; - 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) + ]; - 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', + }); - // 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', + }; - // 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); + 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 - }; + 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(); + // 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)); }); - }); - }); - } - - _.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)); - }); - } - }); - - // 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('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: - - } } }); + + 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)); + }); + } + }); + + // 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('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(); } - }; + }); + }); - // Instantiate model. - const Model = instance.model(definition.globalId, collection.schema, definition.collectionName); + // 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] = Model; + global[definition.globalName] = {}; } - // Expose ORM functions through the `target` object. - target[model] = _.assign(Model, target[model]); + if (_.isEmpty(definition.attributes)) { + // Generate empty schema + _.set( + strapi.config.hook.settings.mongoose, + 'collections.' + mongooseUtils.toCollectionName(definition.globalName) + '.schema', + new instance.Schema({}) + ); - // 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; + return loadedAttributes(); } - 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 - }; + // 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 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; + _.set( + strapi.config.hook.settings.mongoose, + 'collections.' + mongooseUtils.toCollectionName(definition.globalName) + '.schema', + schema + ); - 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 - }; + loadedAttributes(); + }); - // 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 - }; + // 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); } - 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; + switch (verbose) { + case 'hasOne': { + const ref = details.plugin + ? strapi.plugins[details.plugin].models[details.model].globalId + : strapi.models[details.model].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` + definition.loadedModel[name] = { + type: instance.Schema.Types.ObjectId, + ref, + }; + break; } - }; - break; - } - case 'belongsToManyMorph': { - definition.loadedModel[name] = [{ - kind: String, - [details.filter]: String, - ref: { - type: instance.Schema.Types.ObjectId, - refPath: `${name}.kind` + 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; } - }]; - break; - } - default: - 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; - done(); + 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(); + }); + }); + }; + + // Mount `./api` models. + mountModels(_.pickBy(strapi.models, { connection: connectionName }), strapi.models); + + // Mount `./plugins` models. + _.forEach(strapi.plugins, (plugin, name) => { + mountModels( + _.pickBy(strapi.plugins[name].models, { connection: connectionName }), + plugin.models, + name + ); }); - }); - }; - // Mount `./api` models. - mountModels(_.pickBy(strapi.models, { connection: connectionName }), strapi.models); + cb(); + } + ); + }, - // Mount `./plugins` models. - _.forEach(strapi.plugins, (plugin, name) => { - mountModels(_.pickBy(strapi.plugins[name].models, { connection: connectionName }), plugin.models, name); - }); + getQueryParams: (value, type, key) => { + const result = {}; - cb(); - }); + switch (type) { + case '=': + result.key = `where.${key}`; + result.value = value; + break; + case '_ne': + result.key = `where.${key}.$ne`; + result.value = value; + break; + case '_lt': + result.key = `where.${key}.$lt`; + result.value = value; + break; + case '_gt': + result.key = `where.${key}.$gt`; + result.value = value; + break; + case '_lte': + result.key = `where.${key}.$lte`; + result.value = value; + break; + case '_gte': + result.key = `where.${key}.$gte`; + result.value = value; + break; + case '_sort': + result.key = `sort`; + result.value = _.toLower(value) === 'desc' ? '-' : ''; + result.value += key; + break; + case '_start': + result.key = `start`; + result.value = parseFloat(value); + break; + case '_limit': + result.key = `limit`; + result.value = parseFloat(value); + break; + case '_contains': + result.key = `where.${key}`; + result.value = { + $regex: value, + $options: 'i', + }; + break; + case '_containss': + result.key = `where.${key}.$regex`; + result.value = value; + break; + case '_in': + result.key = `where.${key}.$in`; + result.value = value; + break; + default: + result = undefined; + } + + return result; + }, }, - - getQueryParams: (value, type, key) => { - const result = {}; - - switch (type) { - case '=': - result.key = `where.${key}`; - result.value = value; - break; - case '_ne': - result.key = `where.${key}.$ne`; - result.value = value; - break; - case '_lt': - result.key = `where.${key}.$lt`; - result.value = value; - break; - case '_gt': - result.key = `where.${key}.$gt`; - result.value = value; - break; - case '_lte': - result.key = `where.${key}.$lte`; - result.value = value; - break; - case '_gte': - result.key = `where.${key}.$gte`; - result.value = value; - break; - case '_sort': - result.key = `sort`; - result.value = (_.toLower(value) === 'desc') ? '-' : ''; - result.value += key; - break; - case '_start': - result.key = `start`; - result.value = parseFloat(value); - break; - case '_limit': - result.key = `limit`; - result.value = parseFloat(value); - break; - case '_contains': - result.key = `where.${key}`; - result.value = { - $regex: value, - $options: 'i', - }; - break; - case '_containss': - result.key = `where.${key}.$regex`; - result.value = value; - break; - case '_in': - result.key = `where.${key}.$in`; - result.value = value; - break; - default: - result = undefined; - } - - return result; - } - }, relations); + relations + ); return hook; }; From a1d08517b58efb9123c71a8fe4df2c6389be6498 Mon Sep 17 00:00:00 2001 From: sundaycrafts Date: Wed, 29 Aug 2018 23:27:49 +0900 Subject: [PATCH 06/11] revert changes for beautify code --- packages/strapi-hook-mongoose/lib/index.js | 1062 +++++++++----------- 1 file changed, 488 insertions(+), 574 deletions(-) diff --git a/packages/strapi-hook-mongoose/lib/index.js b/packages/strapi-hook-mongoose/lib/index.js index 8b53415895..a0c3e5e9d4 100755 --- a/packages/strapi-hook-mongoose/lib/index.js +++ b/packages/strapi-hook-mongoose/lib/index.js @@ -12,7 +12,8 @@ const Mongoose = mongoose.Mongoose; const mongooseUtils = require('mongoose/lib/utils'); // Strapi helpers for models. -const { models: utilsModels } = require('strapi-utils'); +const { models: utilsModels } = require('strapi-utils'); + // Local helpers. const utils = require('./utils/'); @@ -26,590 +27,503 @@ const relations = require('./relations'); /* eslint-disable no-case-declarations */ /* eslint-disable no-const-assign */ /* eslint-disable no-unused-vars */ -module.exports = function(strapi) { - const hook = _.merge( - { - /** - * Default options - */ +module.exports = function (strapi) { + const hook = _.merge({ - defaults: { - defaultConnection: 'default', - host: 'localhost', - port: 27017, - database: 'strapi', - authenticationDatabase: '', - ssl: false, - debug: false, - }, + /** + * Default options + */ - /** - * Initialize the hook - */ + defaults: { + defaultConnection: 'default', + host: 'localhost', + port: 27017, + database: 'strapi', + authenticationDatabase: '', + ssl: false, + debug: false + }, - initialize: cb => { - _.forEach( - _.pickBy(strapi.config.connections, { connector: 'strapi-hook-mongoose' }), - async (connection, connectionName) => { - const instance = new Mongoose(); - const { uri, host, port, username, password, database, srv } = _.defaults( - connection.settings, - strapi.config.hook.settings.mongoose - ); - const uriOptions = uri ? url.parse(uri, true).query : {}; - const { authenticationDatabase, ssl, debug } = _.defaults( - connection.options, - uriOptions, - strapi.config.hook.settings.mongoose - ); - const isSrv = srv === 'true'; + /** + * Initialize the hook + */ - // Connect to mongo database - const connectOptions = {}; - const options = {}; + initialize: cb => { + _.forEach(_.pickBy(strapi.config.connections, {connector: 'strapi-hook-mongoose'}), (connection, connectionName) => { + const instance = new Mongoose(); + const { uri, host, port, username, password, database } = _.defaults(connection.settings, strapi.config.hook.settings.mongoose); + const uriOptions = uri ? url.parse(uri, true).query : {}; + const { authenticationDatabase, ssl, debug } = _.defaults(connection.options, uriOptions, strapi.config.hook.settings.mongoose); - if (!_.isEmpty(username)) { - connectOptions.user = username; + // Connect to mongo database + const connectOptions = {}; + const options = {}; - if (!_.isEmpty(password)) { - connectOptions.pass = password; - } - } + if (!_.isEmpty(username)) { + connectOptions.user = username; - if (!_.isEmpty(authenticationDatabase)) { - connectOptions.authSource = authenticationDatabase; - } - - connectOptions.ssl = ssl === true || ssl === 'true'; - connectOptions.useNewUrlParser = true; - connectOptions.dbName = database; - - options.debug = debug === true || debug === 'true'; - - try { - await instance.connect( - uri || - `mongodb${isSrv ? '+srv' : ''}://${username}:${password}@${host}${ - !isSrv ? ':' + port : '' - }/`, - connectOptions - ); - } catch (err) { - if (err.message.indexOf(`:${port}`)) { - return cb('Make sure your MongoDB database is running...'); - } - - cb(err); - } - - Object.keys(options, key => instance.set(key, options[key])); - - 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)); - }); - } - }); - - // 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('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(); - }); - }); - }; - - // Mount `./api` models. - mountModels(_.pickBy(strapi.models, { connection: connectionName }), strapi.models); - - // Mount `./plugins` models. - _.forEach(strapi.plugins, (plugin, name) => { - mountModels( - _.pickBy(strapi.plugins[name].models, { connection: connectionName }), - plugin.models, - name - ); - }); - - cb(); + if (!_.isEmpty(password)) { + connectOptions.pass = password; } - ); - }, - - getQueryParams: (value, type, key) => { - const result = {}; - - switch (type) { - case '=': - result.key = `where.${key}`; - result.value = value; - break; - case '_ne': - result.key = `where.${key}.$ne`; - result.value = value; - break; - case '_lt': - result.key = `where.${key}.$lt`; - result.value = value; - break; - case '_gt': - result.key = `where.${key}.$gt`; - result.value = value; - break; - case '_lte': - result.key = `where.${key}.$lte`; - result.value = value; - break; - case '_gte': - result.key = `where.${key}.$gte`; - result.value = value; - break; - case '_sort': - result.key = `sort`; - result.value = _.toLower(value) === 'desc' ? '-' : ''; - result.value += key; - break; - case '_start': - result.key = `start`; - result.value = parseFloat(value); - break; - case '_limit': - result.key = `limit`; - result.value = parseFloat(value); - break; - case '_contains': - result.key = `where.${key}`; - result.value = { - $regex: value, - $options: 'i', - }; - break; - case '_containss': - result.key = `where.${key}.$regex`; - result.value = value; - break; - case '_in': - result.key = `where.${key}.$in`; - result.value = value; - break; - default: - result = undefined; } - return result; - }, + if (!_.isEmpty(authenticationDatabase)) { + connectOptions.authSource = authenticationDatabase; + } + + connectOptions.ssl = ssl === true || ssl === 'true'; + connectOptions.useNewUrlParser = true; + + options.debug = debug === true || debug === 'true'; + + instance.connect(uri || `mongodb://${host}:${port}/${database}`, connectOptions); + + for (let key in options) { + instance.set(key, options[key]); + } + + // Handle error + instance.connection.on('error', error => { + if (error.message.indexOf(`:${port}`)) { + return cb('Make sure your MongoDB database is running...'); + } + + cb(error); + }); + + // Handle success + instance.connection.on('open', () => { + 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)); + }); + } + }); + + // 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('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(); + }); + }); + }; + + // Mount `./api` models. + mountModels(_.pickBy(strapi.models, { connection: connectionName }), strapi.models); + + // Mount `./plugins` models. + _.forEach(strapi.plugins, (plugin, name) => { + mountModels(_.pickBy(strapi.plugins[name].models, { connection: connectionName }), plugin.models, name); + }); + + cb(); + }); + }); }, - relations - ); + + getQueryParams: (value, type, key) => { + const result = {}; + + switch (type) { + case '=': + result.key = `where.${key}`; + result.value = value; + break; + case '_ne': + result.key = `where.${key}.$ne`; + result.value = value; + break; + case '_lt': + result.key = `where.${key}.$lt`; + result.value = value; + break; + case '_gt': + result.key = `where.${key}.$gt`; + result.value = value; + break; + case '_lte': + result.key = `where.${key}.$lte`; + result.value = value; + break; + case '_gte': + result.key = `where.${key}.$gte`; + result.value = value; + break; + case '_sort': + result.key = `sort`; + result.value = (_.toLower(value) === 'desc') ? '-' : ''; + result.value += key; + break; + case '_start': + result.key = `start`; + result.value = parseFloat(value); + break; + case '_limit': + result.key = `limit`; + result.value = parseFloat(value); + break; + case '_contains': + result.key = `where.${key}`; + result.value = { + $regex: value, + $options: 'i', + }; + break; + case '_containss': + result.key = `where.${key}.$regex`; + result.value = value; + break; + case '_in': + result.key = `where.${key}.$in`; + result.value = value; + break; + default: + result = undefined; + } + + return result; + } + }, relations); return hook; }; From cdaf2df5b9c81c1391462d1976ffbe1368a2268e Mon Sep 17 00:00:00 2001 From: sundaycrafts Date: Wed, 29 Aug 2018 23:50:19 +0900 Subject: [PATCH 07/11] strapi supports srv --- packages/strapi-hook-mongoose/lib/index.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/strapi-hook-mongoose/lib/index.js b/packages/strapi-hook-mongoose/lib/index.js index a0c3e5e9d4..4c5e8a783f 100755 --- a/packages/strapi-hook-mongoose/lib/index.js +++ b/packages/strapi-hook-mongoose/lib/index.js @@ -51,9 +51,10 @@ module.exports = function (strapi) { initialize: cb => { _.forEach(_.pickBy(strapi.config.connections, {connector: 'strapi-hook-mongoose'}), (connection, connectionName) => { const instance = new Mongoose(); - const { uri, host, port, username, password, database } = _.defaults(connection.settings, strapi.config.hook.settings.mongoose); + const { uri, host, port, username, password, database, srv } = _.defaults(connection.settings, strapi.config.hook.settings.mongoose); const uriOptions = uri ? url.parse(uri, true).query : {}; const { authenticationDatabase, ssl, debug } = _.defaults(connection.options, uriOptions, strapi.config.hook.settings.mongoose); + const isSrv = srv === 'true'; // Connect to mongo database const connectOptions = {}; @@ -73,10 +74,14 @@ module.exports = function (strapi) { connectOptions.ssl = ssl === true || ssl === 'true'; connectOptions.useNewUrlParser = true; + connectOptions.dbName = database; options.debug = debug === true || debug === 'true'; - instance.connect(uri || `mongodb://${host}:${port}/${database}`, connectOptions); + instance.connect(uri || + `mongodb${isSrv ? '+srv' : ''}://${username}:${password}@${host}${ !isSrv ? ':' + port : '' }/`, + connectOptions + ); for (let key in options) { instance.set(key, options[key]); From b9a9e7a829655bfee5049bd436351f1fa21d99a7 Mon Sep 17 00:00:00 2001 From: sundaycrafts Date: Fri, 31 Aug 2018 00:47:45 +0900 Subject: [PATCH 08/11] tweak to consistent with other code --- packages/strapi-generate-new/lib/before.js | 2 +- packages/strapi-hook-mongoose/lib/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/strapi-generate-new/lib/before.js b/packages/strapi-generate-new/lib/before.js index 00be647da5..e6e8e6e45a 100755 --- a/packages/strapi-generate-new/lib/before.js +++ b/packages/strapi-generate-new/lib/before.js @@ -203,7 +203,7 @@ module.exports = (scope, cb) => { } scope.database.settings.host = answers.host; - scope.database.settings.srv = answers.srv; + scope.database.settings.srv = _.toString(answers.srv) === 'true'; scope.database.settings.port = answers.port; scope.database.settings.database = answers.database; scope.database.settings.username = answers.username; diff --git a/packages/strapi-hook-mongoose/lib/index.js b/packages/strapi-hook-mongoose/lib/index.js index 4c5e8a783f..b9b26a0f55 100755 --- a/packages/strapi-hook-mongoose/lib/index.js +++ b/packages/strapi-hook-mongoose/lib/index.js @@ -54,7 +54,7 @@ module.exports = function (strapi) { const { uri, host, port, username, password, database, srv } = _.defaults(connection.settings, strapi.config.hook.settings.mongoose); const uriOptions = uri ? url.parse(uri, true).query : {}; const { authenticationDatabase, ssl, debug } = _.defaults(connection.options, uriOptions, strapi.config.hook.settings.mongoose); - const isSrv = srv === 'true'; + const isSrv = srv === true || srv === 'true'; // Connect to mongo database const connectOptions = {}; From 082f4a49fe140d6e4fc7db62334746fa6d09ab5b Mon Sep 17 00:00:00 2001 From: sundaycrafts Date: Fri, 31 Aug 2018 10:28:12 +0900 Subject: [PATCH 09/11] add comment --- packages/strapi-hook-mongoose/lib/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/strapi-hook-mongoose/lib/index.js b/packages/strapi-hook-mongoose/lib/index.js index b9b26a0f55..2ad6a41574 100755 --- a/packages/strapi-hook-mongoose/lib/index.js +++ b/packages/strapi-hook-mongoose/lib/index.js @@ -78,6 +78,8 @@ module.exports = function (strapi) { options.debug = debug === true || debug === 'true'; + /* FIXME: for now, mongoose doesn't support srv auth except the way including user/pass in URI. + * https://github.com/Automattic/mongoose/issues/6881 */ instance.connect(uri || `mongodb${isSrv ? '+srv' : ''}://${username}:${password}@${host}${ !isSrv ? ':' + port : '' }/`, connectOptions From 71beb1bebac0e26a17e042bbc279a2240e87147c Mon Sep 17 00:00:00 2001 From: sundaycrafts Date: Fri, 31 Aug 2018 19:07:15 +0900 Subject: [PATCH 10/11] fix the message when generating new app --- packages/strapi-generate-new/lib/before.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/strapi-generate-new/lib/before.js b/packages/strapi-generate-new/lib/before.js index e6e8e6e45a..e8223bc124 100755 --- a/packages/strapi-generate-new/lib/before.js +++ b/packages/strapi-generate-new/lib/before.js @@ -152,7 +152,7 @@ module.exports = (scope, cb) => { when: !hasDatabaseConfig, type: 'input', name: 'port', - message: 'Port (It will be ignored if you enable +srv):', + message: `Port${scope.client.database === 'mongo' ? ' (It will be ignored if you enable +srv)' : ''}:`, default: (answers) => { // eslint-disable-line no-unused-vars if (_.get(scope.database, 'port')) { return scope.database.port; From 3ee7bbacf31023a77c2c7338f6108ad48eea8c98 Mon Sep 17 00:00:00 2001 From: Kati Frantz Date: Sun, 2 Sep 2018 19:38:15 +0100 Subject: [PATCH 11/11] add validation for title enumeration field --- .../admin/src/containers/Form/forms.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/forms.json b/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/forms.json index af00853d4e..38d1808991 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/forms.json +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/forms.json @@ -926,7 +926,10 @@ }, "name": "name", "type": "string", - "value": "" + "value": "", + "validations": { + "required": true + } }, { "label": {