From fc77ef67bddfc9de9a8dc7eba6466c266412d804 Mon Sep 17 00:00:00 2001 From: sundaycrafts Date: Sat, 25 Aug 2018 18:40:57 +0900 Subject: [PATCH 01/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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 5a666d80df33724d0f947c2b34d7b657cfa3e790 Mon Sep 17 00:00:00 2001 From: Vladislav Arsenev Date: Tue, 28 Aug 2018 15:12:46 +0300 Subject: [PATCH 08/22] fix(files-required-validation): fix required prop save. add error display for component. fix validation file repeatedly --- .../lib/src/components/InputFile/index.js | 84 ++++++++++++------- .../lib/src/components/InputFile/styles.scss | 4 + .../components/InputFileWithErrors/index.js | 40 ++++++++- .../services/ContentTypeBuilder.js | 6 +- 4 files changed, 99 insertions(+), 35 deletions(-) diff --git a/packages/strapi-helper-plugin/lib/src/components/InputFile/index.js b/packages/strapi-helper-plugin/lib/src/components/InputFile/index.js index a65ba6f048..700524be89 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputFile/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/InputFile/index.js @@ -8,7 +8,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; -import { cloneDeep } from 'lodash'; +import { cloneDeep, isArray, isObject } from 'lodash'; +import cn from 'classnames'; import ImgPreview from 'components/ImgPreview'; import InputFileDetails from 'components/InputFileDetails'; @@ -71,11 +72,12 @@ class InputFile extends React.Component { if (this.props.multiple) { value.splice(this.state.position, 1); } + // Update the parent's props const target = { name: this.props.name, type: 'file', - value, + value: Object.keys(value).length === 0 ? '' : value, }; this.props.onChange({ target }); @@ -94,6 +96,19 @@ class InputFile extends React.Component { this.setState({ position: newPosition }); } + isVisibleDetails = () => { + const {value} = this.props; + + if (!value || + (isArray(value) && value.length === 0) || + (isObject(value) && Object.keys(value).length === 0) + ) { + return false; + } + + return true; + } + render() { const { multiple, @@ -104,39 +119,43 @@ class InputFile extends React.Component { return (
- - +
+ {this.isVisibleDetails() && ( + + )} ); } @@ -146,9 +165,12 @@ InputFile.defaultProps = { multiple: false, setLabel: () => {}, value: [], + error: false, + }; InputFile.propTypes = { + error: PropTypes.bool, multiple: PropTypes.bool, name: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, diff --git a/packages/strapi-helper-plugin/lib/src/components/InputFile/styles.scss b/packages/strapi-helper-plugin/lib/src/components/InputFile/styles.scss index c6c81e3692..26799e7784 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputFile/styles.scss +++ b/packages/strapi-helper-plugin/lib/src/components/InputFile/styles.scss @@ -32,3 +32,7 @@ .copy { cursor: copy !important; } + +.inputFileControlForm { + padding: 0; +} \ No newline at end of file diff --git a/packages/strapi-helper-plugin/lib/src/components/InputFileWithErrors/index.js b/packages/strapi-helper-plugin/lib/src/components/InputFileWithErrors/index.js index 6ca7ac1ff8..f9a8afed46 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputFileWithErrors/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/InputFileWithErrors/index.js @@ -14,22 +14,39 @@ import Label from 'components/Label'; import InputDescription from 'components/InputDescription'; import InputFile from 'components/InputFile'; import InputSpacer from 'components/InputSpacer'; +import InputErrors from 'components/InputErrors'; +// Styles import styles from './styles.scss'; class InputFileWithErrors extends React.Component { - state = { label: false, hasValue: false }; + state = { errors: [], label: false, hasValue: false }; componentDidMount() { + const { errors } = this.props; + let newState = Object.assign({}, this.state); + if (this.props.multiple && !isEmpty(this.props.value)) { - this.setState({ label: 1, hasValue: true }); + newState = Object.assign({}, newState, { label: 1, hasValue: true }); } + + if (!isEmpty(errors)) { + newState = Object.assign({}, newState, { errors }); + } + + this.setState(newState); } componentWillReceiveProps(nextProps) { if (!this.state.hasValue && !isEmpty(nextProps.value) && nextProps.multiple && differenceBy(nextProps.value, this.props.value, 'name').length > 0) { this.setState({ label: 1, hasValue: true }); } + // Check if errors have been updated during validations + if (nextProps.didCheckErrors !== this.props.didCheckErrors) { + // Remove from the state the errors that have already been set + const errors = isEmpty(nextProps.errors) ? [] : nextProps.errors; + this.setState({ errors }); + } } setLabel = (label) => { @@ -40,6 +57,9 @@ class InputFileWithErrors extends React.Component { const { className, customBootstrapClass, + errorsClassName, + errorsStyle, + noErrorsDescription, inputDescription, inputDescriptionClassName, inputDescriptionStyle, @@ -76,6 +96,7 @@ class InputFileWithErrors extends React.Component { )} + {spacer} ); @@ -93,8 +119,12 @@ class InputFileWithErrors extends React.Component { } InputFileWithErrors.defaultProps = { + errors: [], + errorsClassName: '', + errorsStyle: {}, className: '', customBootstrapClass: 'col-md-6', + didCheckErrors: false, inputDescription: '', inputDescriptionClassName: '', inputDescriptionStyle: {}, @@ -102,6 +132,7 @@ InputFileWithErrors.defaultProps = { labelClassName: '', labelStyle: {}, multiple: false, + noErrorsDescription: false, style: {}, value: [], }; @@ -109,6 +140,10 @@ InputFileWithErrors.defaultProps = { InputFileWithErrors.propTypes = { className: PropTypes.string, customBootstrapClass: PropTypes.string, + didCheckErrors: PropTypes.bool, + errors: PropTypes.array, + errorsClassName: PropTypes.string, + errorsStyle: PropTypes.object, inputDescription: PropTypes.oneOfType([ PropTypes.string, PropTypes.func, @@ -131,6 +166,7 @@ InputFileWithErrors.propTypes = { labelStyle: PropTypes.object, multiple: PropTypes.bool, name: PropTypes.string.isRequired, + noErrorsDescription: PropTypes.bool, onChange: PropTypes.func.isRequired, style: PropTypes.object, value: PropTypes.oneOfType([ diff --git a/packages/strapi-plugin-content-type-builder/services/ContentTypeBuilder.js b/packages/strapi-plugin-content-type-builder/services/ContentTypeBuilder.js index 2170bfec9b..6910be9dc8 100755 --- a/packages/strapi-plugin-content-type-builder/services/ContentTypeBuilder.js +++ b/packages/strapi-plugin-content-type-builder/services/ContentTypeBuilder.js @@ -178,7 +178,8 @@ module.exports = { if (params.plugin === 'upload' && relation.model || relation.collection === 'file') { params = { type: 'media', - multiple: params.collection ? true : false + multiple: params.collection ? true : false, + required: params.required }; } else { params = _.omit(params, ['collection', 'model', 'via']); @@ -288,7 +289,8 @@ module.exports = { attrs[attribute.name] = { [attribute.params.multiple ? 'collection' : 'model']: 'file', via, - plugin: 'upload' + plugin: 'upload', + required: attribute.params.required === true ? true : false }; } } else if (_.has(attribute, 'params.target')) { From b9a9e7a829655bfee5049bd436351f1fa21d99a7 Mon Sep 17 00:00:00 2001 From: sundaycrafts Date: Fri, 31 Aug 2018 00:47:45 +0900 Subject: [PATCH 09/22] 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 99eb07fecdee9e90b90667e35590a0abbd5577b2 Mon Sep 17 00:00:00 2001 From: sundaycrafts Date: Fri, 31 Aug 2018 01:08:45 +0900 Subject: [PATCH 10/22] improve user experience 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 00be647da5..ef88c20733 100755 --- a/packages/strapi-generate-new/lib/before.js +++ b/packages/strapi-generate-new/lib/before.js @@ -187,7 +187,7 @@ module.exports = (scope, cb) => { type: 'input', name: 'authenticationDatabase', message: 'Authentication database:', - default: _.get(scope.database, 'authenticationDatabase', undefined) + default: _.get(scope.database, 'authenticationDatabase', 'admin') }, { when: !hasDatabaseConfig && scope.client.database === 'mongo', From cfda76aec9e69bfcb072d4e4c0b22a01933e8d26 Mon Sep 17 00:00:00 2001 From: Jim LAURIE Date: Thu, 30 Aug 2018 18:53:40 +0200 Subject: [PATCH 11/22] Fix one-one relation update --- packages/strapi-hook-mongoose/lib/relations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/strapi-hook-mongoose/lib/relations.js b/packages/strapi-hook-mongoose/lib/relations.js index 1fbe976cac..0dcf8d35b7 100644 --- a/packages/strapi-hook-mongoose/lib/relations.js +++ b/packages/strapi-hook-mongoose/lib/relations.js @@ -50,7 +50,7 @@ module.exports = { .findOne({ [model.primaryKey]: value[current] }) .populate(details.via) .then(record => { - if (record && _.isObject(record[details.via])) { + if (record && _.isObject(record[details.via]) && record._id.toString() !== record[details.via][current].toString()) { return module.exports.update.call(this, { id: getValuePrimaryKey(record[details.via], model.primaryKey), values: { From 082f4a49fe140d6e4fc7db62334746fa6d09ab5b Mon Sep 17 00:00:00 2001 From: sundaycrafts Date: Fri, 31 Aug 2018 10:28:12 +0900 Subject: [PATCH 12/22] 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 9b02ccfead656cc2686d6b45087f4aabd7dbd2a1 Mon Sep 17 00:00:00 2001 From: sundaycrafts Date: Fri, 31 Aug 2018 10:38:48 +0900 Subject: [PATCH 13/22] Revert "improve user experience when generating new app" This reverts commit 99eb07fecdee9e90b90667e35590a0abbd5577b2. --- 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 ef88c20733..00be647da5 100755 --- a/packages/strapi-generate-new/lib/before.js +++ b/packages/strapi-generate-new/lib/before.js @@ -187,7 +187,7 @@ module.exports = (scope, cb) => { type: 'input', name: 'authenticationDatabase', message: 'Authentication database:', - default: _.get(scope.database, 'authenticationDatabase', 'admin') + default: _.get(scope.database, 'authenticationDatabase', undefined) }, { when: !hasDatabaseConfig && scope.client.database === 'mongo', From 74e47c0e603c8485dac54eaf1fa01ff8fa3190da Mon Sep 17 00:00:00 2001 From: sundaycrafts Date: Fri, 31 Aug 2018 10:44:49 +0900 Subject: [PATCH 14/22] add hint to strapi generate new --- 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 00be647da5..842cbd1668 100755 --- a/packages/strapi-generate-new/lib/before.js +++ b/packages/strapi-generate-new/lib/before.js @@ -186,7 +186,7 @@ module.exports = (scope, cb) => { when: !hasDatabaseConfig && scope.client.database === 'mongo', type: 'input', name: 'authenticationDatabase', - message: 'Authentication database:', + message: 'Authentication database(Maybe "admin" or blank):', default: _.get(scope.database, 'authenticationDatabase', undefined) }, { From bb38f2a42c04fb343dc02a5f58d714988a15ec9f Mon Sep 17 00:00:00 2001 From: Jim LAURIE Date: Fri, 31 Aug 2018 11:41:21 +0200 Subject: [PATCH 15/22] Handle bookshelf fix #1490 --- packages/strapi-hook-bookshelf/lib/relations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/strapi-hook-bookshelf/lib/relations.js b/packages/strapi-hook-bookshelf/lib/relations.js index 09a6a26291..e22b8418d1 100644 --- a/packages/strapi-hook-bookshelf/lib/relations.js +++ b/packages/strapi-hook-bookshelf/lib/relations.js @@ -97,7 +97,7 @@ module.exports = { module.exports.findOne .call(model, { [model.primaryKey]: recordId }, [details.via]) .then(record => { - if (record && _.isObject(record[details.via])) { + if (record && _.isObject(record[details.via]) && record.id !== record[details.via][current]) { return module.exports.update.call(this, { id: getValuePrimaryKey(record[details.via], model.primaryKey), values: { From 71beb1bebac0e26a17e042bbc279a2240e87147c Mon Sep 17 00:00:00 2001 From: sundaycrafts Date: Fri, 31 Aug 2018 19:07:15 +0900 Subject: [PATCH 16/22] 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 d75f3acae55d6a0123ff32dcf6e1e1f390965968 Mon Sep 17 00:00:00 2001 From: Vladislav Arsenev Date: Fri, 31 Aug 2018 13:30:39 +0300 Subject: [PATCH 17/22] fix:repeat-upload-the-same-file - clear file input after trigger event --- .../strapi-helper-plugin/lib/src/components/InputFile/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/strapi-helper-plugin/lib/src/components/InputFile/index.js b/packages/strapi-helper-plugin/lib/src/components/InputFile/index.js index 8763ba4ae6..6369c6de11 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputFile/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/InputFile/index.js @@ -59,7 +59,8 @@ class InputFile extends React.Component { type: 'file', value, }; - + + this.inputFile.value = ''; this.setState({ isUploading: !this.state.isUploading }); this.props.onChange({ target }); } From 2e049da8caa8ca63396166febdb224b1a76c224c Mon Sep 17 00:00:00 2001 From: sundaycrafts Date: Sun, 2 Sep 2018 03:28:22 +0900 Subject: [PATCH 18/22] tweak new command message --- 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 842cbd1668..a0e47d9524 100755 --- a/packages/strapi-generate-new/lib/before.js +++ b/packages/strapi-generate-new/lib/before.js @@ -186,7 +186,7 @@ module.exports = (scope, cb) => { when: !hasDatabaseConfig && scope.client.database === 'mongo', type: 'input', name: 'authenticationDatabase', - message: 'Authentication database(Maybe "admin" or blank):', + message: 'Authentication database (Maybe "admin" or blank):', default: _.get(scope.database, 'authenticationDatabase', undefined) }, { From 3ee7bbacf31023a77c2c7338f6108ad48eea8c98 Mon Sep 17 00:00:00 2001 From: Kati Frantz Date: Sun, 2 Sep 2018 19:38:15 +0100 Subject: [PATCH 19/22] 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": { From 3a396972f00f829742c0cb3bebad2de4fb376378 Mon Sep 17 00:00:00 2001 From: Jim LAURIE Date: Mon, 3 Sep 2018 14:19:51 +0200 Subject: [PATCH 20/22] Fix #1882 --- packages/strapi-plugin-users-permissions/controllers/Auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/strapi-plugin-users-permissions/controllers/Auth.js b/packages/strapi-plugin-users-permissions/controllers/Auth.js index 72ba075327..20e93a6b5d 100644 --- a/packages/strapi-plugin-users-permissions/controllers/Auth.js +++ b/packages/strapi-plugin-users-permissions/controllers/Auth.js @@ -285,7 +285,7 @@ module.exports = { return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.email.taken' }] }] : 'Email is already taken.'); } - if (user && user.provider !== params.provider && strapi.plugins['users-permissions'].config.advanced.unique_email) { + if (user && user.provider !== params.provider && settings.unique_email) { return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.email.taken' }] }] : 'Email is already taken.'); } From 8eb37c2b6b5bc55f7c88441c3cd1cdada5f3d432 Mon Sep 17 00:00:00 2001 From: Vladislav Arsenev Date: Tue, 4 Sep 2018 10:30:54 +0300 Subject: [PATCH 21/22] fix: success-delete-notification - add success notification --- .../admin/src/containers/ListPage/saga.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/saga.js b/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/saga.js index 32497c9870..cf2db9228e 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/saga.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/saga.js @@ -107,6 +107,7 @@ export function* dataDeleteAll({ entriesToDelete, model, source }) { yield put(deleteSeveralDataSuccess()); yield call(dataGet, { currentModel: model, source }); + strapi.notification.success('content-manager.success.record.delete'); } catch(err) { strapi.notification.error('content-manager.error.record.delete'); } From 34124bd6a3cf58ba0248efe7f5f298e87f628abf Mon Sep 17 00:00:00 2001 From: Jim LAURIE Date: Wed, 5 Sep 2018 11:14:03 +0200 Subject: [PATCH 22/22] Check user exist before user is blocked --- .../strapi-plugin-users-permissions/controllers/Auth.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/strapi-plugin-users-permissions/controllers/Auth.js b/packages/strapi-plugin-users-permissions/controllers/Auth.js index 20e93a6b5d..25f608040e 100644 --- a/packages/strapi-plugin-users-permissions/controllers/Auth.js +++ b/packages/strapi-plugin-users-permissions/controllers/Auth.js @@ -56,14 +56,14 @@ module.exports = { return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.confirmed' }] }] : 'Your account email is not confirmed.'); } - if (user.blocked === true) { - return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.blocked' }] }] : 'Your account has been blocked by the administrator.'); - } - if (!user) { return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.invalid' }] }] : 'Identifier or password invalid.'); } + if (user.blocked === true) { + return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.blocked' }] }] : 'Your account has been blocked by the administrator.'); + } + if (user.role.type !== 'root' && ctx.request.admin) { return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.noAdminAccess' }] }] : `You're not an administrator.`); }