2016-03-18 11:12:50 +01:00
'use strict' ;
/ * *
* Module dependencies
* /
2017-01-30 15:04:21 +01:00
// Core
const path = require ( 'path' ) ;
2016-03-18 11:12:50 +01:00
// Public node modules.
const _ = require ( 'lodash' ) ;
const bookshelf = require ( 'bookshelf' ) ;
const pluralize = require ( 'pluralize' ) ;
2018-05-21 17:22:25 +07:00
// Strapi helpers for models.
const utilsModels = require ( 'strapi-utils' ) . models ;
2018-05-04 17:29:44 +02:00
// Local helpers.
const utils = require ( './utils/' ) ;
2018-05-16 18:17:13 +02:00
const relations = require ( './relations' ) ;
2018-05-04 17:29:44 +02:00
2017-02-15 14:43:09 +01:00
const PIVOT _PREFIX = '_pivot_' ;
2017-12-13 11:38:38 +01:00
const GLOBALS = { } ;
2017-02-15 14:43:09 +01:00
2016-03-18 11:12:50 +01:00
/ * *
* Bookshelf hook
* /
2017-04-11 11:56:31 +02:00
module . exports = function ( strapi ) {
2018-05-09 16:08:58 +02:00
const hook = _ . merge ( {
2016-03-18 11:12:50 +01:00
/ * *
* Default options
* /
defaults : {
2017-01-04 17:21:24 +01:00
defaultConnection : 'default' ,
host : 'localhost'
2016-03-18 11:12:50 +01:00
} ,
/ * *
* Initialize the hook
* /
2018-04-23 18:58:48 +02:00
initialize : async cb => {
2018-07-11 16:23:14 +02:00
const connections = _ . pickBy ( strapi . config . connections , { connector : 'strapi-hook-bookshelf' } ) ;
2017-07-24 19:58:03 +02:00
2018-04-23 18:58:48 +02:00
const databaseUpdate = [ ] ;
2018-01-31 19:22:52 +05:30
_ . forEach ( connections , ( connection , connectionName ) => {
2017-07-24 19:58:03 +02:00
// Apply defaults
2017-07-26 18:53:48 +02:00
_ . defaults ( connection . settings , strapi . config . hook . settings . bookshelf ) ;
2017-07-24 19:58:03 +02:00
// Create Bookshelf instance for this connection.
const ORM = new bookshelf ( strapi . connections [ connectionName ] ) ;
try {
// Require `config/functions/bookshelf.js` file to customize connection.
require ( path . resolve (
strapi . config . appPath ,
'config' ,
'functions' ,
'bookshelf.js'
2018-12-12 12:35:16 -03:00
) ) ( ORM , connection ) ;
2017-07-24 19:58:03 +02:00
} catch ( err ) {
// This is not an error if the file is not found.
2016-03-18 11:12:50 +01:00
}
2017-07-24 19:58:03 +02:00
// Load plugins
2017-12-13 11:38:38 +01:00
if ( _ . get ( connection , 'options.plugins' , true ) !== false ) {
2017-07-24 19:58:03 +02:00
ORM . plugin ( 'visibility' ) ;
ORM . plugin ( 'pagination' ) ;
}
2017-11-15 17:27:07 +01:00
const mountModels = ( models , target , plugin = false ) => {
2018-03-14 16:56:12 +01:00
// Parse every authenticated model.
2017-11-15 16:59:12 +01:00
_ . forEach ( models , ( definition , model ) => {
2017-12-12 16:14:38 +01:00
definition . globalName = _ . upperFirst ( _ . camelCase ( definition . globalId ) ) ;
2017-11-15 16:59:12 +01:00
2017-12-13 11:38:38 +01:00
// Define local GLOBALS to expose every models in this file.
GLOBALS [ definition . globalId ] = { } ;
// Add some informations about ORM & client connection & tableName
2017-11-15 16:59:12 +01:00
definition . orm = 'bookshelf' ;
definition . client = _ . get ( connection . settings , 'client' ) ;
2018-05-21 17:22:25 +07:00
_ . defaults ( definition , {
primaryKey : 'id' ,
2018-05-22 10:35:25 +07:00
primaryKeyType : _ . get ( definition , 'options.idAttributeType' , 'integer' )
2018-05-21 17:22:25 +07:00
} ) ;
2019-01-23 15:19:34 +01:00
// 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 ) ;
}
2017-11-15 16:59:12 +01:00
// Register the final model for Bookshelf.
2017-12-13 11:38:38 +01:00
const loadedModel = _ . assign ( {
2018-05-04 17:29:44 +02:00
tableName : definition . collectionName ,
2018-12-28 17:04:36 +05:30
hasTimestamps : _ . get ( definition , 'options.timestamps' , false ) ,
2018-05-04 17:29:44 +02:00
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 ;
}
2018-04-06 17:49:08 +02:00
2018-05-04 17:29:44 +02:00
return acc ;
} , { } )
} , definition . options ) ;
2019-01-23 15:19:34 +01:00
2017-11-15 16:59:12 +01:00
if ( _ . isString ( _ . get ( connection , 'options.pivot_prefix' ) ) ) {
loadedModel . toJSON = function ( options = { } ) {
const { shallow = false , omitPivot = false } = options ;
const attributes = this . serialize ( options ) ;
2016-03-18 11:12:50 +01:00
2017-11-15 16:59:12 +01:00
if ( ! shallow ) {
2017-12-13 11:38:38 +01:00
const pivot = this . pivot && ! omitPivot && this . pivot . attributes ;
2017-02-14 14:34:01 +01:00
2017-11-15 16:59:12 +01:00
// Remove pivot attributes with prefix.
2017-12-13 11:38:38 +01:00
_ . keys ( pivot ) . forEach ( key => delete attributes [ ` ${ PIVOT _PREFIX } ${ key } ` ] ) ;
2017-07-24 19:58:03 +02:00
2017-11-15 16:59:12 +01:00
// Add pivot attributes without prefix.
2017-12-13 11:38:38 +01:00
const pivotAttributes = _ . mapKeys ( pivot , ( value , key ) => ` ${ connection . options . pivot _prefix } ${ key } ` ) ;
2016-03-18 11:12:50 +01:00
2017-11-15 16:59:12 +01:00
return Object . assign ( { } , attributes , pivotAttributes ) ;
}
2016-03-22 15:50:33 +01:00
2017-11-15 16:59:12 +01:00
return attributes ;
} ;
}
2016-03-22 15:50:33 +01:00
2017-11-15 16:59:12 +01:00
// Initialize the global variable with the
// capitalized model name.
2017-11-15 17:27:07 +01:00
if ( ! plugin ) {
2017-12-12 16:14:38 +01:00
global [ definition . globalName ] = { } ;
2017-11-15 17:27:07 +01:00
}
2017-11-15 16:59:12 +01:00
// Call this callback function after we are done parsing
// all attributes for relationships-- see below.
const done = _ . after ( _ . size ( definition . attributes ) , ( ) => {
try {
// External function to map key that has been updated with `columnName`
2018-06-20 20:47:45 +02:00
const mapper = ( params = { } ) => {
2019-01-03 23:29:30 +01:00
if ( definition . client === 'mysql' || definition . client === 'sqlite3' ) {
2018-06-20 20:47:45 +02:00
Object . keys ( params ) . map ( ( key ) => {
const attr = definition . attributes [ key ] || { } ;
if ( attr . type === 'json' ) {
params [ key ] = JSON . stringify ( params [ key ] ) ;
}
} ) ;
}
return _ . mapKeys ( params , ( value , key ) => {
2017-11-15 16:59:12 +01:00
const attr = definition . attributes [ key ] || { } ;
return _ . isPlainObject ( attr ) &&
_ . isString ( attr [ 'columnName' ] )
? attr [ 'columnName' ]
: key ;
} ) ;
2018-06-20 20:47:45 +02:00
} ;
2017-11-15 16:59:12 +01:00
2018-02-21 16:35:25 +01:00
// Update serialize to reformat data for polymorphic associations.
loadedModel . serialize = function ( options ) {
const attrs = _ . clone ( this . attributes ) ;
2018-02-20 19:59:05 +01:00
2018-02-21 16:35:25 +01:00
if ( options && options . shallow ) {
return attrs ;
}
2018-02-20 19:59:05 +01:00
2018-02-21 16:35:25 +01:00
const relations = this . relations ;
2018-02-28 18:10:30 +01: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 ) ;
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 ] = attrs [ association . alias ] [ model . collectionName ] ;
break ;
case 'manyToManyMorph' :
attrs [ association . alias ] = attrs [ association . alias ] . map ( rel => rel [ model . collectionName ] ) ;
break ;
case 'oneMorphToOne' :
attrs [ association . alias ] = attrs [ association . alias ] . related ;
break ;
case 'manyMorphToOne' :
2018-04-11 12:53:07 +02:00
case 'manyMorphToMany' :
2018-02-28 18:10:30 +01:00
attrs [ association . alias ] = attrs [ association . alias ] . map ( obj => obj . related ) ;
break ;
default :
2018-02-20 19:59:05 +01:00
}
2018-02-28 18:10:30 +01:00
}
} ) ;
associations . map ( association => {
const relation = relations [ association . alias ] ;
if ( relation ) {
// Extract raw JSON data.
attrs [ association . alias ] = relation . toJSON ? relation . toJSON ( options ) : relation ;
}
} ) ;
2018-02-20 19:59:05 +01:00
2018-02-21 16:35:25 +01:00
return attrs ;
2018-05-04 17:29:44 +02:00
} ;
2018-02-20 19:59:05 +01:00
2017-11-15 16:59:12 +01:00
// Initialize lifecycle callbacks.
loadedModel . initialize = function ( ) {
const lifecycle = {
creating : 'beforeCreate' ,
created : 'afterCreate' ,
destroying : 'beforeDestroy' ,
destroyed : 'afterDestroy' ,
updating : 'beforeUpdate' ,
updated : 'afterUpdate' ,
fetching : 'beforeFetch' ,
2019-03-12 17:23:38 +01:00
'fetching:collection' : 'beforeFetchAll' ,
2017-11-15 16:59:12 +01:00
fetched : 'afterFetch' ,
2019-03-12 17:23:38 +01:00
'fetched:collection' : 'afterFetchAll' ,
2017-11-15 16:59:12 +01:00
saving : 'beforeSave' ,
saved : 'afterSave'
} ;
2018-02-21 16:35:25 +01:00
_ . forEach ( lifecycle , ( fn , key ) => {
if ( _ . isFunction ( target [ model . toLowerCase ( ) ] [ fn ] ) ) {
this . on ( key , target [ model . toLowerCase ( ) ] [ fn ] ) ;
}
} ) ;
// Update withRelated level to bypass many-to-many association for polymorphic relationshiips.
// Apply only during fetching.
2018-03-01 17:39:31 +01:00
this . on ( 'fetching fetching:collection' , ( instance , attrs , options ) => {
2018-02-21 16:35:25 +01:00
if ( _ . isArray ( options . withRelated ) ) {
2018-03-01 17:39:31 +01:00
options . withRelated = options . withRelated . map ( path => {
const association = definition . associations
. filter ( association => association . nature . toLowerCase ( ) . indexOf ( 'morph' ) !== - 1 )
. filter ( association => association . alias === path || association . via === path ) [ 0 ] ;
2018-05-02 16:15:36 +02:00
2018-03-01 17:39:31 +01:00
if ( association ) {
// Override on polymorphic path only.
if ( _ . isString ( path ) && path === association . via ) {
return ` related. ${ association . via } ` ;
} else if ( _ . isString ( path ) && path === association . alias ) {
// MorphTo side.
if ( association . related ) {
return ` ${ association . alias } .related ` ;
2018-02-21 16:35:25 +01:00
}
2018-03-01 17:39:31 +01:00
// oneToMorph or manyToMorph side.
// Retrieve collection name because we are using it to build our hidden model.
const model = association . plugin ?
strapi . plugins [ association . plugin ] . models [ association . collection || association . model ] :
strapi . models [ association . collection || association . model ] ;
2018-05-02 16:15:36 +02:00
return {
[ ` ${ association . alias } . ${ model . collectionName } ` ] : function ( query ) {
query . orderBy ( 'created_at' , 'desc' ) ;
}
} ;
2018-02-20 19:59:05 +01:00
}
2018-03-01 17:39:31 +01:00
}
2018-02-20 19:59:05 +01:00
2018-03-01 17:39:31 +01:00
return path ;
} ) ;
}
return _ . isFunction (
2019-03-12 17:23:38 +01:00
target [ model . toLowerCase ( ) ] [ 'beforeFetchAll' ]
2018-03-01 17:39:31 +01:00
)
2019-03-12 17:23:38 +01:00
? target [ model . toLowerCase ( ) ] [ 'beforeFetchAll' ]
2018-03-01 17:39:31 +01:00
: Promise . resolve ( ) ;
} ) ;
2017-11-15 16:59:12 +01:00
2018-06-11 20:16:17 +07:00
this . on ( 'saving' , ( instance , attrs , options ) => { //eslint-disable-line
2017-11-15 16:59:12 +01:00
instance . attributes = mapper ( instance . attributes ) ;
attrs = mapper ( attrs ) ;
return _ . isFunction (
2017-11-15 17:27:07 +01:00
target [ model . toLowerCase ( ) ] [ 'beforeSave' ]
2017-11-15 16:59:12 +01:00
)
2017-11-15 17:27:07 +01:00
? target [ model . toLowerCase ( ) ] [ 'beforeSave' ]
2017-11-15 16:59:12 +01:00
: Promise . resolve ( ) ;
} ) ;
2018-06-20 16:21:09 +02:00
// Convert to JSON format stringify json for mysql database
2019-01-03 23:29:30 +01:00
if ( definition . client === 'mysql' || definition . client === 'sqlite3' ) {
2018-06-20 20:47:45 +02:00
const events = [ {
name : 'saved' ,
target : 'afterSave'
} , {
name : 'fetched' ,
target : 'afterFetch'
} , {
name : 'fetched:collection' ,
2019-03-12 17:23:38 +01:00
target : 'afterFetchAll'
2018-06-20 20:47:45 +02:00
} ] ;
2018-06-20 16:21:09 +02:00
const jsonFormatter = ( attributes ) => {
Object . keys ( attributes ) . map ( ( key ) => {
const attr = definition . attributes [ key ] || { } ;
if ( attr . type === 'json' ) {
attributes [ key ] = JSON . parse ( attributes [ key ] ) ;
}
} ) ;
} ;
2018-06-20 20:47:45 +02:00
events . forEach ( ( event ) => {
2018-06-26 14:18:31 +02:00
let fn ;
if ( event . name . indexOf ( 'collection' ) !== - 1 ) {
fn = ( instance ) => instance . models . map ( ( entry ) => {
jsonFormatter ( entry . attributes ) ;
} ) ;
} else {
fn = ( instance ) => jsonFormatter ( instance . attributes ) ;
}
2018-06-20 20:47:45 +02:00
this . on ( event . name , ( instance ) => {
2018-06-26 14:18:31 +02:00
fn ( instance ) ;
2018-06-20 16:21:09 +02:00
2018-06-20 20:47:45 +02:00
return _ . isFunction (
target [ model . toLowerCase ( ) ] [ event . target ]
)
? target [ model . toLowerCase ( ) ] [ event . target ]
: Promise . resolve ( ) ;
2018-06-20 16:21:09 +02:00
} ) ;
} ) ;
}
2017-11-15 16:59:12 +01:00
} ;
2017-07-24 19:58:03 +02:00
2017-11-15 16:59:12 +01:00
loadedModel . hidden = _ . keys (
_ . keyBy (
_ . filter ( definition . attributes , ( value , key ) => {
if (
value . hasOwnProperty ( 'columnName' ) &&
! _ . isEmpty ( value . columnName ) &&
value . columnName !== key
) {
return true ;
}
} ) ,
'columnName'
2017-07-24 19:58:03 +02:00
)
2017-11-15 16:59:12 +01:00
) ;
2017-07-24 19:58:03 +02:00
2017-12-13 11:38:38 +01:00
GLOBALS [ definition . globalId ] = ORM . Model . extend ( loadedModel ) ;
2017-01-24 15:55:50 +01:00
2017-12-13 11:38:38 +01:00
if ( ! plugin ) {
// Only expose as real global variable the models which
// are not scoped in a plugin.
global [ definition . globalId ] = GLOBALS [ definition . globalId ] ;
2017-11-15 17:27:07 +01:00
}
2017-01-04 17:21:24 +01:00
2017-12-13 11:38:38 +01:00
// Expose ORM functions through the `strapi.models[xxx]`
// or `strapi.plugins[xxx].models[yyy]` object.
target [ model ] = _ . assign ( GLOBALS [ definition . globalId ] , target [ model ] ) ;
2017-11-15 16:59:12 +01:00
// Push attributes to be aware of model schema.
2017-11-15 17:27:07 +01:00
target [ model ] . _attributes = definition . attributes ;
2018-05-09 16:08:58 +02:00
target [ model ] . updateRelations = relations . update ;
2017-02-15 14:43:09 +01:00
2018-04-23 18:58:48 +02:00
databaseUpdate . push ( new Promise ( async ( resolve ) => {
2018-04-24 15:34:26 +02:00
// Equilize database tables
const handler = async ( table , attributes ) => {
const tableExist = await ORM . knex . schema . hasTable ( table ) ;
2019-03-08 11:33:47 +05:30
/ * *
*
* @ param { * } attribute
* @ param { * } name
* @ param { * } isTableExist Used to determine queries that cant be run while ALTERing TABLE
* /
const getType = ( attribute , name , isTableExist = false ) => {
2018-04-27 13:47:09 +02:00
let type ;
if ( ! attribute . type ) {
// Add integer value if there is a relation
const relation = definition . associations . find ( ( association ) => {
return association . alias === name ;
} ) ;
switch ( relation . nature ) {
case 'oneToOne' :
case 'manyToOne' :
2018-06-13 05:15:55 -07:00
case 'oneWay' :
2018-05-21 17:22:25 +07:00
type = definition . primaryKeyType ;
2018-04-27 13:47:09 +02:00
break ;
default :
return null ;
}
} else {
switch ( attribute . type ) {
2018-05-21 17:22:25 +07:00
case 'uuid' :
2018-08-02 00:05:29 +07:00
type = definition . client === 'pg' ? 'uuid' : 'varchar(36)' ;
2018-05-21 17:22:25 +07:00
break ;
2018-04-27 13:47:09 +02:00
case 'text' :
2018-12-29 21:35:07 +01:00
type = definition . client === 'pg' ? 'text' : 'longtext' ;
2018-04-27 13:47:09 +02:00
break ;
2018-06-20 16:21:09 +02:00
case 'json' :
2018-06-26 13:42:14 +02:00
type = definition . client === 'pg' ? 'jsonb' : 'longtext' ;
2018-05-21 17:22:25 +07:00
break ;
2018-04-27 14:17:50 +02:00
case 'string' :
2018-05-08 11:00:57 -07:00
case 'enumeration' :
2018-04-27 16:29:18 +02:00
case 'password' :
case 'email' :
2018-04-27 14:17:50 +02:00
type = 'varchar(255)' ;
break ;
2018-04-27 13:47:09 +02:00
case 'integer' :
2019-01-21 08:00:13 -07:00
type = definition . client === 'pg' ? 'integer' : 'int' ;
break ;
2018-04-27 13:47:09 +02:00
case 'biginteger' :
2019-01-21 08:37:35 -07:00
type = definition . client === 'pg' ? 'bigint' : 'bigint(53)' ;
2018-04-27 13:47:09 +02:00
break ;
case 'float' :
2018-05-09 12:11:25 -07:00
type = definition . client === 'pg' ? 'double precision' : 'double' ;
2018-05-09 02:28:17 -07:00
break ;
2018-04-27 13:47:09 +02:00
case 'decimal' :
2018-06-04 18:22:21 +02:00
type = 'decimal(10,2)' ;
2018-04-27 13:47:09 +02:00
break ;
case 'date' :
case 'time' :
case 'datetime' :
case 'timestamp' :
2019-03-08 11:33:47 +05:30
type = definition . client === 'pg' ? 'timestamp with time zone' : definition . client === 'sqlite3' && isTableExist ? 'timestamp DEFAULT NULL' : 'timestamp DEFAULT CURRENT_TIMESTAMP' ;
2018-04-27 13:47:09 +02:00
break ;
case 'timestampUpdate' :
2018-12-29 21:35:07 +01:00
switch ( definition . client ) {
case 'pg' :
type = 'timestamp with time zone' ;
break ;
case 'sqlite3' :
type = 'timestamp DEFAULT CURRENT_TIMESTAMP' ;
break ;
default :
type = 'timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP' ;
break ;
}
2018-04-27 13:47:09 +02:00
break ;
case 'boolean' :
type = 'boolean' ;
break ;
default :
}
}
return type ;
} ;
2018-04-24 15:34:26 +02:00
// Apply field type of attributes definition
2019-03-08 11:33:47 +05:30
const generateColumns = ( attrs , start , isTableExist = false ) => {
2018-04-24 15:24:48 +02:00
return Object . keys ( attrs ) . reduce ( ( acc , attr ) => {
const attribute = attributes [ attr ] ;
2019-03-08 11:33:47 +05:30
const type = getType ( attribute , attr , isTableExist ) ;
2018-04-23 18:58:48 +02:00
2018-04-27 13:47:09 +02:00
if ( type ) {
2019-02-14 16:23:29 +01:00
acc . push ( ` ${ quote } ${ attr } ${ quote } ${ type } ${ attribute . required ? 'NOT' : '' } NULL ` ) ;
2018-04-27 13:47:09 +02:00
}
2018-04-23 18:58:48 +02:00
2018-04-24 15:24:48 +02:00
return acc ;
} , start ) ;
2018-04-23 18:58:48 +02:00
} ;
2018-08-20 14:34:22 -05:00
const generateIndexes = async ( table ) => {
2018-06-07 16:14:04 +02:00
try {
2018-06-07 17:16:19 +02:00
const connection = strapi . config . connections [ definition . connection ] ;
let columns = Object . keys ( attributes ) . filter ( attribute => [ 'string' , 'text' ] . includes ( attributes [ attribute ] . type ) ) ;
2018-09-30 12:22:43 -05:00
if ( ! columns . length ) {
2018-09-13 18:51:37 +02:00
// No text columns founds, exit from creating Fulltext Index
return ;
}
2018-06-07 17:16:19 +02:00
switch ( connection . settings . client ) {
2018-12-29 21:35:07 +01:00
case 'mysql' :
columns = columns
. map ( attribute => ` \` ${ attribute } \` ` )
. join ( ',' ) ;
// Create fulltext indexes for every column.
await ORM . knex . raw ( ` CREATE FULLTEXT INDEX SEARCH_ ${ _ . toUpper ( _ . snakeCase ( table ) ) } ON \` ${ table } \` ( ${ columns } ) ` ) ;
break ;
2018-08-20 14:34:22 -05:00
case 'pg' : {
2018-06-07 17:16:19 +02:00
// Enable extension to allow GIN indexes.
2018-08-20 14:34:22 -05:00
await ORM . knex . raw ( 'CREATE EXTENSION IF NOT EXISTS pg_trgm' ) ;
2018-06-07 17:16:19 +02:00
// Create GIN indexes for every column.
const indexes = columns
. map ( column => {
const indexName = ` ${ _ . snakeCase ( table ) } _ ${ column } ` ;
const attribute = _ . toLower ( column ) === column
? column
: ` " ${ column } " ` ;
2018-10-18 22:34:22 +02:00
return ORM . knex . raw ( ` CREATE INDEX IF NOT EXISTS search_ ${ _ . toLower ( indexName ) } ON " ${ table } " USING gin( ${ attribute } gin_trgm_ops) ` ) ;
2018-06-07 17:16:19 +02:00
} ) ;
await Promise . all ( indexes ) ;
break ;
2018-08-20 14:34:22 -05:00
}
2018-06-07 16:14:04 +02:00
}
} catch ( e ) {
2018-06-07 17:16:19 +02:00
// Handle duplicate errors.
if ( e . errno !== 1061 && e . code !== '42P07' ) {
if ( _ . get ( connection , 'options.debug' ) === true ) {
console . log ( e ) ;
}
2018-09-14 00:24:07 +02:00
strapi . log . warn ( 'The SQL database indexes haven\'t been generated successfully. Please enable the debug mode for more details.' ) ;
2018-06-07 16:14:04 +02:00
}
}
} ;
2018-07-04 16:20:44 +02:00
const storeTable = async ( table , attributes ) => {
const existTable = await StrapiConfigs . forge ( { key : ` db_model_ ${ table } ` } ) . fetch ( ) ;
if ( existTable ) {
await StrapiConfigs . forge ( { id : existTable . id } ) . save ( {
2018-07-04 18:08:42 +02:00
value : JSON . stringify ( attributes )
2018-07-04 16:20:44 +02:00
} ) ;
} else {
await StrapiConfigs . forge ( {
key : ` db_model_ ${ table } ` ,
type : 'object' ,
2018-07-04 18:08:42 +02:00
value : JSON . stringify ( attributes )
2018-07-04 16:20:44 +02:00
} ) . save ( ) ;
}
} ;
2019-02-14 16:23:29 +01:00
const createTable = async ( table ) => {
2018-12-29 21:35:07 +01:00
const defaultAttributeDifinitions = {
mysql : [ ` id INT AUTO_INCREMENT NOT NULL PRIMARY KEY ` ] ,
pg : [ ` id SERIAL NOT NULL PRIMARY KEY ` ] ,
sqlite3 : [ 'id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL' ]
} ;
let idAttributeBuilder = defaultAttributeDifinitions [ definition . client ] ;
2018-05-21 17:22:25 +07:00
if ( definition . primaryKeyType === 'uuid' && definition . client === 'pg' ) {
idAttributeBuilder = [ 'id uuid NOT NULL DEFAULT uuid_generate_v4() NOT NULL PRIMARY KEY' ] ;
2018-05-22 10:35:25 +07:00
} else if ( definition . primaryKeyType !== 'integer' ) {
2018-08-02 23:13:38 +07:00
idAttributeBuilder = [ ` id ${ getType ( { type : definition . primaryKeyType } )} NOT NULL PRIMARY KEY ` ] ;
2018-05-21 17:22:25 +07:00
}
const columns = generateColumns ( attributes , idAttributeBuilder ) . join ( ',\n\r' ) ;
2018-04-24 15:24:48 +02:00
// Create table
await ORM . knex . raw ( `
CREATE TABLE $ { quote } $ { table } $ { quote } (
$ { columns }
)
` );
2019-02-14 16:23:29 +01:00
} ;
if ( ! tableExist ) {
await createTable ( table ) ;
2018-06-07 16:14:04 +02:00
2018-06-07 17:16:19 +02:00
// Generate indexes.
2018-06-07 16:14:04 +02:00
await generateIndexes ( table , attributes ) ;
2018-07-04 16:20:44 +02:00
await storeTable ( table , attributes ) ;
2018-04-24 15:24:48 +02:00
} else {
const columns = Object . keys ( attributes ) ;
2018-04-23 18:58:48 +02:00
2018-04-24 15:24:48 +02:00
// Fetch existing column
const columnsExist = await Promise . all ( columns . map ( attribute =>
ORM . knex . schema . hasColumn ( table , attribute )
) ) ;
2018-04-24 14:29:28 +02:00
2018-04-24 15:24:48 +02:00
const columnsToAdd = { } ;
// Get columns to add
columnsExist . forEach ( ( columnExist , index ) => {
const attribute = attributes [ columns [ index ] ] ;
if ( ! columnExist ) {
columnsToAdd [ columns [ index ] ] = attribute ;
2018-04-24 14:29:28 +02:00
}
2018-04-24 15:24:48 +02:00
} ) ;
2018-04-23 18:58:48 +02:00
2018-06-07 17:16:19 +02:00
// Generate indexes for new attributes.
await generateIndexes ( table , columnsToAdd ) ;
2019-02-14 16:23:29 +01:00
let previousAttributes ;
try {
previousAttributes = JSON . parse ( ( await StrapiConfigs . forge ( { key : ` db_model_ ${ table } ` } ) . fetch ( ) ) . toJSON ( ) . value ) ;
} catch ( err ) {
await storeTable ( table , attributes ) ;
previousAttributes = JSON . parse ( ( await StrapiConfigs . forge ( { key : ` db_model_ ${ table } ` } ) . fetch ( ) ) . toJSON ( ) . value ) ;
}
2018-04-24 15:24:48 +02:00
// Generate and execute query to add missing column
if ( Object . keys ( columnsToAdd ) . length > 0 ) {
2019-03-08 11:33:47 +05:30
const columns = generateColumns ( columnsToAdd , [ ] , tableExist ) ;
2018-04-24 15:24:48 +02:00
const queries = columns . reduce ( ( acc , attribute ) => {
2018-05-16 12:07:02 +02:00
acc . push ( ` ALTER TABLE ${ quote } ${ table } ${ quote } ADD ${ attribute } ; ` ) ;
2018-04-24 15:24:48 +02:00
return acc ;
2018-05-31 12:03:24 +02:00
} , [ ] ) ;
2018-04-23 18:58:48 +02:00
2018-06-01 15:49:08 +02:00
await Promise . all ( queries . map ( query => ORM . knex . raw ( query ) ) ) ;
2018-04-24 15:24:48 +02:00
}
2018-04-27 13:47:09 +02:00
2019-02-14 16:23:29 +01:00
let sqlite3Change = false ;
2018-07-04 16:20:44 +02:00
2018-04-27 13:47:09 +02:00
// Execute query to update column type
await Promise . all ( columns . map ( attribute =>
new Promise ( async ( resolve ) => {
2018-07-04 16:20:44 +02:00
if ( JSON . stringify ( previousAttributes [ attribute ] ) === JSON . stringify ( attributes [ attribute ] ) ) {
return resolve ( ) ;
2019-02-14 16:23:29 +01:00
} else {
sqlite3Change = true ;
2018-07-04 16:20:44 +02:00
}
2018-04-27 13:47:09 +02:00
const type = getType ( attributes [ attribute ] , attribute ) ;
2019-02-14 16:23:29 +01:00
if ( type && definition . client !== 'sqlite3' ) {
2018-04-27 16:07:18 +02:00
const changeType = definition . client === 'pg'
2018-04-27 13:47:09 +02:00
? ` ALTER COLUMN ${ quote } ${ attribute } ${ quote } TYPE ${ type } USING ${ quote } ${ attribute } ${ quote } :: ${ type } `
2018-04-27 16:07:18 +02:00
: ` CHANGE ${ quote } ${ attribute } ${ quote } ${ quote } ${ attribute } ${ quote } ${ type } ` ;
2018-04-27 13:47:09 +02:00
2018-04-27 16:07:18 +02:00
const changeRequired = definition . client === 'pg'
? ` ALTER COLUMN ${ quote } ${ attribute } ${ quote } ${ attributes [ attribute ] . required ? 'SET' : 'DROP' } NOT NULL `
: ` CHANGE ${ quote } ${ attribute } ${ quote } ${ quote } ${ attribute } ${ quote } ${ type } ${ attributes [ attribute ] . required ? 'NOT' : '' } NULL ` ;
2019-02-14 16:23:29 +01:00
2018-04-27 16:07:18 +02:00
await ORM . knex . raw ( ` ALTER TABLE ${ quote } ${ table } ${ quote } ${ changeType } ` ) ;
await ORM . knex . raw ( ` ALTER TABLE ${ quote } ${ table } ${ quote } ${ changeRequired } ` ) ;
2018-04-27 13:47:09 +02:00
}
resolve ( ) ;
} )
) ) ;
2018-07-04 16:20:44 +02:00
2019-02-18 18:12:34 +01:00
if ( sqlite3Change && definition . client === 'sqlite3' ) {
2019-02-14 16:23:29 +01:00
await createTable ( ` tmp_ ${ table } ` ) ;
try {
2019-02-19 09:21:07 +01:00
const attrs = Object . keys ( attributes ) . filter ( attribute => getType ( attributes [ attribute ] , attribute ) ) . join ( ' ,' ) ;
await ORM . knex . raw ( ` INSERT INTO ${ quote } tmp_ ${ table } ${ quote } ( ${ attrs } ) SELECT ${ attrs } FROM ${ quote } ${ table } ${ quote } ` ) ;
2019-02-14 16:23:29 +01:00
} catch ( err ) {
console . log ( 'Warning!' ) ;
2019-02-16 13:14:09 +01:00
console . log ( 'We can\'t migrate your data due to the following error.' ) ;
2019-02-14 16:23:29 +01:00
console . log ( ) ;
console . log ( err ) ;
console . log ( ) ;
2019-02-16 13:14:09 +01:00
console . log ( ` We created a new table "tmp_ ${ table } " with your latest changes. ` ) ;
console . log ( ` We suggest you manually migrate your data from " ${ table } " to "tmp_ ${ table } " and then to DROP and RENAME the tables. ` ) ;
2019-02-14 16:23:29 +01:00
return false ;
}
await ORM . knex . raw ( ` DROP TABLE ${ quote } ${ table } ${ quote } ` ) ;
await ORM . knex . raw ( ` ALTER TABLE ${ quote } tmp_ ${ table } ${ quote } RENAME TO ${ quote } ${ table } ${ quote } ` ) ;
await generateIndexes ( table , attributes ) ;
}
2018-07-04 16:20:44 +02:00
await storeTable ( table , attributes ) ;
2018-04-24 15:24:48 +02:00
}
2018-04-23 18:58:48 +02:00
} ;
2018-04-24 15:24:48 +02:00
const quote = definition . client === 'pg' ? '"' : '`' ;
2018-04-23 18:58:48 +02:00
2018-04-24 15:24:48 +02:00
// Add created_at and updated_at field if timestamp option is true
if ( loadedModel . hasTimestamps ) {
2018-12-28 17:04:36 +05:30
definition . attributes [ _ . isString ( loadedModel . hasTimestamps [ 0 ] ) ? loadedModel . hasTimestamps [ 0 ] : 'created_at' ] = {
2018-04-25 11:06:12 +02:00
type : 'timestamp'
} ;
2018-12-28 17:04:36 +05:30
definition . attributes [ _ . isString ( loadedModel . hasTimestamps [ 1 ] ) ? loadedModel . hasTimestamps [ 1 ] : 'updated_at' ] = {
2018-04-25 11:06:12 +02:00
type : 'timestampUpdate'
2018-04-24 15:24:48 +02:00
} ;
}
2018-04-23 18:58:48 +02:00
2018-04-24 15:34:26 +02:00
// Equilize tables
2018-07-04 17:23:04 +02:00
if ( connection . options && connection . options . autoMigration !== false ) {
await handler ( loadedModel . tableName , definition . attributes ) ;
}
2018-04-23 18:58:48 +02:00
2018-04-24 15:34:26 +02:00
// Equilize polymorphic releations
2018-04-24 15:24:48 +02:00
const morphRelations = definition . associations . find ( ( association ) => {
2018-04-24 16:19:17 +02:00
return association . nature . toLowerCase ( ) . includes ( 'morphto' ) ;
2018-04-24 15:24:48 +02:00
} ) ;
2018-04-23 18:58:48 +02:00
2018-04-24 15:24:48 +02:00
if ( morphRelations ) {
const attributes = {
[ ` ${ loadedModel . tableName } _id ` ] : {
2018-05-21 17:22:25 +07:00
type : definition . primaryKeyType
2018-04-24 15:24:48 +02:00
} ,
[ ` ${ morphRelations . alias } _id ` ] : {
2018-05-21 17:22:25 +07:00
type : definition . primaryKeyType
2018-04-24 15:24:48 +02:00
} ,
[ ` ${ morphRelations . alias } _type ` ] : {
type : 'text'
} ,
[ definition . attributes [ morphRelations . alias ] . filter ] : {
type : 'text'
2018-04-23 18:58:48 +02:00
}
2018-04-24 15:24:48 +02:00
} ;
2018-04-23 18:58:48 +02:00
2018-07-04 17:23:04 +02:00
if ( connection . options && connection . options . autoMigration !== false ) {
await handler ( ` ${ loadedModel . tableName } _morph ` , attributes ) ;
}
2018-04-23 18:58:48 +02:00
}
2018-04-24 17:48:15 +02:00
// Equilize many to many releations
const manyRelations = definition . associations . find ( ( association ) => {
return association . nature === 'manyToMany' ;
} ) ;
if ( manyRelations && manyRelations . dominant ) {
2018-04-27 14:53:09 +02:00
const collection = manyRelations . plugin ?
strapi . plugins [ manyRelations . plugin ] . models [ manyRelations . collection ] :
strapi . models [ manyRelations . collection ] ;
2018-04-24 17:48:15 +02:00
const attributes = {
[ ` ${ pluralize . singular ( manyRelations . collection ) } _id ` ] : {
2018-05-21 17:22:25 +07:00
type : definition . primaryKeyType
2018-04-24 17:48:15 +02:00
} ,
2018-04-27 14:53:09 +02:00
[ ` ${ pluralize . singular ( definition . globalId . toLowerCase ( ) ) } _id ` ] : {
2018-05-21 17:22:25 +07:00
type : definition . primaryKeyType
2018-04-24 17:48:15 +02:00
}
} ;
2018-12-04 17:05:03 +01:00
const table = _ . get ( manyRelations , 'collectionName' ) ||
_ . map (
_ . sortBy (
[
collection . attributes [
manyRelations . via
] ,
manyRelations
] ,
'collection'
) ,
table => {
return _ . snakeCase (
// eslint-disable-next-line prefer-template
pluralize . plural ( table . collection ) + ' ' + pluralize . plural ( table . via )
) ;
}
) . join ( '__' ) ;
2018-04-24 17:48:15 +02:00
await handler ( table , attributes ) ;
}
2018-04-25 12:25:20 +02:00
// Remove from attributes (auto handled by bookshlef and not displayed on ctb)
if ( loadedModel . hasTimestamps ) {
2018-12-28 17:04:36 +05:30
delete definition . attributes [ _ . isString ( loadedModel . hasTimestamps [ 0 ] ) ? loadedModel . hasTimestamps [ 0 ] : 'created_at' ] ;
delete definition . attributes [ _ . isString ( loadedModel . hasTimestamps [ 1 ] ) ? loadedModel . hasTimestamps [ 1 ] : 'updated_at' ] ;
2018-04-25 12:25:20 +02:00
}
2018-04-23 18:58:48 +02:00
resolve ( ) ;
} ) ) ;
2017-11-15 16:59:12 +01:00
} catch ( err ) {
2018-08-20 14:34:22 -05:00
strapi . log . error ( ` Impossible to register the ' ${ model } ' model. ` ) ;
2017-11-15 16:59:12 +01:00
strapi . log . error ( err ) ;
strapi . stop ( ) ;
}
} ) ;
2017-02-15 14:43:09 +01:00
2017-11-15 16:59:12 +01:00
if ( _ . isEmpty ( definition . attributes ) ) {
done ( ) ;
2017-07-24 19:58:03 +02:00
}
2017-02-15 14:43:09 +01:00
2017-11-15 16:59:12 +01:00
// Add every relationships to the loaded model for Bookshelf.
// Basic attributes don't need this-- only relations.
_ . forEach ( definition . attributes , ( details , name ) => {
const verbose = _ . get (
utilsModels . getNature ( details , name , undefined , model . toLowerCase ( ) ) ,
'verbose'
) || '' ;
// Build associations key
utilsModels . defineAssociations (
2018-01-23 18:54:17 +01:00
model . toLowerCase ( ) ,
2017-11-15 16:59:12 +01:00
definition ,
details ,
name
) ;
2017-02-15 14:43:09 +01:00
2018-02-22 15:34:33 +01:00
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 ` ) ;
}
2017-12-13 11:38:38 +01:00
2017-11-15 16:59:12 +01:00
switch ( verbose ) {
case 'hasOne' : {
2017-12-13 11:38:38 +01:00
const FK = details . plugin ?
_ . findKey (
strapi . plugins [ details . plugin ] . models [ details . model ] . attributes ,
details => {
if (
details . hasOwnProperty ( 'model' ) &&
details . model === model &&
details . hasOwnProperty ( 'via' ) &&
details . via === name
) {
return details ;
}
2017-11-15 16:59:12 +01:00
}
2017-12-13 11:38:38 +01:00
) :
_ . findKey (
strapi . models [ details . model ] . attributes ,
details => {
if (
details . hasOwnProperty ( 'model' ) &&
details . model === model &&
details . hasOwnProperty ( 'via' ) &&
details . via === name
) {
return details ;
}
}
) ;
2017-09-14 18:47:10 +02:00
2017-12-13 11:38:38 +01:00
const columnName = details . plugin ?
_ . get ( strapi . plugins , ` ${ details . plugin } .models. ${ details . model } .attributes. ${ FK } .columnName ` , FK ) :
_ . get ( strapi . models , ` ${ details . model } .attributes. ${ FK } .columnName ` , FK ) ;
2017-01-04 17:21:24 +01:00
2017-11-15 16:59:12 +01:00
loadedModel [ name ] = function ( ) {
return this . hasOne (
2017-12-13 11:38:38 +01:00
GLOBALS [ globalId ] ,
columnName
2017-11-15 16:59:12 +01:00
) ;
} ;
break ;
}
case 'hasMany' : {
2017-12-13 11:38:38 +01:00
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 ) ;
2017-11-15 16:59:12 +01:00
// Set this info to be able to see if this field is a real database's field.
details . isVirtual = true ;
loadedModel [ name ] = function ( ) {
2017-12-13 11:38:38 +01:00
return this . hasMany ( GLOBALS [ globalId ] , columnName ) ;
2017-11-15 16:59:12 +01:00
} ;
break ;
}
case 'belongsTo' : {
loadedModel [ name ] = function ( ) {
return this . belongsTo (
2017-12-13 11:38:38 +01:00
GLOBALS [ globalId ] ,
_ . get ( details , 'columnName' , name )
2017-11-15 16:59:12 +01:00
) ;
} ;
break ;
}
case 'belongsToMany' : {
2017-12-13 11:38:38 +01:00
const collection = details . plugin ?
strapi . plugins [ details . plugin ] . models [ details . collection ] :
strapi . models [ details . collection ] ;
2018-12-04 17:05:03 +01:00
const collectionName = _ . get ( details , 'collectionName' ) ||
_ . map (
_ . sortBy (
[
collection . attributes [
details . via
] ,
details
] ,
'collection'
) ,
table => {
return _ . snakeCase (
// eslint-disable-next-line prefer-template
pluralize . plural ( table . collection ) + ' ' + pluralize . plural ( table . via )
) ;
}
) . join ( '__' ) ;
2017-11-15 16:59:12 +01:00
const relationship = _ . clone (
2017-12-13 11:38:38 +01:00
collection . attributes [ details . via ]
2017-11-15 16:59:12 +01:00
) ;
2017-04-11 11:56:31 +02:00
2017-11-15 16:59:12 +01:00
// Force singular foreign key
relationship . attribute = pluralize . singular (
relationship . collection
) ;
details . attribute = pluralize . singular ( details . collection ) ;
2017-04-11 11:56:31 +02:00
2017-11-15 16:59:12 +01:00
// Define PK column
details . column = utils . getPK ( model , strapi . models ) ;
relationship . column = utils . getPK (
details . collection ,
strapi . models
) ;
2017-04-11 11:56:31 +02:00
2017-11-15 16:59:12 +01:00
// Sometimes the many-to-many relationships
// is on the same keys on the same models (ex: `friends` key in model `User`)
2018-08-20 14:34:22 -05:00
if ( ` ${ details . attribute } _ ${ details . column } ` === ` ${ relationship . attribute } _ ${ relationship . column } ` ) {
2017-11-15 16:59:12 +01:00
relationship . attribute = pluralize . singular ( details . via ) ;
}
2017-01-04 17:21:24 +01:00
2017-11-15 16:59:12 +01:00
// Set this info to be able to see if this field is a real database's field.
details . isVirtual = true ;
2017-01-04 17:21:24 +01:00
2017-11-15 16:59:12 +01:00
loadedModel [ name ] = function ( ) {
if (
_ . isArray ( _ . get ( details , 'withPivot' ) ) &&
! _ . isEmpty ( details . withPivot )
) {
return this . belongsToMany (
2017-12-13 11:38:38 +01:00
GLOBALS [ globalId ] ,
2017-11-15 16:59:12 +01:00
collectionName ,
2018-08-20 14:34:22 -05:00
` ${ relationship . attribute } _ ${ relationship . column } ` ,
` ${ details . attribute } _ ${ details . column } `
2017-11-15 16:59:12 +01:00
) . withPivot ( details . withPivot ) ;
}
2017-09-14 18:47:10 +02:00
2017-04-11 11:56:31 +02:00
return this . belongsToMany (
2017-12-13 11:38:38 +01:00
GLOBALS [ globalId ] ,
2017-07-28 16:31:23 +02:00
collectionName ,
2018-08-20 14:34:22 -05:00
` ${ relationship . attribute } _ ${ relationship . column } ` ,
` ${ details . attribute } _ ${ details . column } `
2017-11-15 16:59:12 +01:00
) ;
} ;
break ;
}
2018-02-20 19:59:05 +01:00
case 'morphOne' : {
2018-02-21 16:35:25 +01:00
const model = details . plugin ?
strapi . plugins [ details . plugin ] . models [ details . model ] :
strapi . models [ details . model ] ;
const globalId = ` ${ model . collectionName } _morph ` ;
2018-02-20 19:59:05 +01:00
loadedModel [ name ] = function ( ) {
2018-02-21 16:35:25 +01:00
return this
. morphOne ( GLOBALS [ globalId ] , details . via , ` ${ definition . collectionName } ` )
. query ( qb => {
2018-02-21 17:33:30 +01:00
qb . where ( _ . get ( model , ` attributes. ${ details . via } .filter ` , 'field' ) , name ) ;
2018-02-21 16:35:25 +01:00
} ) ;
2018-05-04 17:29:44 +02:00
} ;
2018-02-20 19:59:05 +01:00
break ;
}
case 'morphMany' : {
2018-02-21 16:35:25 +01:00
const collection = details . plugin ?
strapi . plugins [ details . plugin ] . models [ details . collection ] :
strapi . models [ details . collection ] ;
const globalId = ` ${ collection . collectionName } _morph ` ;
2018-02-20 19:59:05 +01:00
loadedModel [ name ] = function ( ) {
2018-02-21 16:35:25 +01:00
return this
. morphMany ( GLOBALS [ globalId ] , details . via , ` ${ definition . collectionName } ` )
. query ( qb => {
2018-02-21 17:33:30 +01:00
qb . where ( _ . get ( collection , ` attributes. ${ details . via } .filter ` , 'field' ) , name ) ;
2018-02-21 16:35:25 +01:00
} ) ;
2018-05-04 17:29:44 +02:00
} ;
2018-02-20 19:59:05 +01:00
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 ) ;
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 ) {
2018-08-20 14:34:22 -05:00
strapi . log . error ( ` Impossible to register the ' ${ model } ' model. ` ) ;
2018-02-20 19:59:05 +01:00
strapi . log . error ( 'The collection name cannot be found for the morphTo method.' ) ;
strapi . stop ( ) ;
}
return models [ 0 ] . collectionName ;
} ) ;
// Define new model.
const options = {
2018-02-21 16:35:25 +01:00
tableName : ` ${ definition . collectionName } _morph ` ,
[ definition . collectionName ] : function ( ) {
return this
. belongsTo (
GLOBALS [ definition . globalId ] ,
` ${ definition . collectionName } _id `
) ;
} ,
2018-02-20 19:59:05 +01:00
related : function ( ) {
2018-02-21 16:35:25 +01:00
return this
. morphTo ( name , ... association . related . map ( ( id , index ) => [ GLOBALS [ id ] , morphValues [ index ] ] ) ) ;
2018-02-20 19:59:05 +01:00
}
} ;
2018-02-21 16:35:25 +01:00
GLOBALS [ options . tableName ] = ORM . Model . extend ( options ) ;
2018-02-20 19:59:05 +01:00
2018-03-01 17:39:31 +01:00
// Set polymorphic table name to the main model.
target [ model ] . morph = GLOBALS [ options . tableName ] ;
2018-02-21 16:35:25 +01:00
// Hack Bookshelf to create a many-to-many polymorphic association.
// Upload has many Upload_morph that morph to different model.
2018-02-20 19:59:05 +01:00
loadedModel [ name ] = function ( ) {
2018-02-21 16:35:25 +01:00
if ( verbose === 'belongsToMorph' ) {
return this . hasOne (
GLOBALS [ options . tableName ] ,
` ${ definition . collectionName } _id `
) ;
}
2018-02-20 19:59:05 +01:00
return this . hasMany (
2018-02-21 16:35:25 +01:00
GLOBALS [ options . tableName ] ,
` ${ definition . collectionName } _id `
2018-02-20 19:59:05 +01:00
) ;
} ;
break ;
}
2017-11-15 16:59:12 +01:00
default : {
break ;
}
2017-07-24 19:58:03 +02:00
}
2016-03-18 11:12:50 +01:00
2017-11-15 16:59:12 +01:00
done ( ) ;
} ) ;
2016-03-18 11:12:50 +01:00
} ) ;
2017-11-15 16:59:12 +01:00
} ;
2017-12-12 16:14:38 +01:00
// Mount `./api` models.
mountModels ( _ . pickBy ( strapi . models , { connection : connectionName } ) , strapi . models ) ;
2017-11-28 15:00:49 +01:00
2017-12-12 16:14:38 +01:00
// Mount `./plugins` models.
_ . forEach ( strapi . plugins , ( plugin , name ) => {
mountModels ( _ . pickBy ( strapi . plugins [ name ] . models , { connection : connectionName } ) , plugin . models , name ) ;
2017-11-15 17:27:07 +01:00
} ) ;
2016-03-18 11:12:50 +01:00
} ) ;
2018-01-24 20:15:11 +05:30
2018-04-23 18:58:48 +02:00
await Promise . all ( databaseUpdate ) ;
2018-01-24 20:15:11 +05:30
cb ( ) ;
2017-09-13 12:18:54 +02:00
} ,
2018-06-11 20:16:17 +07:00
getQueryParams : ( value , type , key ) => {
2017-09-13 12:18:54 +02:00
const result = { } ;
switch ( type ) {
case '=' :
2018-04-03 12:38:33 +02:00
result . key = ` where. ${ key } ` ;
2017-09-13 12:18:54 +02:00
result . value = {
symbol : '=' ,
value
} ;
break ;
case '_ne' :
2018-04-03 12:38:33 +02:00
result . key = ` where. ${ key } ` ;
2017-09-13 12:18:54 +02:00
result . value = {
symbol : '!=' ,
value
} ;
break ;
case '_lt' :
2018-04-03 12:38:33 +02:00
result . key = ` where. ${ key } ` ;
2017-09-13 12:18:54 +02:00
result . value = {
symbol : '<' ,
value
} ;
break ;
case '_gt' :
2018-04-03 12:38:33 +02:00
result . key = ` where. ${ key } ` ;
2017-09-13 12:18:54 +02:00
result . value = {
symbol : '>' ,
value
} ;
break ;
case '_lte' :
2018-04-03 12:38:33 +02:00
result . key = ` where. ${ key } ` ;
2017-09-13 12:18:54 +02:00
result . value = {
symbol : '<=' ,
value
} ;
break ;
case '_gte' :
2018-04-03 12:38:33 +02:00
result . key = ` where. ${ key } ` ;
2017-09-13 12:18:54 +02:00
result . value = {
symbol : '>=' ,
value
} ;
break ;
case '_sort' :
2018-05-21 17:22:25 +07:00
result . key = 'sort' ;
2018-04-03 12:38:33 +02:00
result . value = {
key ,
order : value . toUpperCase ( )
} ;
2017-09-13 12:18:54 +02:00
break ;
case '_start' :
2018-05-21 17:22:25 +07:00
result . key = 'start' ;
2017-09-13 12:18:54 +02:00
result . value = parseFloat ( value ) ;
break ;
case '_limit' :
2018-05-21 17:22:25 +07:00
result . key = 'limit' ;
2017-09-13 12:18:54 +02:00
result . value = parseFloat ( value ) ;
break ;
2018-11-22 18:32:33 +01:00
case '_populate' :
result . key = 'populate' ;
result . value = value ;
break ;
2018-04-03 11:30:39 +02:00
case '_contains' :
2018-04-12 15:44:04 +02:00
case '_containss' :
result . key = ` where. ${ key } ` ;
2018-04-03 11:30:39 +02:00
result . value = {
symbol : 'like' ,
value : ` % ${ value } % `
} ;
break ;
2018-06-06 12:47:29 +02:00
case '_in' :
result . key = ` where. ${ key } ` ;
result . value = {
symbol : 'IN' ,
2019-02-04 12:20:17 +03:30
value : _ . castArray ( value ) ,
2018-06-06 12:47:29 +02:00
} ;
2019-02-03 15:08:36 +03:30
break ;
2019-02-02 16:25:03 +03:30
case '_nin' :
result . key = ` where. ${ key } ` ;
result . value = {
symbol : 'NOT IN' ,
2019-02-04 12:20:17 +03:30
value : _ . castArray ( value ) ,
2018-06-06 12:47:29 +02:00
} ;
break ;
2017-09-13 12:18:54 +02:00
default :
2018-01-12 00:58:34 +02:00
return undefined ;
2017-09-13 12:18:54 +02:00
}
return result ;
2016-03-18 11:12:50 +01:00
}
2018-05-09 16:08:58 +02:00
} , relations ) ;
2016-03-18 11:12:50 +01:00
return hook ;
} ;