| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | const _ = require('lodash'); | 
					
						
							| 
									
										
										
										
											2019-07-05 17:17:39 +02:00
										 |  |  | const { singular } = require('pluralize'); | 
					
						
							| 
									
										
										
										
											2019-08-05 15:22:39 +02:00
										 |  |  | const dateFns = require('date-fns'); | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | const utilsModels = require('strapi-utils').models; | 
					
						
							|  |  |  | const relations = require('./relations'); | 
					
						
							|  |  |  | const buildDatabaseSchema = require('./buildDatabaseSchema'); | 
					
						
							| 
									
										
										
										
											2019-07-16 18:05:24 +02:00
										 |  |  | const { | 
					
						
							|  |  |  |   createGroupJoinTables, | 
					
						
							|  |  |  |   createGroupModels, | 
					
						
							|  |  |  | } = require('./generate-group-relations'); | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | const PIVOT_PREFIX = '_pivot_'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const getDatabaseName = connection => { | 
					
						
							|  |  |  |   const dbName = _.get(connection.settings, 'database'); | 
					
						
							|  |  |  |   const dbSchema = _.get(connection.settings, 'schema', 'public'); | 
					
						
							|  |  |  |   switch (_.get(connection.settings, 'client')) { | 
					
						
							|  |  |  |     case 'sqlite3': | 
					
						
							|  |  |  |       return 'main'; | 
					
						
							|  |  |  |     case 'pg': | 
					
						
							|  |  |  |       return `${dbName}.${dbSchema}`; | 
					
						
							|  |  |  |     case 'mysql': | 
					
						
							|  |  |  |       return dbName; | 
					
						
							|  |  |  |     default: | 
					
						
							|  |  |  |       return dbName; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports = ({ models, target, plugin = false }, ctx) => { | 
					
						
							|  |  |  |   const { GLOBALS, connection, ORM } = ctx; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Parse every authenticated model.
 | 
					
						
							|  |  |  |   const updates = Object.keys(models).map(async model => { | 
					
						
							|  |  |  |     const definition = models[model]; | 
					
						
							|  |  |  |     definition.globalName = _.upperFirst(_.camelCase(definition.globalId)); | 
					
						
							|  |  |  |     definition.associations = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Define local GLOBALS to expose every models in this file.
 | 
					
						
							|  |  |  |     GLOBALS[definition.globalId] = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-11 10:14:50 +02:00
										 |  |  |     // Add some information about ORM & client connection & tableName
 | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |     definition.orm = 'bookshelf'; | 
					
						
							|  |  |  |     definition.databaseName = getDatabaseName(connection); | 
					
						
							|  |  |  |     definition.client = _.get(connection.settings, 'client'); | 
					
						
							|  |  |  |     _.defaults(definition, { | 
					
						
							|  |  |  |       primaryKey: 'id', | 
					
						
							|  |  |  |       primaryKeyType: _.get(definition, 'options.idAttributeType', 'integer'), | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Use default timestamp column names if value is `true`
 | 
					
						
							|  |  |  |     if (_.get(definition, 'options.timestamps', false) === true) { | 
					
						
							|  |  |  |       _.set(definition, 'options.timestamps', ['created_at', 'updated_at']); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     // Use false for values other than `Boolean` or `Array`
 | 
					
						
							|  |  |  |     if ( | 
					
						
							|  |  |  |       !_.isArray(_.get(definition, 'options.timestamps')) && | 
					
						
							|  |  |  |       !_.isBoolean(_.get(definition, 'options.timestamps')) | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |       _.set(definition, 'options.timestamps', false); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Register the final model for Bookshelf.
 | 
					
						
							|  |  |  |     const loadedModel = _.assign( | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         tableName: definition.collectionName, | 
					
						
							|  |  |  |         hasTimestamps: _.get(definition, 'options.timestamps', false), | 
					
						
							|  |  |  |         idAttribute: _.get(definition, 'options.idAttribute', 'id'), | 
					
						
							|  |  |  |         associations: [], | 
					
						
							|  |  |  |         defaults: Object.keys(definition.attributes).reduce((acc, current) => { | 
					
						
							|  |  |  |           if ( | 
					
						
							|  |  |  |             definition.attributes[current].type && | 
					
						
							|  |  |  |             definition.attributes[current].default | 
					
						
							|  |  |  |           ) { | 
					
						
							|  |  |  |             acc[current] = definition.attributes[current].default; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return acc; | 
					
						
							|  |  |  |         }, {}), | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       definition.options | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-01 16:59:14 +02:00
										 |  |  |     const groupAttributes = Object.keys(definition.attributes).filter( | 
					
						
							|  |  |  |       key => definition.attributes[key].type === 'group' | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |     if (_.isString(_.get(connection, 'options.pivot_prefix'))) { | 
					
						
							|  |  |  |       loadedModel.toJSON = function(options = {}) { | 
					
						
							|  |  |  |         const { shallow = false, omitPivot = false } = options; | 
					
						
							|  |  |  |         const attributes = this.serialize(options); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!shallow) { | 
					
						
							|  |  |  |           const pivot = this.pivot && !omitPivot && this.pivot.attributes; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           // Remove pivot attributes with prefix.
 | 
					
						
							|  |  |  |           _.keys(pivot).forEach( | 
					
						
							|  |  |  |             key => delete attributes[`${PIVOT_PREFIX}${key}`] | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           // Add pivot attributes without prefix.
 | 
					
						
							|  |  |  |           const pivotAttributes = _.mapKeys( | 
					
						
							|  |  |  |             pivot, | 
					
						
							|  |  |  |             (value, key) => `${connection.options.pivot_prefix}${key}` | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return Object.assign({}, attributes, pivotAttributes); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return attributes; | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Initialize the global variable with the
 | 
					
						
							|  |  |  |     // capitalized model name.
 | 
					
						
							|  |  |  |     if (!plugin) { | 
					
						
							|  |  |  |       global[definition.globalName] = {}; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:05:24 +02:00
										 |  |  |     await createGroupModels({ model: loadedModel, definition, ORM, GLOBALS }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |     // Add every relationships to the loaded model for Bookshelf.
 | 
					
						
							|  |  |  |     // Basic attributes don't need this-- only relations.
 | 
					
						
							| 
									
										
										
										
											2019-07-04 15:27:27 +02:00
										 |  |  |     Object.keys(definition.attributes).forEach(name => { | 
					
						
							|  |  |  |       const details = definition.attributes[name]; | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |       if (details.type !== undefined) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-05 17:17:39 +02:00
										 |  |  |       const { nature, verbose } = | 
					
						
							|  |  |  |         utilsModels.getNature(details, name, undefined, model.toLowerCase()) || | 
					
						
							|  |  |  |         {}; | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |       // Build associations key
 | 
					
						
							|  |  |  |       utilsModels.defineAssociations( | 
					
						
							|  |  |  |         model.toLowerCase(), | 
					
						
							|  |  |  |         definition, | 
					
						
							|  |  |  |         details, | 
					
						
							|  |  |  |         name | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       let globalId; | 
					
						
							|  |  |  |       const globalName = details.model || details.collection || ''; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Exclude polymorphic association.
 | 
					
						
							|  |  |  |       if (globalName !== '*') { | 
					
						
							|  |  |  |         globalId = details.plugin | 
					
						
							|  |  |  |           ? _.get( | 
					
						
							|  |  |  |               strapi.plugins, | 
					
						
							|  |  |  |               `${details.plugin}.models.${globalName.toLowerCase()}.globalId` | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |           : _.get(strapi.models, `${globalName.toLowerCase()}.globalId`); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       switch (verbose) { | 
					
						
							|  |  |  |         case 'hasOne': { | 
					
						
							|  |  |  |           const target = details.plugin | 
					
						
							|  |  |  |             ? strapi.plugins[details.plugin].models[details.model] | 
					
						
							|  |  |  |             : strapi.models[details.model]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           const FK = _.findKey(target.attributes, details => { | 
					
						
							|  |  |  |             if ( | 
					
						
							| 
									
										
										
										
											2019-09-09 15:32:02 +02:00
										 |  |  |               _.has(details, 'model') && | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |               details.model === model && | 
					
						
							| 
									
										
										
										
											2019-09-09 15:32:02 +02:00
										 |  |  |               _.has(details, 'via') && | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |               details.via === name | 
					
						
							|  |  |  |             ) { | 
					
						
							|  |  |  |               return details; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           const columnName = _.get(target.attributes, [FK, 'columnName'], FK); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           loadedModel[name] = function() { | 
					
						
							|  |  |  |             return this.hasOne(GLOBALS[globalId], columnName); | 
					
						
							|  |  |  |           }; | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         case 'hasMany': { | 
					
						
							|  |  |  |           const columnName = details.plugin | 
					
						
							|  |  |  |             ? _.get( | 
					
						
							|  |  |  |                 strapi.plugins, | 
					
						
							|  |  |  |                 `${ | 
					
						
							|  |  |  |                   details.plugin | 
					
						
							|  |  |  |                 }.models.${globalId.toLowerCase()}.attributes.${ | 
					
						
							|  |  |  |                   details.via | 
					
						
							|  |  |  |                 }.columnName`,
 | 
					
						
							|  |  |  |                 details.via | 
					
						
							|  |  |  |               ) | 
					
						
							|  |  |  |             : _.get( | 
					
						
							|  |  |  |                 strapi.models[globalId.toLowerCase()].attributes, | 
					
						
							|  |  |  |                 `${details.via}.columnName`, | 
					
						
							|  |  |  |                 details.via | 
					
						
							|  |  |  |               ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           // Set this info to be able to see if this field is a real database's field.
 | 
					
						
							|  |  |  |           details.isVirtual = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           loadedModel[name] = function() { | 
					
						
							|  |  |  |             return this.hasMany(GLOBALS[globalId], columnName); | 
					
						
							|  |  |  |           }; | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         case 'belongsTo': { | 
					
						
							|  |  |  |           loadedModel[name] = function() { | 
					
						
							|  |  |  |             return this.belongsTo( | 
					
						
							|  |  |  |               GLOBALS[globalId], | 
					
						
							|  |  |  |               _.get(details, 'columnName', name) | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |           }; | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         case 'belongsToMany': { | 
					
						
							| 
									
										
										
										
											2019-07-05 17:17:39 +02:00
										 |  |  |           const targetModel = details.plugin | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |             ? strapi.plugins[details.plugin].models[details.collection] | 
					
						
							|  |  |  |             : strapi.models[details.collection]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-05 17:17:39 +02:00
										 |  |  |           // Force singular foreign key
 | 
					
						
							|  |  |  |           details.attribute = singular(details.collection); | 
					
						
							|  |  |  |           details.column = targetModel.primaryKey; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-19 11:44:53 +02:00
										 |  |  |           // Set this info to be able to see if this field is a real database's field.
 | 
					
						
							|  |  |  |           details.isVirtual = true; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-05 17:17:39 +02:00
										 |  |  |           if (nature === 'manyWay') { | 
					
						
							|  |  |  |             const joinTableName = `${definition.collectionName}__${_.snakeCase( | 
					
						
							|  |  |  |               name | 
					
						
							|  |  |  |             )}`;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const foreignKey = `${singular(definition.collectionName)}_${ | 
					
						
							|  |  |  |               definition.primaryKey | 
					
						
							|  |  |  |             }`;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const otherKey = `${details.attribute}_${details.column}`; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-19 11:44:53 +02:00
										 |  |  |             loadedModel[name] = function() { | 
					
						
							|  |  |  |               const targetBookshelfModel = GLOBALS[globalId]; | 
					
						
							|  |  |  |               let collection = this.belongsToMany( | 
					
						
							|  |  |  |                 targetBookshelfModel, | 
					
						
							|  |  |  |                 joinTableName, | 
					
						
							|  |  |  |                 foreignKey, | 
					
						
							|  |  |  |                 otherKey | 
					
						
							|  |  |  |               ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |               if (Array.isArray(details.withPivot)) { | 
					
						
							|  |  |  |                 return collection.withPivot(details.withPivot); | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |               return collection; | 
					
						
							|  |  |  |             }; | 
					
						
							| 
									
										
										
										
											2019-07-05 17:17:39 +02:00
										 |  |  |           } else { | 
					
						
							|  |  |  |             const joinTableName = | 
					
						
							|  |  |  |               _.get(details, 'collectionName') || | 
					
						
							|  |  |  |               utilsModels.getCollectionName( | 
					
						
							|  |  |  |                 targetModel.attributes[details.via], | 
					
						
							|  |  |  |                 details | 
					
						
							|  |  |  |               ); | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-05 17:17:39 +02:00
										 |  |  |             const relationship = targetModel.attributes[details.via]; | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-05 17:17:39 +02:00
										 |  |  |             // Define PK column
 | 
					
						
							|  |  |  |             relationship.attribute = singular(relationship.collection); | 
					
						
							|  |  |  |             relationship.column = definition.primaryKey; | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-05 17:17:39 +02:00
										 |  |  |             // Sometimes the many-to-many relationships
 | 
					
						
							|  |  |  |             // is on the same keys on the same models (ex: `friends` key in model `User`)
 | 
					
						
							|  |  |  |             if ( | 
					
						
							|  |  |  |               `${details.attribute}_${details.column}` === | 
					
						
							|  |  |  |               `${relationship.attribute}_${relationship.column}` | 
					
						
							|  |  |  |             ) { | 
					
						
							|  |  |  |               relationship.attribute = singular(details.via); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-19 11:44:53 +02:00
										 |  |  |             loadedModel[name] = function() { | 
					
						
							|  |  |  |               const targetBookshelfModel = GLOBALS[globalId]; | 
					
						
							| 
									
										
										
										
											2019-07-05 17:17:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-19 11:44:53 +02:00
										 |  |  |               const foreignKey = `${relationship.attribute}_${relationship.column}`; | 
					
						
							|  |  |  |               const otherKey = `${details.attribute}_${details.column}`; | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-19 11:44:53 +02:00
										 |  |  |               let collection = this.belongsToMany( | 
					
						
							|  |  |  |                 targetBookshelfModel, | 
					
						
							|  |  |  |                 joinTableName, | 
					
						
							|  |  |  |                 foreignKey, | 
					
						
							|  |  |  |                 otherKey | 
					
						
							|  |  |  |               ); | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-19 11:44:53 +02:00
										 |  |  |               if (Array.isArray(details.withPivot)) { | 
					
						
							|  |  |  |                 return collection.withPivot(details.withPivot); | 
					
						
							|  |  |  |               } | 
					
						
							| 
									
										
										
										
											2019-07-05 17:17:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-19 11:44:53 +02:00
										 |  |  |               return collection; | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         case 'morphOne': { | 
					
						
							|  |  |  |           const model = details.plugin | 
					
						
							|  |  |  |             ? strapi.plugins[details.plugin].models[details.model] | 
					
						
							|  |  |  |             : strapi.models[details.model]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           const globalId = `${model.collectionName}_morph`; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           loadedModel[name] = function() { | 
					
						
							|  |  |  |             return this.morphOne( | 
					
						
							|  |  |  |               GLOBALS[globalId], | 
					
						
							|  |  |  |               details.via, | 
					
						
							|  |  |  |               `${definition.collectionName}` | 
					
						
							|  |  |  |             ).query(qb => { | 
					
						
							|  |  |  |               qb.where( | 
					
						
							|  |  |  |                 _.get(model, `attributes.${details.via}.filter`, 'field'), | 
					
						
							|  |  |  |                 name | 
					
						
							|  |  |  |               ); | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |           }; | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         case 'morphMany': { | 
					
						
							|  |  |  |           const collection = details.plugin | 
					
						
							|  |  |  |             ? strapi.plugins[details.plugin].models[details.collection] | 
					
						
							|  |  |  |             : strapi.models[details.collection]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           const globalId = `${collection.collectionName}_morph`; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           loadedModel[name] = function() { | 
					
						
							|  |  |  |             return this.morphMany( | 
					
						
							|  |  |  |               GLOBALS[globalId], | 
					
						
							|  |  |  |               details.via, | 
					
						
							|  |  |  |               `${definition.collectionName}` | 
					
						
							|  |  |  |             ).query(qb => { | 
					
						
							|  |  |  |               qb.where( | 
					
						
							|  |  |  |                 _.get(collection, `attributes.${details.via}.filter`, 'field'), | 
					
						
							|  |  |  |                 name | 
					
						
							|  |  |  |               ); | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |           }; | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         case 'belongsToMorph': | 
					
						
							|  |  |  |         case 'belongsToManyMorph': { | 
					
						
							|  |  |  |           const association = definition.associations.find( | 
					
						
							|  |  |  |             association => association.alias === name | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           const morphValues = association.related.map(id => { | 
					
						
							|  |  |  |             let models = Object.values(strapi.models).filter( | 
					
						
							|  |  |  |               model => model.globalId === id | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |             if (models.length === 0) { | 
					
						
							|  |  |  |               models = Object.values(strapi.groups).filter( | 
					
						
							|  |  |  |                 model => model.globalId === id | 
					
						
							|  |  |  |               ); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |             if (models.length === 0) { | 
					
						
							|  |  |  |               models = Object.keys(strapi.plugins).reduce((acc, current) => { | 
					
						
							|  |  |  |                 const models = Object.values( | 
					
						
							|  |  |  |                   strapi.plugins[current].models | 
					
						
							|  |  |  |                 ).filter(model => model.globalId === id); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if (acc.length === 0 && models.length > 0) { | 
					
						
							|  |  |  |                   acc = models; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 return acc; | 
					
						
							|  |  |  |               }, []); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (models.length === 0) { | 
					
						
							|  |  |  |               strapi.log.error(`Impossible to register the '${model}' model.`); | 
					
						
							|  |  |  |               strapi.log.error( | 
					
						
							|  |  |  |                 'The collection name cannot be found for the morphTo method.' | 
					
						
							|  |  |  |               ); | 
					
						
							|  |  |  |               strapi.stop(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return models[0].collectionName; | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           // Define new model.
 | 
					
						
							|  |  |  |           const options = { | 
					
						
							|  |  |  |             tableName: `${definition.collectionName}_morph`, | 
					
						
							|  |  |  |             [definition.collectionName]: function() { | 
					
						
							|  |  |  |               return this.belongsTo( | 
					
						
							|  |  |  |                 GLOBALS[definition.globalId], | 
					
						
							|  |  |  |                 `${definition.collectionName}_id` | 
					
						
							|  |  |  |               ); | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             related: function() { | 
					
						
							|  |  |  |               return this.morphTo( | 
					
						
							|  |  |  |                 name, | 
					
						
							|  |  |  |                 ...association.related.map((id, index) => [ | 
					
						
							|  |  |  |                   GLOBALS[id], | 
					
						
							|  |  |  |                   morphValues[index], | 
					
						
							|  |  |  |                 ]) | 
					
						
							|  |  |  |               ); | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |           }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           GLOBALS[options.tableName] = ORM.Model.extend(options); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           // Set polymorphic table name to the main model.
 | 
					
						
							|  |  |  |           target[model].morph = GLOBALS[options.tableName]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           // Hack Bookshelf to create a many-to-many polymorphic association.
 | 
					
						
							|  |  |  |           // Upload has many Upload_morph that morph to different model.
 | 
					
						
							|  |  |  |           loadedModel[name] = function() { | 
					
						
							|  |  |  |             if (verbose === 'belongsToMorph') { | 
					
						
							|  |  |  |               return this.hasOne( | 
					
						
							|  |  |  |                 GLOBALS[options.tableName], | 
					
						
							|  |  |  |                 `${definition.collectionName}_id` | 
					
						
							|  |  |  |               ); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return this.hasMany( | 
					
						
							|  |  |  |               GLOBALS[options.tableName], | 
					
						
							|  |  |  |               `${definition.collectionName}_id` | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |           }; | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         default: { | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Call this callback function after we are done parsing
 | 
					
						
							|  |  |  |     // all attributes for relationships-- see below.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       // External function to map key that has been updated with `columnName`
 | 
					
						
							|  |  |  |       const mapper = (params = {}) => { | 
					
						
							| 
									
										
										
										
											2019-08-08 12:03:57 +02:00
										 |  |  |         Object.keys(params).map(key => { | 
					
						
							|  |  |  |           const attr = definition.attributes[key] || {}; | 
					
						
							|  |  |  |           params[key] = castValueFromType(attr.type, params[key], definition); | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return _.mapKeys(params, (value, key) => { | 
					
						
							|  |  |  |           const attr = definition.attributes[key] || {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return _.isPlainObject(attr) && _.isString(attr['columnName']) | 
					
						
							|  |  |  |             ? attr['columnName'] | 
					
						
							|  |  |  |             : key; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-29 18:00:01 +02:00
										 |  |  |       // Extract association except polymorphic.
 | 
					
						
							|  |  |  |       const associations = definition.associations.filter( | 
					
						
							|  |  |  |         association => association.nature.toLowerCase().indexOf('morph') === -1 | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       // Extract polymorphic association.
 | 
					
						
							|  |  |  |       const polymorphicAssociations = definition.associations.filter( | 
					
						
							|  |  |  |         association => association.nature.toLowerCase().indexOf('morph') !== -1 | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |       // Update serialize to reformat data for polymorphic associations.
 | 
					
						
							|  |  |  |       loadedModel.serialize = function(options) { | 
					
						
							|  |  |  |         const attrs = _.clone(this.attributes); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (options && options.shallow) { | 
					
						
							|  |  |  |           return attrs; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const relations = this.relations; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-16 19:45:42 +02:00
										 |  |  |         groupAttributes.forEach(key => { | 
					
						
							| 
									
										
										
										
											2019-07-29 18:00:01 +02:00
										 |  |  |           const { repeatable } = definition.attributes[key]; | 
					
						
							| 
									
										
										
										
											2019-06-16 19:45:42 +02:00
										 |  |  |           if (relations[key]) { | 
					
						
							| 
									
										
										
										
											2019-07-30 15:12:14 +02:00
										 |  |  |             const groups = relations[key].toJSON().map(el => el.group); | 
					
						
							| 
									
										
										
										
											2019-06-16 19:45:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-29 18:00:01 +02:00
										 |  |  |             attrs[key] = repeatable === true ? groups : _.first(groups) || null; | 
					
						
							| 
									
										
										
										
											2019-06-16 19:45:42 +02:00
										 |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |         polymorphicAssociations.map(association => { | 
					
						
							|  |  |  |           // Retrieve relation Bookshelf object.
 | 
					
						
							|  |  |  |           const relation = relations[association.alias]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if (relation) { | 
					
						
							|  |  |  |             // Extract raw JSON data.
 | 
					
						
							|  |  |  |             attrs[association.alias] = relation.toJSON | 
					
						
							|  |  |  |               ? relation.toJSON(options) | 
					
						
							|  |  |  |               : relation; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Retrieve opposite model.
 | 
					
						
							|  |  |  |             const model = association.plugin | 
					
						
							|  |  |  |               ? strapi.plugins[association.plugin].models[ | 
					
						
							|  |  |  |                   association.collection || association.model | 
					
						
							|  |  |  |                 ] | 
					
						
							|  |  |  |               : strapi.models[association.collection || association.model]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Reformat data by bypassing the many-to-many relationship.
 | 
					
						
							|  |  |  |             switch (association.nature) { | 
					
						
							|  |  |  |               case 'oneToManyMorph': | 
					
						
							|  |  |  |                 attrs[association.alias] = | 
					
						
							| 
									
										
										
										
											2019-07-29 18:00:01 +02:00
										 |  |  |                   attrs[association.alias][model.collectionName] || null; | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |                 break; | 
					
						
							|  |  |  |               case 'manyToManyMorph': | 
					
						
							|  |  |  |                 attrs[association.alias] = attrs[association.alias].map( | 
					
						
							|  |  |  |                   rel => rel[model.collectionName] | 
					
						
							|  |  |  |                 ); | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |               case 'oneMorphToOne': | 
					
						
							| 
									
										
										
										
											2019-07-29 18:00:01 +02:00
										 |  |  |                 attrs[association.alias] = | 
					
						
							|  |  |  |                   attrs[association.alias].related || null; | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |                 break; | 
					
						
							|  |  |  |               case 'manyMorphToOne': | 
					
						
							|  |  |  |               case 'manyMorphToMany': | 
					
						
							|  |  |  |                 attrs[association.alias] = attrs[association.alias].map( | 
					
						
							|  |  |  |                   obj => obj.related | 
					
						
							|  |  |  |                 ); | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |               default: | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         associations.map(association => { | 
					
						
							|  |  |  |           const relation = relations[association.alias]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if (relation) { | 
					
						
							|  |  |  |             // Extract raw JSON data.
 | 
					
						
							|  |  |  |             attrs[association.alias] = relation.toJSON | 
					
						
							|  |  |  |               ? relation.toJSON(options) | 
					
						
							|  |  |  |               : relation; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return attrs; | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |       const findModelByAssoc = ({ assoc }) => { | 
					
						
							|  |  |  |         const target = assoc.collection || assoc.model; | 
					
						
							|  |  |  |         return assoc.plugin === 'admin' | 
					
						
							|  |  |  |           ? strapi.admin.models[target] | 
					
						
							|  |  |  |           : assoc.plugin | 
					
						
							|  |  |  |           ? strapi.plugins[assoc.plugin].models[target] | 
					
						
							|  |  |  |           : strapi.models[target]; | 
					
						
							|  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |       const isPolymorphic = ({ assoc }) => { | 
					
						
							|  |  |  |         return assoc.nature.toLowerCase().indexOf('morph') !== -1; | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const formatPolymorphicPopulate = ({ assoc, path, prefix = '' }) => { | 
					
						
							|  |  |  |         if (_.isString(path) && path === assoc.via) { | 
					
						
							| 
									
										
										
										
											2019-07-30 15:17:03 +02:00
										 |  |  |           return { [`related.${assoc.via}`]: () => {} }; | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |         } else if (_.isString(path) && path === assoc.alias) { | 
					
						
							|  |  |  |           // MorphTo side.
 | 
					
						
							|  |  |  |           if (assoc.related) { | 
					
						
							| 
									
										
										
										
											2019-07-30 15:17:03 +02:00
										 |  |  |             return { [`${prefix}${assoc.alias}.related`]: () => {} }; | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |           // oneToMorph or manyToMorph side.
 | 
					
						
							|  |  |  |           // Retrieve collection name because we are using it to build our hidden model.
 | 
					
						
							|  |  |  |           const model = findModelByAssoc({ assoc }); | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |           return { | 
					
						
							|  |  |  |             [`${prefix}${assoc.alias}.${model.collectionName}`]: function( | 
					
						
							|  |  |  |               query | 
					
						
							|  |  |  |             ) { | 
					
						
							|  |  |  |               query.orderBy('created_at', 'desc'); | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |           }; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |       const createAssociationPopulate = () => { | 
					
						
							|  |  |  |         return definition.associations | 
					
						
							|  |  |  |           .filter(ast => ast.autoPopulate !== false) | 
					
						
							|  |  |  |           .map(assoc => { | 
					
						
							|  |  |  |             if (isPolymorphic({ assoc })) { | 
					
						
							|  |  |  |               return formatPolymorphicPopulate({ | 
					
						
							|  |  |  |                 assoc, | 
					
						
							|  |  |  |                 path: assoc.alias, | 
					
						
							|  |  |  |               }); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             let path = assoc.alias; | 
					
						
							|  |  |  |             let extraAssocs = []; | 
					
						
							|  |  |  |             if (assoc) { | 
					
						
							|  |  |  |               const assocModel = findModelByAssoc({ assoc }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |               extraAssocs = assocModel.associations | 
					
						
							|  |  |  |                 .filter(assoc => isPolymorphic({ assoc })) | 
					
						
							|  |  |  |                 .map(assoc => | 
					
						
							|  |  |  |                   formatPolymorphicPopulate({ | 
					
						
							|  |  |  |                     assoc, | 
					
						
							|  |  |  |                     path: assoc.alias, | 
					
						
							|  |  |  |                     prefix: `${path}.`, | 
					
						
							|  |  |  |                   }) | 
					
						
							|  |  |  |                 ); | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |             return [assoc.alias, ...extraAssocs]; | 
					
						
							|  |  |  |           }) | 
					
						
							|  |  |  |           .reduce((acc, val) => acc.concat(val), []); | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const populateGroup = key => { | 
					
						
							|  |  |  |         let paths = []; | 
					
						
							|  |  |  |         const group = strapi.groups[definition.attributes[key].group]; | 
					
						
							| 
									
										
										
										
											2019-08-01 08:52:35 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |         const assocs = (group.associations || []).filter( | 
					
						
							|  |  |  |           assoc => assoc.autoPopulate === true | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 15:12:14 +02:00
										 |  |  |         // paths.push(`${key}.group`);
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |         assocs.forEach(assoc => { | 
					
						
							|  |  |  |           if (isPolymorphic({ assoc })) { | 
					
						
							|  |  |  |             const rel = formatPolymorphicPopulate({ | 
					
						
							|  |  |  |               assoc, | 
					
						
							|  |  |  |               path: assoc.alias, | 
					
						
							| 
									
										
										
										
											2019-07-30 15:12:14 +02:00
										 |  |  |               prefix: `${key}.group.`, | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |             }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             paths.push(rel); | 
					
						
							|  |  |  |           } else { | 
					
						
							| 
									
										
										
										
											2019-07-30 15:12:14 +02:00
										 |  |  |             paths.push(`${key}.group.${assoc.alias}`); | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |           } | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |         }); | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-01 08:52:35 +02:00
										 |  |  |         return [`${key}.group`, ...paths]; | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |       }; | 
					
						
							| 
									
										
										
										
											2019-07-29 18:00:01 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |       const createGroupsPopulate = () => { | 
					
						
							|  |  |  |         const groupsToPopulate = groupAttributes.reduce((acc, key) => { | 
					
						
							|  |  |  |           const attribute = definition.attributes[key]; | 
					
						
							|  |  |  |           const autoPopulate = _.get(attribute, ['autoPopulate'], true); | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |           if (autoPopulate === true) { | 
					
						
							|  |  |  |             return acc.concat(populateGroup(key)); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           return acc; | 
					
						
							|  |  |  |         }, []); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return groupsToPopulate; | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const isGroup = (def, key) => | 
					
						
							|  |  |  |         _.get(def, ['attributes', key, 'type']) === 'group'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const formatPopulateOptions = withRelated => { | 
					
						
							|  |  |  |         if (!Array.isArray(withRelated)) withRelated = [withRelated]; | 
					
						
							| 
									
										
										
										
											2019-07-29 18:00:01 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |         const obj = withRelated.reduce((acc, key) => { | 
					
						
							|  |  |  |           if (_.isString(key)) { | 
					
						
							|  |  |  |             acc[key] = () => {}; | 
					
						
							|  |  |  |             return acc; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return _.extend(acc, key); | 
					
						
							|  |  |  |         }, {}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // if groups are no
 | 
					
						
							|  |  |  |         const finalObj = Object.keys(obj).reduce((acc, key) => { | 
					
						
							|  |  |  |           // check the key path and update it if necessary nothing more
 | 
					
						
							|  |  |  |           const parts = key.split('.'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           let newKey; | 
					
						
							|  |  |  |           let prefix = ''; | 
					
						
							|  |  |  |           let tmpModel = definition; | 
					
						
							|  |  |  |           for (let part of parts) { | 
					
						
							|  |  |  |             if (isGroup(tmpModel, part)) { | 
					
						
							|  |  |  |               tmpModel = strapi.groups[tmpModel.attributes[part].group]; | 
					
						
							|  |  |  |               // add group path and there relations / images
 | 
					
						
							| 
									
										
										
										
											2019-07-30 15:12:14 +02:00
										 |  |  |               const path = `${prefix}${part}.group`; | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |               newKey = path; | 
					
						
							|  |  |  |               prefix = `${path}.`; | 
					
						
							|  |  |  |               continue; | 
					
						
							| 
									
										
										
										
											2019-07-29 18:00:01 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |             const assoc = tmpModel.associations.find( | 
					
						
							|  |  |  |               association => association.alias === part | 
					
						
							|  |  |  |             ); | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |             if (!assoc) return acc; | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |             tmpModel = findModelByAssoc({ assoc }); | 
					
						
							| 
									
										
										
										
											2019-07-29 18:00:01 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |             if (isPolymorphic({ assoc })) { | 
					
						
							|  |  |  |               const path = formatPolymorphicPopulate({ | 
					
						
							|  |  |  |                 assoc, | 
					
						
							|  |  |  |                 path: assoc.alias, | 
					
						
							|  |  |  |                 prefix, | 
					
						
							|  |  |  |               }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |               return _.extend(acc, path); | 
					
						
							| 
									
										
										
										
											2019-07-29 18:00:01 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |             newKey = `${prefix}${part}`; | 
					
						
							|  |  |  |             prefix = `${newKey}.`; | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2019-07-29 18:00:01 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |           acc[newKey] = obj[key]; | 
					
						
							|  |  |  |           return acc; | 
					
						
							|  |  |  |         }, {}); | 
					
						
							| 
									
										
										
										
											2019-07-29 18:00:01 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |         return [finalObj]; | 
					
						
							|  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2019-07-29 18:00:01 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |       // Initialize lifecycle callbacks.
 | 
					
						
							|  |  |  |       loadedModel.initialize = function() { | 
					
						
							| 
									
										
										
										
											2019-08-07 02:34:03 -07:00
										 |  |  |         // Load bookshelf plugin arguments from model options
 | 
					
						
							|  |  |  |         this.constructor.__super__.initialize.apply(this, arguments); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |         const lifecycle = { | 
					
						
							|  |  |  |           creating: 'beforeCreate', | 
					
						
							|  |  |  |           created: 'afterCreate', | 
					
						
							|  |  |  |           destroying: 'beforeDestroy', | 
					
						
							|  |  |  |           destroyed: 'afterDestroy', | 
					
						
							|  |  |  |           updating: 'beforeUpdate', | 
					
						
							|  |  |  |           updated: 'afterUpdate', | 
					
						
							|  |  |  |           fetching: 'beforeFetch', | 
					
						
							|  |  |  |           'fetching:collection': 'beforeFetchAll', | 
					
						
							|  |  |  |           fetched: 'afterFetch', | 
					
						
							|  |  |  |           'fetched:collection': 'afterFetchAll', | 
					
						
							|  |  |  |           saving: 'beforeSave', | 
					
						
							|  |  |  |           saved: 'afterSave', | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         _.forEach(lifecycle, (fn, key) => { | 
					
						
							|  |  |  |           if (_.isFunction(target[model.toLowerCase()][fn])) { | 
					
						
							|  |  |  |             this.on(key, target[model.toLowerCase()][fn]); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |         // Update withRelated level to bypass many-to-many association for polymorphic relationshiips.
 | 
					
						
							|  |  |  |         // Apply only during fetching.
 | 
					
						
							|  |  |  |         this.on('fetching fetching:collection', (instance, attrs, options) => { | 
					
						
							| 
									
										
										
										
											2019-07-29 18:00:01 +02:00
										 |  |  |           // do not populate anything
 | 
					
						
							|  |  |  |           if (options.withRelated === false) return; | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |           if (options.isEager === true) return; | 
					
						
							| 
									
										
										
										
											2019-07-29 18:00:01 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |           if (_.isNil(options.withRelated)) { | 
					
						
							|  |  |  |             options.withRelated = [] | 
					
						
							|  |  |  |               .concat(createGroupsPopulate()) | 
					
						
							|  |  |  |               .concat(createAssociationPopulate()); | 
					
						
							| 
									
										
										
										
											2019-10-24 00:12:20 +02:00
										 |  |  |           } else if (_.isEmpty(options.withRelated)) { | 
					
						
							|  |  |  |             options.withRelated = createGroupsPopulate(); | 
					
						
							| 
									
										
										
										
											2019-07-30 11:22:44 +02:00
										 |  |  |           } else { | 
					
						
							| 
									
										
										
										
											2019-07-29 18:00:01 +02:00
										 |  |  |             options.withRelated = formatPopulateOptions(options.withRelated); | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return _.isFunction(target[model.toLowerCase()]['beforeFetchAll']) | 
					
						
							|  |  |  |             ? target[model.toLowerCase()]['beforeFetchAll'] | 
					
						
							|  |  |  |             : Promise.resolve(); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         //eslint-disable-next-line
 | 
					
						
							|  |  |  |         this.on('saving', (instance, attrs, options) => { | 
					
						
							|  |  |  |           instance.attributes = mapper(instance.attributes); | 
					
						
							|  |  |  |           attrs = mapper(attrs); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return _.isFunction(target[model.toLowerCase()]['beforeSave']) | 
					
						
							|  |  |  |             ? target[model.toLowerCase()]['beforeSave'] | 
					
						
							|  |  |  |             : Promise.resolve(); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Convert to JSON format stringify json for mysql database
 | 
					
						
							|  |  |  |         if (definition.client === 'mysql' || definition.client === 'sqlite3') { | 
					
						
							|  |  |  |           const events = [ | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               name: 'saved', | 
					
						
							|  |  |  |               target: 'afterSave', | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               name: 'fetched', | 
					
						
							|  |  |  |               target: 'afterFetch', | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               name: 'fetched:collection', | 
					
						
							|  |  |  |               target: 'afterFetchAll', | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |           ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 16:26:53 +02:00
										 |  |  |           const formatter = attributes => { | 
					
						
							| 
									
										
										
										
											2019-08-08 14:29:09 +02:00
										 |  |  |             Object.keys(attributes).forEach(key => { | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |               const attr = definition.attributes[key] || {}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-08 14:29:09 +02:00
										 |  |  |               if (attributes[key] === null) return; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |               if (attr.type === 'json') { | 
					
						
							|  |  |  |                 attributes[key] = JSON.parse(attributes[key]); | 
					
						
							|  |  |  |               } | 
					
						
							| 
									
										
										
										
											2019-07-16 16:26:53 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |               if (attr.type === 'boolean') { | 
					
						
							|  |  |  |                 if (typeof attributes[key] === 'boolean') { | 
					
						
							|  |  |  |                   return; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-08 14:29:09 +02:00
										 |  |  |                 const strVal = attributes[key].toString(); | 
					
						
							| 
									
										
										
										
											2019-07-16 16:26:53 +02:00
										 |  |  |                 if (strVal === '1') { | 
					
						
							|  |  |  |                   attributes[key] = true; | 
					
						
							|  |  |  |                 } else if (strVal === '0') { | 
					
						
							|  |  |  |                   attributes[key] = false; | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                   attributes[key] = null; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |               } | 
					
						
							| 
									
										
										
										
											2019-08-05 15:22:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-08 14:29:09 +02:00
										 |  |  |               if (attr.type === 'date' && definition.client === 'sqlite3') { | 
					
						
							| 
									
										
										
										
											2019-08-05 15:22:39 +02:00
										 |  |  |                 attributes[key] = dateFns.parse(attributes[key]); | 
					
						
							|  |  |  |               } | 
					
						
							| 
									
										
										
										
											2019-08-08 14:29:09 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |               if ( | 
					
						
							|  |  |  |                 attr.type === 'biginteger' && | 
					
						
							|  |  |  |                 definition.client === 'sqlite3' | 
					
						
							|  |  |  |               ) { | 
					
						
							|  |  |  |                 attributes[key] = attributes[key].toString(); | 
					
						
							|  |  |  |               } | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |             }); | 
					
						
							|  |  |  |           }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           events.forEach(event => { | 
					
						
							|  |  |  |             let fn; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (event.name.indexOf('collection') !== -1) { | 
					
						
							|  |  |  |               fn = instance => | 
					
						
							|  |  |  |                 instance.models.map(entry => { | 
					
						
							| 
									
										
										
										
											2019-07-16 16:26:53 +02:00
										 |  |  |                   formatter(entry.attributes); | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |                 }); | 
					
						
							|  |  |  |             } else { | 
					
						
							| 
									
										
										
										
											2019-07-16 16:26:53 +02:00
										 |  |  |               fn = instance => formatter(instance.attributes); | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             this.on(event.name, instance => { | 
					
						
							|  |  |  |               fn(instance); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |               return _.isFunction(target[model.toLowerCase()][event.target]) | 
					
						
							|  |  |  |                 ? target[model.toLowerCase()][event.target] | 
					
						
							|  |  |  |                 : Promise.resolve(); | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       loadedModel.hidden = _.keys( | 
					
						
							|  |  |  |         _.keyBy( | 
					
						
							|  |  |  |           _.filter(definition.attributes, (value, key) => { | 
					
						
							|  |  |  |             if ( | 
					
						
							| 
									
										
										
										
											2019-09-09 15:32:02 +02:00
										 |  |  |               _.has(value, 'columnName') && | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |               !_.isEmpty(value.columnName) && | 
					
						
							|  |  |  |               value.columnName !== key | 
					
						
							|  |  |  |             ) { | 
					
						
							|  |  |  |               return true; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           }), | 
					
						
							|  |  |  |           'columnName' | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       GLOBALS[definition.globalId] = ORM.Model.extend(loadedModel); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (!plugin) { | 
					
						
							|  |  |  |         // Only expose as real global variable the models which
 | 
					
						
							|  |  |  |         // are not scoped in a plugin.
 | 
					
						
							|  |  |  |         global[definition.globalId] = GLOBALS[definition.globalId]; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Expose ORM functions through the `strapi.models[xxx]`
 | 
					
						
							|  |  |  |       // or `strapi.plugins[xxx].models[yyy]` object.
 | 
					
						
							|  |  |  |       target[model] = _.assign(GLOBALS[definition.globalId], target[model]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Push attributes to be aware of model schema.
 | 
					
						
							|  |  |  |       target[model]._attributes = definition.attributes; | 
					
						
							|  |  |  |       target[model].updateRelations = relations.update; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 15:31:15 +02:00
										 |  |  |       await buildDatabaseSchema({ | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |         ORM, | 
					
						
							|  |  |  |         definition, | 
					
						
							|  |  |  |         loadedModel, | 
					
						
							|  |  |  |         connection, | 
					
						
							|  |  |  |         model: target[model], | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2019-07-16 15:31:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:05:24 +02:00
										 |  |  |       await createGroupJoinTables({ definition, ORM }); | 
					
						
							| 
									
										
										
										
											2019-06-03 21:00:03 +02:00
										 |  |  |     } catch (err) { | 
					
						
							|  |  |  |       strapi.log.error(`Impossible to register the '${model}' model.`); | 
					
						
							|  |  |  |       strapi.log.error(err); | 
					
						
							|  |  |  |       strapi.stop(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return Promise.all(updates); | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2019-08-05 15:22:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-27 16:26:09 +02:00
										 |  |  | const castValueFromType = (type, value /* definition */) => { | 
					
						
							| 
									
										
										
										
											2019-08-08 14:29:09 +02:00
										 |  |  |   // do not cast null values
 | 
					
						
							|  |  |  |   if (value === null) return null; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-05 15:22:39 +02:00
										 |  |  |   switch (type) { | 
					
						
							| 
									
										
										
										
											2019-09-13 10:26:54 +02:00
										 |  |  |     case 'json': | 
					
						
							|  |  |  |       return JSON.stringify(value); | 
					
						
							| 
									
										
										
										
											2019-08-05 15:22:39 +02:00
										 |  |  |     // TODO: handle real date format 1970-01-01
 | 
					
						
							|  |  |  |     // TODO: handle real time format 12:00:00
 | 
					
						
							|  |  |  |     case 'time': | 
					
						
							| 
									
										
										
										
											2019-08-08 12:34:40 +02:00
										 |  |  |     case 'timestamp': | 
					
						
							|  |  |  |     case 'date': | 
					
						
							|  |  |  |     case 'datetime': { | 
					
						
							| 
									
										
										
										
											2019-08-05 15:22:39 +02:00
										 |  |  |       const date = dateFns.parse(value); | 
					
						
							|  |  |  |       if (dateFns.isValid(date)) return date; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       date.setTime(value); | 
					
						
							| 
									
										
										
										
											2019-08-08 12:34:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-05 15:22:39 +02:00
										 |  |  |       if (!dateFns.isValid(date)) { | 
					
						
							| 
									
										
										
										
											2019-08-06 08:45:32 +02:00
										 |  |  |         throw new Error( | 
					
						
							|  |  |  |           `Invalid ${type} format, expected a timestamp or an ISO date` | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2019-08-05 15:22:39 +02:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return date; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     default: | 
					
						
							|  |  |  |       return value; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; |