2019-09-20 12:44:24 +02:00
//TODO: move to dbal
2019-03-13 19:27:18 +01:00
const _ = require ( 'lodash' ) ;
2019-12-04 12:17:45 +01:00
const parseType = require ( './parse-type' ) ;
2019-03-13 19:27:18 +01:00
2019-03-22 12:16:09 +01:00
const findModelByAssoc = assoc => {
const { models } = assoc . plugin ? strapi . plugins [ assoc . plugin ] : strapi ;
return models [ assoc . collection || assoc . model ] ;
} ;
2019-07-18 19:28:52 +02:00
const isAttribute = ( model , field ) =>
2019-11-26 17:01:58 +01:00
_ . has ( model . allAttributes , field ) ||
model . primaryKey === field ||
field === 'id' ;
2019-03-22 12:16:09 +01:00
2019-03-28 17:12:43 +01:00
/ * *
* Returns the model , attribute name and association from a path of relation
* @ param { Object } options - Options
* @ param { string } options . model - Strapi model
2020-01-07 12:51:21 +01:00
* @ param { string } options . field - path of relation / attribute
2019-03-28 17:12:43 +01:00
* /
const getAssociationFromFieldKey = ( { model , field } ) => {
2019-03-13 19:27:18 +01:00
const fieldParts = field . split ( '.' ) ;
2019-03-22 12:16:09 +01:00
let tmpModel = model ;
2019-03-28 17:12:43 +01:00
let association ;
let attribute ;
2019-03-22 12:16:09 +01:00
for ( let i = 0 ; i < fieldParts . length ; i ++ ) {
2019-03-28 17:12:43 +01:00
const part = fieldParts [ i ] ;
attribute = part ;
2019-03-22 12:16:09 +01:00
2019-03-28 17:12:43 +01:00
const assoc = tmpModel . associations . find ( ast => ast . alias === part ) ;
2019-03-22 12:16:09 +01:00
if ( assoc ) {
2019-03-28 17:12:43 +01:00
association = assoc ;
2019-03-22 12:16:09 +01:00
tmpModel = findModelByAssoc ( assoc ) ;
continue ;
}
2019-07-18 19:28:52 +02:00
if (
! assoc &&
( ! isAttribute ( tmpModel , part ) || i !== fieldParts . length - 1 )
) {
2019-03-28 17:12:43 +01:00
const err = new Error (
` Your filters contain a field ' ${ field } ' that doesn't appear on your model definition nor it's relations `
) ;
err . status = 400 ;
throw err ;
2019-03-13 19:27:18 +01:00
}
2019-03-22 12:16:09 +01:00
}
2019-03-13 19:27:18 +01:00
2019-03-28 17:12:43 +01:00
return {
association ,
model : tmpModel ,
attribute ,
} ;
} ;
/ * *
2019-12-04 12:17:45 +01:00
* Cast an input value
2019-03-28 17:12:43 +01:00
* @ param { Object } options - Options
* @ param { string } options . type - type of the atribute
* @ param { * } options . value - value tu cast
2019-12-04 12:17:45 +01:00
* @ param { string } options . operator - name of operator
2019-03-28 17:12:43 +01:00
* /
2019-12-04 12:17:45 +01:00
const castInput = ( { type , value , operator } ) => {
return Array . isArray ( value )
? value . map ( val => castValue ( { type , operator , value : val } ) )
: castValue ( { type , operator , value : value } ) ;
2019-03-13 19:27:18 +01:00
} ;
2019-07-04 19:10:17 -03:00
/ * *
* Cast basic values based on attribute type
* @ param { Object } options - Options
* @ param { string } options . type - type of the atribute
* @ param { * } options . value - value tu cast
* @ param { string } options . operator - name of operator
* /
2019-07-18 19:28:52 +02:00
const castValue = ( { type , value , operator } ) => {
2019-12-04 12:17:45 +01:00
if ( operator === 'null' ) return parseType ( { type : 'boolean' , value } ) ;
return parseType ( { type , value } ) ;
2019-07-18 19:28:52 +02:00
} ;
2020-01-07 12:51:21 +01:00
/ * *
*
* @ param { Object } options - Options
* @ param { string } options . model - The model
* @ param { string } options . field - path of relation / attribute
* /
const normalizeFieldName = ( { model , field } ) => {
const fieldPath = field . split ( '.' ) ;
return _ . last ( fieldPath ) === 'id'
? _ . initial ( fieldPath )
. concat ( model . primaryKey )
. join ( '.' )
: fieldPath . join ( '.' ) ;
} ;
2019-03-26 14:57:02 +01:00
/ * *
*
* @ param { Object } options - Options
* @ param { Object } options . model - The model for which the query will be built
* @ param { Object } options . filters - The filters for the query ( start , sort , limit , and where clauses )
* @ param { Object } options . rest - In case the database layer requires any other params pass them
* /
2019-03-28 17:12:43 +01:00
const buildQuery = ( { model , filters = { } , ... rest } ) => {
2019-03-26 14:57:02 +01:00
// Validate query clauses
2019-03-13 19:27:18 +01:00
if ( filters . where && Array . isArray ( filters . where ) ) {
2019-07-18 19:28:52 +02:00
const deepFilters = filters . where . filter (
( { field } ) => field . split ( '.' ) . length > 1
) ;
2019-03-26 14:57:02 +01:00
if ( deepFilters . length > 0 ) {
strapi . log . warn (
'Deep filtering queries should be used carefully (e.g Can cause performance issues).\nWhen possible build custom routes which will in most case be more optimised.'
) ;
}
2019-03-28 17:12:43 +01:00
// cast where clauses to match the inner types
filters . where = filters . where
2019-04-23 13:18:09 +02:00
. filter ( ( { value } ) => ! _ . isNil ( value ) )
2019-03-28 17:12:43 +01:00
. map ( ( { field , operator , value } ) => {
const { model : assocModel , attribute } = getAssociationFromFieldKey ( {
model ,
field ,
} ) ;
2019-03-22 12:16:09 +01:00
2019-12-04 16:10:22 +01:00
const { type } = _ . get ( assocModel , [ 'allAttributes' , attribute ] , { } ) ;
2019-04-25 18:18:54 +02:00
// cast value or array of values
2019-12-04 12:17:45 +01:00
const castedValue = castInput ( { type , operator , value } ) ;
2019-04-25 18:18:54 +02:00
2019-11-26 17:01:58 +01:00
return {
2020-01-07 12:51:21 +01:00
field : normalizeFieldName ( { model , field } ) ,
2019-11-26 17:01:58 +01:00
operator ,
value : castedValue ,
} ;
2019-03-28 17:12:43 +01:00
} ) ;
2019-03-13 19:27:18 +01:00
}
2019-03-26 14:57:02 +01:00
// call the orm's buildQuery implementation
2019-09-20 12:44:24 +02:00
return strapi . db . connectors
. get ( model . orm )
. buildQuery ( { model , filters , ... rest } ) ;
2019-03-13 19:27:18 +01:00
} ;
module . exports = buildQuery ;