2021-05-18 10:16:03 +02:00
'use strict' ;
2021-06-17 16:17:15 +02:00
const _ = require ( 'lodash/fp' ) ;
2021-07-01 14:32:50 +02:00
const types = require ( '../types' ) ;
const { createRelation } = require ( './relations' ) ;
2021-06-17 16:17:15 +02:00
class Metadata extends Map {
add ( meta ) {
return this . set ( meta . uid , meta ) ;
}
2022-09-27 12:38:22 +01:00
/ * *
* Validate the DB metadata , throwing an error if a duplicate DB table name is detected
* /
validate ( ) {
const seenTables = new Map ( ) ;
for ( const meta of this . values ( ) ) {
if ( seenTables . get ( meta . tableName ) ) {
throw new Error (
` DB table ${ meta . tableName } already exists. Change the collectionName of this content type. \n More info here: https://docs.strapi.io/developer-docs/latest/development/backend-customization/ `
) ;
}
seenTables . set ( meta . tableName , true ) ;
}
}
2021-06-17 16:17:15 +02:00
}
2021-05-18 10:16:03 +02:00
2021-09-22 10:49:43 +02:00
// TODO: check if there isn't an attribute with an id already
2021-07-01 14:32:50 +02:00
/ * *
* Create Metadata from models configurations
* @ param { object [ ] } models
* @ returns { Metadata }
* /
2021-06-02 15:53:22 +02:00
const createMetadata = ( models = [ ] ) => {
2021-06-17 16:17:15 +02:00
const metadata = new Metadata ( ) ;
2021-05-18 10:16:03 +02:00
2021-06-17 16:17:15 +02:00
// init pass
2021-06-30 22:52:12 +02:00
for ( const model of _ . cloneDeep ( models ) ) {
2021-06-17 16:17:15 +02:00
metadata . add ( {
singularName : model . singularName ,
uid : model . uid ,
tableName : model . tableName ,
2021-05-18 10:16:03 +02:00
attributes : {
id : {
2021-06-17 16:17:15 +02:00
type : 'increments' ,
2021-05-18 10:16:03 +02:00
} ,
2021-06-17 16:17:15 +02:00
... model . attributes ,
} ,
2021-08-04 17:47:38 +02:00
lifecycles : model . lifecycles || { } ,
2022-06-03 16:21:52 +02:00
indexes : model . indexes || [ ] ,
2021-06-17 16:17:15 +02:00
} ) ;
}
// build compos / relations
for ( const meta of metadata . values ( ) ) {
if ( hasComponentsOrDz ( meta ) ) {
const compoLinkModelMeta = createCompoLinkModelMeta ( meta ) ;
meta . componentLink = compoLinkModelMeta ;
metadata . add ( compoLinkModelMeta ) ;
}
for ( const [ attributeName , attribute ] of Object . entries ( meta . attributes ) ) {
2021-06-18 12:27:47 +02:00
try {
if ( types . isComponent ( attribute . type ) ) {
2021-09-22 10:49:43 +02:00
createComponent ( attributeName , attribute , meta , metadata ) ;
2021-06-18 12:27:47 +02:00
continue ;
}
2021-06-17 16:17:15 +02:00
2021-06-18 12:27:47 +02:00
if ( types . isDynamicZone ( attribute . type ) ) {
2021-09-22 10:49:43 +02:00
createDynamicZone ( attributeName , attribute , meta , metadata ) ;
2021-06-18 12:27:47 +02:00
continue ;
}
if ( types . isRelation ( attribute . type ) ) {
createRelation ( attributeName , attribute , meta , metadata ) ;
continue ;
}
2021-09-22 10:49:43 +02:00
createAttribute ( attributeName , attribute , meta , metadata ) ;
2021-06-18 12:27:47 +02:00
} catch ( error ) {
2021-09-22 10:49:43 +02:00
console . log ( error ) ;
2021-06-18 12:27:47 +02:00
throw new Error (
` Error on attribute ${ attributeName } in model ${ meta . singularName } ( ${ meta . uid } ): ${ error . message } `
) ;
2021-06-17 16:17:15 +02:00
}
2021-06-18 12:27:47 +02:00
}
}
2021-09-22 10:49:43 +02:00
for ( const meta of metadata . values ( ) ) {
const columnToAttribute = Object . keys ( meta . attributes ) . reduce ( ( acc , key ) => {
const attribute = meta . attributes [ key ] ;
return Object . assign ( acc , { [ attribute . columnName || key ] : key } ) ;
} , { } ) ;
meta . columnToAttribute = columnToAttribute ;
}
2022-09-27 12:38:22 +01:00
metadata . validate ( ) ;
2021-06-18 12:27:47 +02:00
return metadata ;
} ;
2021-06-17 16:17:15 +02:00
2022-08-08 23:33:39 +02:00
const hasComponentsOrDz = ( model ) => {
2021-07-01 14:32:50 +02:00
return Object . values ( model . attributes ) . some (
( { type } ) => types . isComponent ( type ) || types . isDynamicZone ( type )
) ;
2021-05-18 10:16:03 +02:00
} ;
2021-06-17 16:17:15 +02:00
// NOTE: we might just move the compo logic outside this layer too at some point
2022-08-08 23:33:39 +02:00
const createCompoLinkModelMeta = ( baseModelMeta ) => {
2021-06-17 16:17:15 +02:00
return {
// TODO: make sure there can't be any conflicts with a prefix
// singularName: 'compo',
2021-06-25 12:07:32 +02:00
uid : ` ${ baseModelMeta . tableName } _components ` ,
2021-06-17 16:17:15 +02:00
tableName : ` ${ baseModelMeta . tableName } _components ` ,
attributes : {
id : {
type : 'increments' ,
} ,
entity _id : {
type : 'integer' ,
column : {
unsigned : true ,
} ,
} ,
component _id : {
type : 'integer' ,
column : {
unsigned : true ,
} ,
} ,
component _type : {
type : 'string' ,
} ,
field : {
type : 'string' ,
} ,
order : {
type : 'integer' ,
column : {
unsigned : true ,
2021-09-15 12:25:09 +02:00
defaultTo : 0 ,
2021-06-17 16:17:15 +02:00
} ,
} ,
} ,
indexes : [
{
name : ` ${ baseModelMeta . tableName } _field_index ` ,
columns : [ 'field' ] ,
2021-09-15 12:25:09 +02:00
type : null ,
2021-06-17 16:17:15 +02:00
} ,
{
name : ` ${ baseModelMeta . tableName } _component_type_index ` ,
columns : [ 'component_type' ] ,
2021-09-15 12:25:09 +02:00
type : null ,
2021-06-17 16:17:15 +02:00
} ,
2021-09-16 11:37:44 +02:00
{
name : ` ${ baseModelMeta . tableName } _entity_fk ` ,
columns : [ 'entity_id' ] ,
} ,
2021-06-17 16:17:15 +02:00
] ,
foreignKeys : [
{
2021-06-24 18:28:36 +02:00
name : ` ${ baseModelMeta . tableName } _entity_fk ` ,
2021-06-17 16:17:15 +02:00
columns : [ 'entity_id' ] ,
referencedColumns : [ 'id' ] ,
referencedTable : baseModelMeta . tableName ,
onDelete : 'CASCADE' ,
} ,
] ,
} ;
} ;
2021-09-22 10:49:43 +02:00
const createDynamicZone = ( attributeName , attribute , meta ) => {
Object . assign ( attribute , {
type : 'relation' ,
relation : 'morphToMany' ,
// TODO: handle restrictions at some point
// target: attribute.components,
joinTable : {
name : meta . componentLink . tableName ,
joinColumn : {
name : 'entity_id' ,
referencedColumn : 'id' ,
} ,
morphColumn : {
idColumn : {
name : 'component_id' ,
referencedColumn : 'id' ,
} ,
typeColumn : {
name : 'component_type' ,
} ,
typeField : '__component' ,
} ,
on : {
field : attributeName ,
} ,
orderBy : {
order : 'asc' ,
} ,
} ,
} ) ;
} ;
const createComponent = ( attributeName , attribute , meta ) => {
Object . assign ( attribute , {
type : 'relation' ,
relation : attribute . repeatable === true ? 'oneToMany' : 'oneToOne' ,
target : attribute . component ,
joinTable : {
name : meta . componentLink . tableName ,
joinColumn : {
name : 'entity_id' ,
referencedColumn : 'id' ,
} ,
inverseJoinColumn : {
name : 'component_id' ,
referencedColumn : 'id' ,
} ,
on : {
field : attributeName ,
} ,
orderBy : {
order : 'asc' ,
} ,
} ,
} ) ;
} ;
const createAttribute = ( attributeName , attribute ) => {
const columnName = _ . snakeCase ( attributeName ) ;
Object . assign ( attribute , { columnName } ) ;
} ;
2021-05-18 10:16:03 +02:00
module . exports = createMetadata ;