2016-03-16 14:41:15 +01:00
'use strict' ;
/ * *
* Module dependencies
* /
2018-05-03 18:13:22 +02:00
// Public node modules.
const _ = require ( 'lodash' ) ;
2019-02-02 13:25:09 +01:00
const pluralize = require ( 'pluralize' ) ;
2018-05-03 18:13:22 +02:00
2018-12-06 18:11:53 +01:00
// Following this discussion https://stackoverflow.com/questions/18082/validate-decimal-numbers-in-javascript-isnumeric this function is the best implem to determine if a value is a valid number candidate
2019-04-09 12:09:03 +02:00
const isNumeric = value => {
2018-12-06 18:11:53 +01:00
return ! _ . isObject ( value ) && ! isNaN ( parseFloat ( value ) ) && isFinite ( value ) ;
} ;
2018-06-17 23:34:25 +02:00
2016-03-16 14:41:15 +01:00
/ *
* Set of utils for models
* /
module . exports = {
2016-07-05 14:13:35 +02:00
/ * *
* Initialize to prevent some mistakes
* /
initialize : cb => {
cb ( ) ;
} ,
2018-05-10 12:19:33 +02:00
/ * *
* Retrieve the value based on the primary key
* /
getValuePrimaryKey : ( value , defaultKey ) => {
return value [ defaultKey ] || value . id || value . _id ;
} ,
2016-03-16 14:41:15 +01:00
/ * *
* Find relation nature with verbose
* /
2020-03-19 16:46:27 +01:00
getNature : ( { attribute , attributeName , modelName } ) => {
const types = {
current : '' ,
other : '' ,
} ;
2016-04-19 17:29:19 +02:00
2020-03-19 16:46:27 +01:00
const models = attribute . plugin ? strapi . plugins [ attribute . plugin ] . models : strapi . models ;
2018-02-22 15:34:33 +01:00
2020-03-19 16:46:27 +01:00
const pluginModels = Object . values ( strapi . plugins ) . reduce ( ( acc , plugin ) => {
return acc . concat ( Object . values ( plugin . models ) ) ;
} , [ ] ) ;
2018-02-22 15:34:33 +01:00
2020-03-19 16:46:27 +01:00
const allModels = Object . values ( strapi . models ) . concat ( pluginModels ) ;
2018-02-22 15:34:33 +01:00
2020-03-19 16:46:27 +01:00
if (
( _ . has ( attribute , 'collection' ) && attribute . collection === '*' ) ||
( _ . has ( attribute , 'model' ) && attribute . model === '*' )
) {
if ( attribute . model ) {
types . current = 'morphToD' ;
} else {
types . current = 'morphTo' ;
}
2017-12-15 18:04:56 +01:00
2020-03-19 16:46:27 +01:00
// We have to find if they are a model linked to this key
_ . forEach ( allModels , model => {
_ . forIn ( model . attributes , attribute => {
if ( _ . has ( attribute , 'via' ) && attribute . via === attributeName ) {
if ( _ . has ( attribute , 'collection' ) && attribute . collection === modelName ) {
types . other = 'collection' ;
2019-10-28 17:04:29 +01:00
2020-03-19 16:46:27 +01:00
// Break loop
return false ;
} else if ( _ . has ( attribute , 'model' ) && attribute . model === modelName ) {
types . other = 'modelD' ;
2018-02-26 14:39:06 +01:00
2020-03-19 16:46:27 +01:00
// Break loop
return false ;
2016-03-16 14:41:15 +01:00
}
2020-03-19 16:46:27 +01:00
}
2018-02-12 18:54:34 +01:00
} ) ;
2020-03-19 16:46:27 +01:00
} ) ;
} else if ( _ . has ( attribute , 'via' ) && _ . has ( attribute , 'collection' ) ) {
const relatedAttribute = models [ attribute . collection ] . attributes [ attribute . via ] ;
if ( ! relatedAttribute ) {
throw new Error (
` The attribute \` ${ attribute . via } \` is missing in the model ${ _ . upperFirst (
attribute . collection
) } $ { attribute . plugin ? '(plugin - ' + attribute . plugin + ')' : '' } `
) ;
2017-12-15 18:04:56 +01:00
}
2016-03-16 14:41:15 +01:00
2020-03-19 16:46:27 +01:00
types . current = 'collection' ;
if (
_ . has ( relatedAttribute , 'collection' ) &&
relatedAttribute . collection !== '*' &&
_ . has ( relatedAttribute , 'via' )
2019-07-05 17:17:39 +02:00
) {
2020-03-19 16:46:27 +01:00
types . other = 'collection' ;
2019-07-05 17:17:39 +02:00
} else if (
2020-03-19 16:46:27 +01:00
_ . has ( relatedAttribute , 'collection' ) &&
relatedAttribute . collection !== '*' &&
! _ . has ( relatedAttribute , 'via' )
2019-07-05 17:17:39 +02:00
) {
2020-03-19 16:46:27 +01:00
types . other = 'collectionD' ;
} else if ( _ . has ( relatedAttribute , 'model' ) && relatedAttribute . model !== '*' ) {
types . other = 'model' ;
} else if ( _ . has ( relatedAttribute , 'collection' ) || _ . has ( relatedAttribute , 'model' ) ) {
types . other = 'morphTo' ;
}
} else if ( _ . has ( attribute , 'via' ) && _ . has ( attribute , 'model' ) ) {
types . current = 'modelD' ;
// We have to find if they are a model linked to this attributeName
const model = models [ attribute . model ] ;
const reverseAttribute = model . attributes [ attribute . via ] ;
if (
_ . has ( reverseAttribute , 'via' ) &&
reverseAttribute . via === attributeName &&
_ . has ( reverseAttribute , 'collection' ) &&
reverseAttribute . collection !== '*'
2019-04-09 12:09:03 +02:00
) {
2020-03-19 16:46:27 +01:00
types . other = 'collection' ;
} else if ( _ . has ( reverseAttribute , 'model' ) && reverseAttribute . model !== '*' ) {
types . other = 'model' ;
} else if ( _ . has ( reverseAttribute , 'collection' ) || _ . has ( reverseAttribute , 'model' ) ) {
types . other = 'morphTo' ;
2017-12-15 18:04:56 +01:00
}
2020-03-19 16:46:27 +01:00
} else if ( _ . has ( attribute , 'model' ) ) {
types . current = 'model' ;
// We have to find if they are a model linked to this attributeName
_ . forIn ( models , model => {
_ . forIn ( model . attributes , attribute => {
if ( _ . has ( attribute , 'via' ) && attribute . via === attributeName ) {
if ( _ . has ( attribute , 'collection' ) && attribute . collection === modelName ) {
types . other = 'collection' ;
// Break loop
return false ;
} else if ( _ . has ( attribute , 'model' ) && attribute . model === modelName ) {
types . other = 'modelD' ;
// Break loop
return false ;
}
}
} ) ;
} ) ;
} else if ( _ . has ( attribute , 'collection' ) ) {
types . current = 'collectionD' ;
// We have to find if they are a model linked to this attributeName
_ . forIn ( models , model => {
_ . forIn ( model . attributes , attribute => {
if ( _ . has ( attribute , 'via' ) && attribute . via === attributeName ) {
if ( _ . has ( attribute , 'collection' ) && attribute . collection === modelName ) {
types . other = 'collection' ;
// Break loop
return false ;
} else if ( _ . has ( attribute , 'model' ) && attribute . model === modelName ) {
types . other = 'modelD' ;
// Break loop
return false ;
}
}
} ) ;
} ) ;
}
2016-03-16 14:41:15 +01:00
2020-03-19 16:46:27 +01:00
if ( types . current === 'collection' && types . other === 'morphTo' ) {
return {
nature : 'manyToManyMorph' ,
verbose : 'morphMany' ,
} ;
} else if ( types . current === 'collection' && types . other === 'morphToD' ) {
return {
nature : 'manyToOneMorph' ,
verbose : 'morphMany' ,
} ;
} else if ( types . current === 'modelD' && types . other === 'morphTo' ) {
return {
nature : 'oneToManyMorph' ,
verbose : 'morphOne' ,
} ;
} else if ( types . current === 'modelD' && types . other === 'morphToD' ) {
return {
nature : 'oneToOneMorph' ,
verbose : 'morphOne' ,
} ;
} else if ( types . current === 'morphToD' && types . other === 'collection' ) {
return {
nature : 'oneMorphToMany' ,
verbose : 'belongsToMorph' ,
} ;
} else if ( types . current === 'morphToD' && types . other === 'model' ) {
return {
nature : 'oneMorphToOne' ,
verbose : 'belongsToMorph' ,
} ;
} else if (
types . current === 'morphTo' &&
( types . other === 'model' || _ . has ( attribute , 'model' ) )
) {
return {
nature : 'manyMorphToOne' ,
verbose : 'belongsToManyMorph' ,
} ;
} else if (
types . current === 'morphTo' &&
( types . other === 'collection' || _ . has ( attribute , 'collection' ) )
) {
return {
nature : 'manyMorphToMany' ,
verbose : 'belongsToManyMorph' ,
} ;
} else if ( types . current === 'modelD' && types . other === 'model' ) {
return {
nature : 'oneToOne' ,
verbose : 'belongsTo' ,
} ;
} else if ( types . current === 'model' && types . other === 'modelD' ) {
return {
nature : 'oneToOne' ,
verbose : 'hasOne' ,
} ;
} else if (
( types . current === 'model' || types . current === 'modelD' ) &&
types . other === 'collection'
) {
return {
nature : 'manyToOne' ,
verbose : 'belongsTo' ,
} ;
} else if ( types . current === 'modelD' && types . other === 'collection' ) {
return {
nature : 'oneToMany' ,
verbose : 'hasMany' ,
} ;
} else if ( types . current === 'collection' && types . other === 'model' ) {
return {
nature : 'oneToMany' ,
verbose : 'hasMany' ,
} ;
} else if ( types . current === 'collection' && types . other === 'collection' ) {
return {
nature : 'manyToMany' ,
verbose : 'belongsToMany' ,
} ;
} else if (
( types . current === 'collectionD' && types . other === 'collection' ) ||
( types . current === 'collection' && types . other === 'collectionD' )
) {
return {
nature : 'manyToMany' ,
verbose : 'belongsToMany' ,
} ;
} else if ( types . current === 'collectionD' && types . other === '' ) {
return {
nature : 'manyWay' ,
verbose : 'belongsToMany' ,
} ;
} else if ( types . current === 'model' && types . other === '' ) {
return {
nature : 'oneWay' ,
verbose : 'belongsTo' ,
} ;
2017-12-15 18:04:56 +01:00
}
2020-03-19 16:46:27 +01:00
return undefined ;
2016-03-16 14:41:15 +01:00
} ,
2019-02-02 13:25:09 +01:00
/ * *
* Return table name for a collection many - to - many
* /
getCollectionName : ( associationA , associationB ) => {
2020-03-27 15:51:23 +01:00
if ( associationA . dominant && _ . has ( associationA , 'collectionName' ) ) {
return associationA . collectionName ;
}
if ( associationB . dominant && _ . has ( associationB , 'collectionName' ) ) {
return associationB . collectionName ;
}
2019-02-02 13:25:09 +01:00
return [ associationA , associationB ]
2019-04-19 17:24:56 +02:00
. sort ( ( a , b ) => {
if ( a . collection === b . collection ) {
if ( a . dominant ) return 1 ;
else return - 1 ;
}
return a . collection < b . collection ? - 1 : 1 ;
} )
. map ( table =>
2020-03-19 16:46:27 +01:00
_ . snakeCase ( ` ${ pluralize . plural ( table . collection ) } ${ pluralize . plural ( table . via ) } ` )
2019-04-19 17:24:56 +02:00
)
2019-02-02 13:25:09 +01:00
. join ( '__' ) ;
} ,
2016-03-16 14:41:15 +01:00
/ * *
* Define associations key to models
* /
2019-04-09 12:09:03 +02:00
defineAssociations : function ( model , definition , association , key ) {
2017-12-16 18:26:04 +01:00
try {
// Initialize associations object
if ( definition . associations === undefined ) {
definition . associations = [ ] ;
}
2016-03-16 14:41:15 +01:00
2017-12-16 18:26:04 +01:00
// Exclude non-relational attribute
2019-07-05 17:17:39 +02:00
if ( ! _ . has ( association , 'collection' ) && ! _ . has ( association , 'model' ) ) {
return ;
2017-12-16 18:26:04 +01:00
}
2016-03-16 14:41:15 +01:00
2017-12-16 18:26:04 +01:00
// Get relation nature
2018-02-22 15:34:33 +01:00
let details ;
2019-07-05 17:17:39 +02:00
const targetName = association . model || association . collection || '' ;
2020-03-19 16:46:27 +01:00
const infos = this . getNature ( {
attribute : association ,
attributeName : key ,
modelName : model . toLowerCase ( ) ,
} ) ;
2018-02-22 15:34:33 +01:00
2019-07-05 17:17:39 +02:00
if ( targetName !== '*' ) {
if ( association . plugin ) {
details = _ . get (
strapi . plugins ,
2020-03-19 16:46:27 +01:00
[ association . plugin , 'models' , targetName , 'attributes' , association . via ] ,
2019-07-05 17:17:39 +02:00
{ }
) ;
} else {
2020-03-19 16:46:27 +01:00
details = _ . get ( strapi . models , [ targetName , 'attributes' , association . via ] , { } ) ;
2019-07-05 17:17:39 +02:00
}
2018-02-22 15:34:33 +01:00
}
2017-12-16 18:26:04 +01:00
// Build associations object
2019-07-05 17:17:39 +02:00
if ( _ . has ( association , 'collection' ) && association . collection !== '*' ) {
2019-02-02 13:25:09 +01:00
const ast = {
2017-12-16 18:26:04 +01:00
alias : key ,
type : 'collection' ,
collection : association . collection ,
via : association . via || undefined ,
nature : infos . nature ,
autoPopulate : _ . get ( association , 'autoPopulate' , true ) ,
dominant : details . dominant !== true ,
plugin : association . plugin || undefined ,
2018-02-22 16:08:11 +01:00
filter : details . filter ,
2019-02-02 13:25:09 +01:00
} ;
if ( infos . nature === 'manyToMany' && definition . orm === 'bookshelf' ) {
2020-03-27 15:51:23 +01:00
ast . tableCollectionName = this . getCollectionName ( details , association ) ;
2019-07-05 17:17:39 +02:00
}
if ( infos . nature === 'manyWay' && definition . orm === 'bookshelf' ) {
2020-03-27 15:51:23 +01:00
ast . tableCollectionName =
_ . get ( association , 'collectionName' ) ||
` ${ definition . collectionName } __ ${ _ . snakeCase ( key ) } ` ;
2019-02-02 13:25:09 +01:00
}
definition . associations . push ( ast ) ;
2019-07-05 17:17:39 +02:00
return ;
}
if ( _ . has ( association , 'model' ) && association . model !== '*' ) {
2017-12-16 18:26:04 +01:00
definition . associations . push ( {
alias : key ,
type : 'model' ,
model : association . model ,
via : association . via || undefined ,
nature : infos . nature ,
autoPopulate : _ . get ( association , 'autoPopulate' , true ) ,
dominant : details . dominant !== true ,
plugin : association . plugin || undefined ,
2018-02-22 16:08:11 +01:00
filter : details . filter ,
2018-02-12 18:54:34 +01:00
} ) ;
2019-07-05 17:17:39 +02:00
return ;
}
2020-03-19 16:46:27 +01:00
const pluginsModels = Object . keys ( strapi . plugins ) . reduce ( ( acc , current ) => {
Object . keys ( strapi . plugins [ current ] . models ) . forEach ( entity => {
Object . keys ( strapi . plugins [ current ] . models [ entity ] . attributes ) . forEach ( attribute => {
const attr = strapi . plugins [ current ] . models [ entity ] . attributes [ attribute ] ;
if ( ( attr . collection || attr . model || '' ) . toLowerCase ( ) === model . toLowerCase ( ) ) {
acc . push ( strapi . plugins [ current ] . models [ entity ] . globalId ) ;
}
2018-02-20 19:59:05 +01:00
} ) ;
2020-03-19 16:46:27 +01:00
} ) ;
2018-02-20 19:59:05 +01:00
2020-03-19 16:46:27 +01:00
return acc ;
} , [ ] ) ;
2018-02-20 19:59:05 +01:00
2019-07-05 17:17:39 +02:00
const appModels = Object . keys ( strapi . models ) . reduce ( ( acc , entity ) => {
Object . keys ( strapi . models [ entity ] . attributes ) . forEach ( attribute => {
const attr = strapi . models [ entity ] . attributes [ attribute ] ;
2020-03-19 16:46:27 +01:00
if ( ( attr . collection || attr . model || '' ) . toLowerCase ( ) === model . toLowerCase ( ) ) {
2019-07-05 17:17:39 +02:00
acc . push ( strapi . models [ entity ] . globalId ) ;
}
2017-12-16 18:26:04 +01:00
} ) ;
2019-07-05 17:17:39 +02:00
return acc ;
} , [ ] ) ;
2020-03-19 16:46:27 +01:00
const componentModels = Object . keys ( strapi . components ) . reduce ( ( acc , entity ) => {
Object . keys ( strapi . components [ entity ] . attributes ) . forEach ( attribute => {
const attr = strapi . components [ entity ] . attributes [ attribute ] ;
2019-07-18 10:55:13 +02:00
2020-03-19 16:46:27 +01:00
if ( ( attr . collection || attr . model || '' ) . toLowerCase ( ) === model . toLowerCase ( ) ) {
acc . push ( strapi . components [ entity ] . globalId ) ;
}
} ) ;
2019-07-18 10:55:13 +02:00
2020-03-19 16:46:27 +01:00
return acc ;
} , [ ] ) ;
const models = _ . uniq ( appModels . concat ( pluginsModels ) . concat ( componentModels ) ) ;
2019-07-05 17:17:39 +02:00
definition . associations . push ( {
alias : key ,
type : association . model ? 'model' : 'collection' ,
related : models ,
nature : infos . nature ,
autoPopulate : _ . get ( association , 'autoPopulate' , true ) ,
filter : association . filter ,
} ) ;
2017-12-16 18:26:04 +01:00
} catch ( e ) {
2019-07-05 17:17:39 +02:00
strapi . log . error (
2020-03-19 16:46:27 +01:00
` Something went wrong in the model \` ${ _ . upperFirst ( model ) } \` with the attribute \` ${ key } \` `
2019-07-05 17:17:39 +02:00
) ;
2018-02-21 17:33:30 +01:00
strapi . log . error ( e ) ;
2017-12-16 18:26:04 +01:00
strapi . stop ( ) ;
2016-03-16 14:41:15 +01:00
}
2016-08-08 11:12:09 +02:00
} ,
2018-12-06 18:11:53 +01:00
convertParams : ( entity , params ) => {
2017-09-13 10:30:37 +02:00
if ( ! entity ) {
2019-07-05 17:17:39 +02:00
throw new Error (
"You can't call the convert params method without passing the model's name as a first argument."
) ;
2017-09-12 17:58:31 +02:00
}
2018-06-04 17:46:08 +02:00
// Remove the source params (that can be sent from the ctm plugin) since it is not a filter
if ( params . source ) {
delete params . source ;
}
2018-12-06 18:11:53 +01:00
const model = entity . toLowerCase ( ) ;
2017-11-20 14:35:24 +01:00
2019-04-09 12:09:03 +02:00
const models = _ . assign (
_ . clone ( strapi . models ) ,
_ . clone ( strapi . admin . models ) ,
Object . keys ( strapi . plugins ) . reduce ( ( acc , current ) => {
_ . assign ( acc , _ . get ( strapi . plugins [ current ] , [ 'models' ] , { } ) ) ;
return acc ;
2019-07-05 17:17:39 +02:00
} , { } )
2019-04-09 12:09:03 +02:00
) ;
2017-09-13 10:30:37 +02:00
2019-08-23 14:13:19 +02:00
if ( ! _ . has ( models , model ) ) {
2018-12-06 18:11:53 +01:00
return this . log . error ( ` The model ${ model } can't be found. ` ) ;
2017-09-13 10:30:37 +02:00
}
2018-12-06 18:11:53 +01:00
const client = models [ model ] . client ;
const connector = models [ model ] . orm ;
2017-09-13 10:30:37 +02:00
2018-12-06 18:11:53 +01:00
if ( ! connector ) {
2020-03-19 16:46:27 +01:00
throw new Error ( ` Impossible to determine the ORM used for the model ${ model } . ` ) ;
2018-12-06 18:11:53 +01:00
}
2017-09-13 10:30:37 +02:00
2019-09-20 12:44:24 +02:00
const convertor = strapi . db . connectors . get ( connector ) . getQueryParams ;
2018-12-06 18:11:53 +01:00
const convertParams = {
where : { } ,
sort : '' ,
start : 0 ,
2019-04-09 12:09:03 +02:00
limit : 100 ,
2017-09-12 17:58:31 +02:00
} ;
2019-04-09 12:09:03 +02:00
_ . forEach ( params , ( value , key ) => {
2018-12-06 18:11:53 +01:00
let result ;
let formattedValue ;
let modelAttributes = models [ model ] [ 'attributes' ] ;
let fieldType ;
// Get the field type to later check if it's a string before number conversion
if ( modelAttributes [ key ] ) {
fieldType = modelAttributes [ key ] [ 'type' ] ;
} else {
// Remove the filter keyword at the end
2019-04-09 12:09:03 +02:00
let splitKey = key . split ( '_' ) . slice ( 0 , - 1 ) ;
2018-12-06 18:11:53 +01:00
splitKey = splitKey . join ( '_' ) ;
if ( modelAttributes [ splitKey ] ) {
fieldType = modelAttributes [ splitKey ] [ 'type' ] ;
2018-07-08 09:33:46 -04:00
}
}
2018-12-06 18:11:53 +01:00
// Check if the value is a valid candidate to be converted to a number value
if ( fieldType !== 'string' ) {
2019-04-09 12:09:03 +02:00
formattedValue = isNumeric ( value ) ? _ . toNumber ( value ) : value ;
2018-12-06 18:11:53 +01:00
} else {
formattedValue = value ;
2017-09-12 17:58:31 +02:00
}
2018-09-24 21:20:30 +02:00
2018-12-06 19:12:31 +01:00
if ( _ . includes ( [ '_start' , '_limit' , '_populate' ] , key ) ) {
2018-12-06 18:11:53 +01:00
result = convertor ( formattedValue , key ) ;
} else if ( key === '_sort' ) {
const [ attr , order = 'ASC' ] = formattedValue . split ( ':' ) ;
result = convertor ( order , key , attr ) ;
} else {
const suffix = key . split ( '_' ) ;
// Mysql stores boolean as 1 or 0
2019-07-05 17:17:39 +02:00
if (
client === 'mysql' &&
_ . get ( models , [ model , 'attributes' , suffix , 'type' ] ) === 'boolean'
) {
2019-01-24 11:56:21 +01:00
formattedValue = value . toString ( ) === 'true' ? '1' : '0' ;
2017-09-12 17:58:31 +02:00
}
2018-12-06 18:11:53 +01:00
let type ;
2017-09-12 17:58:31 +02:00
2019-07-05 17:17:39 +02:00
if (
_ . includes (
2020-03-19 16:46:27 +01:00
[ 'ne' , 'lt' , 'gt' , 'lte' , 'gte' , 'contains' , 'containss' , 'in' , 'nin' ] ,
2019-07-05 17:17:39 +02:00
_ . last ( suffix )
)
) {
2018-12-06 18:11:53 +01:00
type = ` _ ${ _ . last ( suffix ) } ` ;
key = _ . dropRight ( suffix ) . join ( '_' ) ;
2018-10-09 01:10:15 +02:00
} else {
2018-12-06 18:11:53 +01:00
type = '=' ;
2018-10-09 01:10:15 +02:00
}
2018-12-06 18:11:53 +01:00
result = convertor ( formattedValue , type , key ) ;
}
2017-09-12 17:58:31 +02:00
2018-12-06 18:11:53 +01:00
_ . set ( convertParams , result . key , result . value ) ;
} ) ;
return convertParams ;
2019-04-09 12:09:03 +02:00
} ,
2016-03-16 14:41:15 +01:00
} ;