mirror of
				https://github.com/strapi/strapi.git
				synced 2025-11-04 11:54:10 +00:00 
			
		
		
		
	Handle join table pivot associations and ordering
This commit is contained in:
		
							parent
							
								
									2009ebf129
								
							
						
					
					
						commit
						3c2040011a
					
				@ -5,14 +5,32 @@ const types = require('./types');
 | 
				
			|||||||
const { createField } = require('./fields');
 | 
					const { createField } = require('./fields');
 | 
				
			||||||
const { createQueryBuilder } = require('./query');
 | 
					const { createQueryBuilder } = require('./query');
 | 
				
			||||||
const { createRepository } = require('./entity-repository');
 | 
					const { createRepository } = require('./entity-repository');
 | 
				
			||||||
const { isBidirectional } = require('./metadata/relations');
 | 
					const { isBidirectional, isOneToAny } = require('./metadata/relations');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const toId = value => value.id || value;
 | 
					const toId = value => value.id || value;
 | 
				
			||||||
const toIds = value => _.castArray(value || []).map(toId);
 | 
					const toIds = value => _.castArray(value || []).map(toId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: move to query layer
 | 
					const isValidId = value => _.isString(value) || _.isInteger(value);
 | 
				
			||||||
 | 
					const toAssocs = data => {
 | 
				
			||||||
 | 
					  return _.castArray(data)
 | 
				
			||||||
 | 
					    .filter(datum => !_.isNil(datum))
 | 
				
			||||||
 | 
					    .map(datum => {
 | 
				
			||||||
 | 
					      // if it is a string or an integer return an obj with id = to datum
 | 
				
			||||||
 | 
					      if (isValidId(datum)) {
 | 
				
			||||||
 | 
					        return { id: datum, __pivot: {} };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // if it is an object check it has at least a valid id
 | 
				
			||||||
 | 
					      if (!_.has('id', datum) || !isValidId(datum.id)) {
 | 
				
			||||||
 | 
					        throw new Error(`Invalid id, expected a string or integer, got ${datum}`);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return datum;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: handle programmatic defaults
 | 
					// TODO: handle programmatic defaults
 | 
				
			||||||
const toRow = (metadata, data = {}) => {
 | 
					const toRow = (metadata, data = {}, { withDefaults = false } = {}) => {
 | 
				
			||||||
  const { attributes } = metadata;
 | 
					  const { attributes } = metadata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const obj = {};
 | 
					  const obj = {};
 | 
				
			||||||
@ -20,14 +38,24 @@ const toRow = (metadata, data = {}) => {
 | 
				
			|||||||
  for (const attributeName in attributes) {
 | 
					  for (const attributeName in attributes) {
 | 
				
			||||||
    const attribute = attributes[attributeName];
 | 
					    const attribute = attributes[attributeName];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (types.isScalar(attribute.type) && _.has(attributeName, data)) {
 | 
					    // TODO:  convert to column name
 | 
				
			||||||
      // TODO: we convert to column name
 | 
					    if (types.isScalar(attribute.type)) {
 | 
				
			||||||
      // TODO: handle default value
 | 
					      const field = createField(attribute);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const field = createField(attribute.type, attribute);
 | 
					      if (_.isUndefined(data[attributeName])) {
 | 
				
			||||||
 | 
					        if (!_.isUndefined(attribute.default) && withDefaults) {
 | 
				
			||||||
 | 
					          if (typeof attribute.default === 'function') {
 | 
				
			||||||
 | 
					            obj[attributeName] = attribute.default();
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            obj[attributeName] = attribute.default;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // TODO: validate data on creation
 | 
					      if (typeof field.validate === 'function' && data[attributeName] !== null) {
 | 
				
			||||||
      // field.validate(data[attributeName]);
 | 
					        field.validate(data[attributeName]);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const val = data[attributeName] === null ? null : field.toDB(data[attributeName]);
 | 
					      const val = data[attributeName] === null ? null : field.toDB(data[attributeName]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -139,7 +167,7 @@ const createEntityManager = db => {
 | 
				
			|||||||
        throw new Error('Create expects a data object');
 | 
					        throw new Error('Create expects a data object');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const dataToInsert = toRow(metadata, data);
 | 
					      const dataToInsert = toRow(metadata, data, { withDefaults: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const [id] = await this.createQueryBuilder(uid)
 | 
					      const [id] = await this.createQueryBuilder(uid)
 | 
				
			||||||
        .insert(dataToInsert)
 | 
					        .insert(dataToInsert)
 | 
				
			||||||
@ -160,6 +188,7 @@ const createEntityManager = db => {
 | 
				
			|||||||
      return result;
 | 
					      return result;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: where do we handle relation processing for many queries ?
 | 
				
			||||||
    async createMany(uid, params = {}) {
 | 
					    async createMany(uid, params = {}) {
 | 
				
			||||||
      await db.lifecycles.run('beforeCreateMany', uid, { params });
 | 
					      await db.lifecycles.run('beforeCreateMany', uid, { params });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -170,7 +199,7 @@ const createEntityManager = db => {
 | 
				
			|||||||
        throw new Error('CreateMany expects data to be an array');
 | 
					        throw new Error('CreateMany expects data to be an array');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const dataToInsert = data.map(datum => toRow(metadata, datum));
 | 
					      const dataToInsert = data.map(datum => toRow(metadata, datum, { withDefaults: true }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (_.isEmpty(dataToInsert)) {
 | 
					      if (_.isEmpty(dataToInsert)) {
 | 
				
			||||||
        throw new Error('Nothing to insert');
 | 
					        throw new Error('Nothing to insert');
 | 
				
			||||||
@ -236,6 +265,7 @@ const createEntityManager = db => {
 | 
				
			|||||||
      return result;
 | 
					      return result;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: where do we handle relation processing for many queries ?
 | 
				
			||||||
    async updateMany(uid, params = {}) {
 | 
					    async updateMany(uid, params = {}) {
 | 
				
			||||||
      await db.lifecycles.run('beforeUpdateMany', uid, { params });
 | 
					      await db.lifecycles.run('beforeUpdateMany', uid, { params });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -327,7 +357,7 @@ const createEntityManager = db => {
 | 
				
			|||||||
      for (const attributeName in attributes) {
 | 
					      for (const attributeName in attributes) {
 | 
				
			||||||
        const attribute = attributes[attributeName];
 | 
					        const attribute = attributes[attributeName];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const isValidLink = _.has(attributeName, data) && !_.isNull(data[attributeName]);
 | 
					        const isValidLink = _.has(attributeName, data) && !_.isNil(data[attributeName]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (attribute.type !== 'relation' || !isValidLink) {
 | 
					        if (attribute.type !== 'relation' || !isValidLink) {
 | 
				
			||||||
          continue;
 | 
					          continue;
 | 
				
			||||||
@ -352,13 +382,15 @@ const createEntityManager = db => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            const { idColumn, typeColumn } = morphColumn;
 | 
					            const { idColumn, typeColumn } = morphColumn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const rows = toIds(data[attributeName]).map((dataID, idx) => ({
 | 
					            const rows = toAssocs(data[attributeName]).map(data => {
 | 
				
			||||||
              [joinColumn.name]: dataID,
 | 
					              return {
 | 
				
			||||||
              [idColumn.name]: id,
 | 
					                [joinColumn.name]: data.id,
 | 
				
			||||||
              [typeColumn.name]: uid,
 | 
					                [idColumn.name]: id,
 | 
				
			||||||
              ...(joinTable.on || {}),
 | 
					                [typeColumn.name]: uid,
 | 
				
			||||||
              order: idx,
 | 
					                ...(joinTable.on || {}),
 | 
				
			||||||
            }));
 | 
					                ...(data.__pivot || {}),
 | 
				
			||||||
 | 
					              };
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (_.isEmpty(rows)) {
 | 
					            if (_.isEmpty(rows)) {
 | 
				
			||||||
              continue;
 | 
					              continue;
 | 
				
			||||||
@ -379,12 +411,12 @@ const createEntityManager = db => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
          const { idColumn, typeColumn, typeField = '__type' } = morphColumn;
 | 
					          const { idColumn, typeColumn, typeField = '__type' } = morphColumn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          const rows = _.castArray(data[attributeName] || []).map((data, idx) => ({
 | 
					          const rows = toAssocs(data[attributeName]).map(data => ({
 | 
				
			||||||
            [joinColumn.name]: id,
 | 
					            [joinColumn.name]: id,
 | 
				
			||||||
            [idColumn.name]: data.id,
 | 
					            [idColumn.name]: data.id,
 | 
				
			||||||
            [typeColumn.name]: data[typeField],
 | 
					            [typeColumn.name]: data[typeField],
 | 
				
			||||||
            ...(joinTable.on || {}),
 | 
					            ...(joinTable.on || {}),
 | 
				
			||||||
            order: idx,
 | 
					            ...(data.__pivot || {}),
 | 
				
			||||||
          }));
 | 
					          }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if (_.isEmpty(rows)) {
 | 
					          if (_.isEmpty(rows)) {
 | 
				
			||||||
@ -438,13 +470,8 @@ const createEntityManager = db => {
 | 
				
			|||||||
          const { joinTable } = attribute;
 | 
					          const { joinTable } = attribute;
 | 
				
			||||||
          const { joinColumn, inverseJoinColumn } = joinTable;
 | 
					          const { joinColumn, inverseJoinColumn } = joinTable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          // TODO: redefine
 | 
					          // TODO: validate logic of delete
 | 
				
			||||||
          // TODO: check it is an id & the entity exists (will throw due to FKs otherwise so not a big pbl in SQL)
 | 
					          if (isOneToAny(attribute) && isBidirectional(attribute)) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
          if (
 | 
					 | 
				
			||||||
            ['oneToOne', 'oneToMany'].includes(attribute.relation) &&
 | 
					 | 
				
			||||||
            isBidirectional(attribute)
 | 
					 | 
				
			||||||
          ) {
 | 
					 | 
				
			||||||
            await this.createQueryBuilder(joinTable.name)
 | 
					            await this.createQueryBuilder(joinTable.name)
 | 
				
			||||||
              .delete()
 | 
					              .delete()
 | 
				
			||||||
              .where({ [inverseJoinColumn.name]: _.castArray(data[attributeName]) })
 | 
					              .where({ [inverseJoinColumn.name]: _.castArray(data[attributeName]) })
 | 
				
			||||||
@ -452,11 +479,12 @@ const createEntityManager = db => {
 | 
				
			|||||||
              .execute();
 | 
					              .execute();
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          const insert = _.castArray(data[attributeName]).map(datum => {
 | 
					          const insert = toAssocs(data[attributeName]).map(data => {
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
              [joinColumn.name]: id,
 | 
					              [joinColumn.name]: id,
 | 
				
			||||||
              [inverseJoinColumn.name]: datum,
 | 
					              [inverseJoinColumn.name]: data.id,
 | 
				
			||||||
              ...(joinTable.on || {}),
 | 
					              ...(joinTable.on || {}),
 | 
				
			||||||
 | 
					              ...(data.__pivot || {}),
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -527,12 +555,12 @@ const createEntityManager = db => {
 | 
				
			|||||||
              })
 | 
					              })
 | 
				
			||||||
              .execute();
 | 
					              .execute();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const rows = toIds(data[attributeName] || []).map((dataID, idx) => ({
 | 
					            const rows = toAssocs(data[attributeName]).map(data => ({
 | 
				
			||||||
              [joinColumn.name]: dataID,
 | 
					              [joinColumn.name]: data.id,
 | 
				
			||||||
              [idColumn.name]: id,
 | 
					              [idColumn.name]: id,
 | 
				
			||||||
              [typeColumn.name]: uid,
 | 
					              [typeColumn.name]: uid,
 | 
				
			||||||
              ...(joinTable.on || {}),
 | 
					              ...(joinTable.on || {}),
 | 
				
			||||||
              order: idx,
 | 
					              ...(data.__pivot || {}),
 | 
				
			||||||
            }));
 | 
					            }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (_.isEmpty(rows)) {
 | 
					            if (_.isEmpty(rows)) {
 | 
				
			||||||
@ -566,12 +594,12 @@ const createEntityManager = db => {
 | 
				
			|||||||
            })
 | 
					            })
 | 
				
			||||||
            .execute();
 | 
					            .execute();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          const rows = _.castArray(data[attributeName] || []).map((data, idx) => ({
 | 
					          const rows = toAssocs(data[attributeName]).map(data => ({
 | 
				
			||||||
            [joinColumn.name]: id,
 | 
					            [joinColumn.name]: id,
 | 
				
			||||||
            [idColumn.name]: data.id,
 | 
					            [idColumn.name]: data.id,
 | 
				
			||||||
            [typeColumn.name]: data[typeField],
 | 
					            [typeColumn.name]: data[typeField],
 | 
				
			||||||
            ...(joinTable.on || {}),
 | 
					            ...(joinTable.on || {}),
 | 
				
			||||||
            order: idx,
 | 
					            ...(data.__pivot || {}),
 | 
				
			||||||
          }));
 | 
					          }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if (_.isEmpty(rows)) {
 | 
					          if (_.isEmpty(rows)) {
 | 
				
			||||||
@ -624,17 +652,18 @@ const createEntityManager = db => {
 | 
				
			|||||||
          if (['oneToOne', 'oneToMany'].includes(attribute.relation)) {
 | 
					          if (['oneToOne', 'oneToMany'].includes(attribute.relation)) {
 | 
				
			||||||
            await this.createQueryBuilder(joinTable.name)
 | 
					            await this.createQueryBuilder(joinTable.name)
 | 
				
			||||||
              .delete()
 | 
					              .delete()
 | 
				
			||||||
              .where({ [inverseJoinColumn.name]: _.castArray(data[attributeName] || []) })
 | 
					              .where({ [inverseJoinColumn.name]: toIds(data[attributeName]) })
 | 
				
			||||||
              .where(joinTable.on || {})
 | 
					              .where(joinTable.on || {})
 | 
				
			||||||
              .execute();
 | 
					              .execute();
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if (!_.isNull(data[attributeName])) {
 | 
					          if (!_.isNull(data[attributeName])) {
 | 
				
			||||||
            const insert = _.castArray(data[attributeName] || []).map(datum => {
 | 
					            const insert = toAssocs(data[attributeName]).map(data => {
 | 
				
			||||||
              return {
 | 
					              return {
 | 
				
			||||||
                [joinColumn.name]: id,
 | 
					                [joinColumn.name]: id,
 | 
				
			||||||
                [inverseJoinColumn.name]: datum,
 | 
					                [inverseJoinColumn.name]: data.id,
 | 
				
			||||||
                ...(joinTable.on || {}),
 | 
					                ...(joinTable.on || {}),
 | 
				
			||||||
 | 
					                ...(data.__pivot || {}),
 | 
				
			||||||
              };
 | 
					              };
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -652,9 +681,9 @@ const createEntityManager = db => {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Delete relations of an existing entity
 | 
					     * Delete relational associations of an existing entity
 | 
				
			||||||
     * This removes associations but doesn't do cascade deletions for components for example. This will be handled on the entity service layer instead
 | 
					     * This removes associations but doesn't do cascade deletions for components for example. This will be handled on the entity service layer instead
 | 
				
			||||||
     * NOTE: Most of the deletion should be handled by ON DELETE CASCADE for dialect that have FKs
 | 
					     * NOTE: Most of the deletion should be handled by ON DELETE CASCADE for dialects that have FKs
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param {EntityManager} em - entity manager instance
 | 
					     * @param {EntityManager} em - entity manager instance
 | 
				
			||||||
     * @param {Metadata} metadata - model metadta
 | 
					     * @param {Metadata} metadata - model metadta
 | 
				
			||||||
@ -816,11 +845,8 @@ const createEntityManager = db => {
 | 
				
			|||||||
    // custom queries
 | 
					    // custom queries
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // utilities
 | 
					    // utilities
 | 
				
			||||||
    // -> format
 | 
					 | 
				
			||||||
    // -> parse
 | 
					 | 
				
			||||||
    // -> map result
 | 
					    // -> map result
 | 
				
			||||||
    // -> map input
 | 
					    // -> map input
 | 
				
			||||||
    // -> validation
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // extra features
 | 
					    // extra features
 | 
				
			||||||
    // -> virtuals
 | 
					    // -> virtuals
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										5
									
								
								packages/core/database/lib/fields.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								packages/core/database/lib/fields.d.ts
									
									
									
									
										vendored
									
									
								
							@ -4,4 +4,7 @@ interface Field {
 | 
				
			|||||||
  fromDB(value: any): any;
 | 
					  fromDB(value: any): any;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function createField(type: string): Field;
 | 
					interface Attribute {
 | 
				
			||||||
 | 
					  type: string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export function createField(attribute: Attribute): Field;
 | 
				
			||||||
 | 
				
			|||||||
@ -217,7 +217,9 @@ const typeToFieldMap = {
 | 
				
			|||||||
  boolean: BooleanField,
 | 
					  boolean: BooleanField,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const createField = (type /*attribute*/) => {
 | 
					const createField = attribute => {
 | 
				
			||||||
 | 
					  const { type } = attribute;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (_.has(type, typeToFieldMap)) {
 | 
					  if (_.has(type, typeToFieldMap)) {
 | 
				
			||||||
    return new typeToFieldMap[type]({});
 | 
					    return new typeToFieldMap[type]({});
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -102,6 +102,9 @@ const createMetadata = (models = []) => {
 | 
				
			|||||||
              on: {
 | 
					              on: {
 | 
				
			||||||
                field: attributeName,
 | 
					                field: attributeName,
 | 
				
			||||||
              },
 | 
					              },
 | 
				
			||||||
 | 
					              orderBy: {
 | 
				
			||||||
 | 
					                order: 'asc',
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -135,6 +138,9 @@ const createMetadata = (models = []) => {
 | 
				
			|||||||
              on: {
 | 
					              on: {
 | 
				
			||||||
                field: attributeName,
 | 
					                field: attributeName,
 | 
				
			||||||
              },
 | 
					              },
 | 
				
			||||||
 | 
					              orderBy: {
 | 
				
			||||||
 | 
					                order: 'asc',
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ const _ = require('lodash/fp');
 | 
				
			|||||||
const hasInversedBy = _.has('inversedBy');
 | 
					const hasInversedBy = _.has('inversedBy');
 | 
				
			||||||
const hasMappedBy = _.has('mappedBy');
 | 
					const hasMappedBy = _.has('mappedBy');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const isOneToAny = attribute => ['oneToOne', 'oneToMany'].includes(attribute.relation);
 | 
				
			||||||
const isBidirectional = attribute => hasInversedBy(attribute) || hasMappedBy(attribute);
 | 
					const isBidirectional = attribute => hasInversedBy(attribute) || hasMappedBy(attribute);
 | 
				
			||||||
const isOwner = attribute => !isBidirectional(attribute) || hasInversedBy(attribute);
 | 
					const isOwner = attribute => !isBidirectional(attribute) || hasInversedBy(attribute);
 | 
				
			||||||
const shouldUseJoinTable = attribute => attribute.useJoinTable !== false;
 | 
					const shouldUseJoinTable = attribute => attribute.useJoinTable !== false;
 | 
				
			||||||
@ -442,4 +443,5 @@ module.exports = {
 | 
				
			|||||||
  createRelation,
 | 
					  createRelation,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  isBidirectional,
 | 
					  isBidirectional,
 | 
				
			||||||
 | 
					  isOneToAny,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -431,6 +431,7 @@ const applyJoin = (qb, join) => {
 | 
				
			|||||||
    rootColumn,
 | 
					    rootColumn,
 | 
				
			||||||
    rootTable = this.alias,
 | 
					    rootTable = this.alias,
 | 
				
			||||||
    on,
 | 
					    on,
 | 
				
			||||||
 | 
					    orderBy,
 | 
				
			||||||
  } = join;
 | 
					  } = join;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  qb[method]({ [alias]: referencedTable }, inner => {
 | 
					  qb[method]({ [alias]: referencedTable }, inner => {
 | 
				
			||||||
@ -442,6 +443,13 @@ const applyJoin = (qb, join) => {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (orderBy) {
 | 
				
			||||||
 | 
					    Object.keys(orderBy).forEach(column => {
 | 
				
			||||||
 | 
					      const direction = orderBy[column];
 | 
				
			||||||
 | 
					      qb.orderBy(`${alias}.${column}`, direction);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const applyJoins = (qb, joins) => joins.forEach(join => applyJoin(qb, join));
 | 
					const applyJoins = (qb, joins) => joins.forEach(join => applyJoin(qb, join));
 | 
				
			||||||
@ -617,6 +625,7 @@ const applyPopulate = async (results, populate, ctx) => {
 | 
				
			|||||||
            rootColumn: joinTable.inverseJoinColumn.referencedColumn,
 | 
					            rootColumn: joinTable.inverseJoinColumn.referencedColumn,
 | 
				
			||||||
            rootTable: qb.alias,
 | 
					            rootTable: qb.alias,
 | 
				
			||||||
            on: joinTable.on,
 | 
					            on: joinTable.on,
 | 
				
			||||||
 | 
					            orderBy: joinTable.orderBy,
 | 
				
			||||||
          })
 | 
					          })
 | 
				
			||||||
          .addSelect(joinColAlias)
 | 
					          .addSelect(joinColAlias)
 | 
				
			||||||
          .where({ [joinColAlias]: referencedValues })
 | 
					          .where({ [joinColAlias]: referencedValues })
 | 
				
			||||||
@ -734,6 +743,7 @@ const applyPopulate = async (results, populate, ctx) => {
 | 
				
			|||||||
            rootColumn: joinTable.inverseJoinColumn.referencedColumn,
 | 
					            rootColumn: joinTable.inverseJoinColumn.referencedColumn,
 | 
				
			||||||
            rootTable: qb.alias,
 | 
					            rootTable: qb.alias,
 | 
				
			||||||
            on: joinTable.on,
 | 
					            on: joinTable.on,
 | 
				
			||||||
 | 
					            orderBy: joinTable.orderBy,
 | 
				
			||||||
          })
 | 
					          })
 | 
				
			||||||
          .addSelect(joinColAlias)
 | 
					          .addSelect(joinColAlias)
 | 
				
			||||||
          .where({ [joinColAlias]: referencedValues })
 | 
					          .where({ [joinColAlias]: referencedValues })
 | 
				
			||||||
@ -812,6 +822,7 @@ const applyPopulate = async (results, populate, ctx) => {
 | 
				
			|||||||
          rootColumn: joinTable.inverseJoinColumn.referencedColumn,
 | 
					          rootColumn: joinTable.inverseJoinColumn.referencedColumn,
 | 
				
			||||||
          rootTable: qb.alias,
 | 
					          rootTable: qb.alias,
 | 
				
			||||||
          on: joinTable.on,
 | 
					          on: joinTable.on,
 | 
				
			||||||
 | 
					          orderBy: joinTable.orderBy,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .addSelect(joinColAlias)
 | 
					        .addSelect(joinColAlias)
 | 
				
			||||||
        .where({ [joinColAlias]: referencedValues })
 | 
					        .where({ [joinColAlias]: referencedValues })
 | 
				
			||||||
@ -894,6 +905,7 @@ const applyPopulate = async (results, populate, ctx) => {
 | 
				
			|||||||
            rootColumn: joinColumn.referencedColumn,
 | 
					            rootColumn: joinColumn.referencedColumn,
 | 
				
			||||||
            rootTable: qb.alias,
 | 
					            rootTable: qb.alias,
 | 
				
			||||||
            on: joinTable.on,
 | 
					            on: joinTable.on,
 | 
				
			||||||
 | 
					            orderBy: joinTable.orderBy,
 | 
				
			||||||
          })
 | 
					          })
 | 
				
			||||||
          .addSelect([`${alias}.${idColumn.name}`, `${alias}.${typeColumn.name}`])
 | 
					          .addSelect([`${alias}.${idColumn.name}`, `${alias}.${typeColumn.name}`])
 | 
				
			||||||
          .where({
 | 
					          .where({
 | 
				
			||||||
@ -1078,7 +1090,7 @@ const fromRow = (metadata, row) => {
 | 
				
			|||||||
      // TODO: handle default value too
 | 
					      // TODO: handle default value too
 | 
				
			||||||
      // TODO: format data & use dialect to know which type they support (json particularly)
 | 
					      // TODO: format data & use dialect to know which type they support (json particularly)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const field = createField(attribute.type, attribute);
 | 
					      const field = createField(attribute);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // TODO: validate data on creation
 | 
					      // TODO: validate data on creation
 | 
				
			||||||
      // field.validate(data[attributeName]);
 | 
					      // field.validate(data[attributeName]);
 | 
				
			||||||
 | 
				
			|||||||
@ -14,31 +14,6 @@ const omitComponentData = (contentType, data) => {
 | 
				
			|||||||
  return omit(componentAttributes, data);
 | 
					  return omit(componentAttributes, data);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// components can have nested compos so this must be recursive
 | 
					 | 
				
			||||||
const createComponent = async (uid, data) => {
 | 
					 | 
				
			||||||
  const model = strapi.getModel(uid);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const componentData = await createComponents(uid, data);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return await strapi.query(uid).create({
 | 
					 | 
				
			||||||
    data: Object.assign(omitComponentData(model, data), componentData),
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// components can have nested compos so this must be recursive
 | 
					 | 
				
			||||||
const updateComponent = async (uid, componentToUpdate, data) => {
 | 
					 | 
				
			||||||
  const model = strapi.getModel(uid);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const componentData = await updateComponents(uid, componentToUpdate, data);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return await strapi.query(uid).update({
 | 
					 | 
				
			||||||
    where: {
 | 
					 | 
				
			||||||
      id: componentToUpdate.id,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    data: Object.assign(omitComponentData(model, data), componentData),
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// NOTE: we could generalize the logic to allow CRUD of relation directly in the DB layer
 | 
					// NOTE: we could generalize the logic to allow CRUD of relation directly in the DB layer
 | 
				
			||||||
const createComponents = async (uid, data) => {
 | 
					const createComponents = async (uid, data) => {
 | 
				
			||||||
  const { attributes } = strapi.getModel(uid);
 | 
					  const { attributes } = strapi.getModel(uid);
 | 
				
			||||||
@ -48,7 +23,7 @@ const createComponents = async (uid, data) => {
 | 
				
			|||||||
  for (const attributeName in attributes) {
 | 
					  for (const attributeName in attributes) {
 | 
				
			||||||
    const attribute = attributes[attributeName];
 | 
					    const attribute = attributes[attributeName];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!has(attributeName, data)) {
 | 
					    if (!has(attributeName, data) || !contentTypesUtils.isComponentAttribute(attribute)) {
 | 
				
			||||||
      continue;
 | 
					      continue;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -71,10 +46,26 @@ const createComponents = async (uid, data) => {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // TODO: add order
 | 
					        // TODO: add order
 | 
				
			||||||
        componentBody[attributeName] = components.map(({ id }) => id);
 | 
					        componentBody[attributeName] = components.map(({ id }, idx) => {
 | 
				
			||||||
 | 
					          return {
 | 
				
			||||||
 | 
					            id,
 | 
				
			||||||
 | 
					            __pivot: {
 | 
				
			||||||
 | 
					              order: idx + 1,
 | 
				
			||||||
 | 
					              field: attributeName,
 | 
				
			||||||
 | 
					              component_type: componentUID,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        const component = await createComponent(componentUID, componentValue);
 | 
					        const component = await createComponent(componentUID, componentValue);
 | 
				
			||||||
        componentBody[attributeName] = component.id;
 | 
					        componentBody[attributeName] = {
 | 
				
			||||||
 | 
					          id: component.id,
 | 
				
			||||||
 | 
					          __pivot: {
 | 
				
			||||||
 | 
					            order: 1,
 | 
				
			||||||
 | 
					            field: attributeName,
 | 
				
			||||||
 | 
					            component_type: componentUID,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      continue;
 | 
					      continue;
 | 
				
			||||||
@ -88,9 +79,16 @@ const createComponents = async (uid, data) => {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      componentBody[attributeName] = await Promise.all(
 | 
					      componentBody[attributeName] = await Promise.all(
 | 
				
			||||||
        dynamiczoneValues.map(async value => {
 | 
					        dynamiczoneValues.map(async (value, idx) => {
 | 
				
			||||||
          const { id } = await createComponent(value.__component, value);
 | 
					          const { id } = await createComponent(value.__component, value);
 | 
				
			||||||
          return { id, __component: value.__component };
 | 
					          return {
 | 
				
			||||||
 | 
					            id,
 | 
				
			||||||
 | 
					            __component: value.__component,
 | 
				
			||||||
 | 
					            __pivot: {
 | 
				
			||||||
 | 
					              order: idx + 1,
 | 
				
			||||||
 | 
					              field: attributeName,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -101,21 +99,6 @@ const createComponents = async (uid, data) => {
 | 
				
			|||||||
  return componentBody;
 | 
					  return componentBody;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const updateOrCreateComponent = (componentUID, value) => {
 | 
					 | 
				
			||||||
  if (value === null) {
 | 
					 | 
				
			||||||
    return null;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // update
 | 
					 | 
				
			||||||
  if (has('id', value)) {
 | 
					 | 
				
			||||||
    // TODO: verify the compo is associated with the entity
 | 
					 | 
				
			||||||
    return updateComponent(componentUID, { id: value.id }, value);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // create
 | 
					 | 
				
			||||||
  return createComponent(componentUID, value);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
  delete old components
 | 
					  delete old components
 | 
				
			||||||
  create or update
 | 
					  create or update
 | 
				
			||||||
@ -260,11 +243,6 @@ const deleteOldDZComponents = async (uid, entityToUpdate, attributeName, dynamic
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const deleteComponent = async (uid, componentToDelete) => {
 | 
					 | 
				
			||||||
  await deleteComponents(uid, componentToDelete);
 | 
					 | 
				
			||||||
  await strapi.query(uid).delete({ where: { id: componentToDelete.id } });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const deleteComponents = async (uid, entityToDelete) => {
 | 
					const deleteComponents = async (uid, entityToDelete) => {
 | 
				
			||||||
  const { attributes } = strapi.getModel(uid);
 | 
					  const { attributes } = strapi.getModel(uid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -305,6 +283,55 @@ const deleteComponents = async (uid, entityToDelete) => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***************************
 | 
				
			||||||
 | 
					    Component queries
 | 
				
			||||||
 | 
					***************************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// components can have nested compos so this must be recursive
 | 
				
			||||||
 | 
					const createComponent = async (uid, data) => {
 | 
				
			||||||
 | 
					  const model = strapi.getModel(uid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const componentData = await createComponents(uid, data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return await strapi.query(uid).create({
 | 
				
			||||||
 | 
					    data: Object.assign(omitComponentData(model, data), componentData),
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// components can have nested compos so this must be recursive
 | 
				
			||||||
 | 
					const updateComponent = async (uid, componentToUpdate, data) => {
 | 
				
			||||||
 | 
					  const model = strapi.getModel(uid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const componentData = await updateComponents(uid, componentToUpdate, data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return await strapi.query(uid).update({
 | 
				
			||||||
 | 
					    where: {
 | 
				
			||||||
 | 
					      id: componentToUpdate.id,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    data: Object.assign(omitComponentData(model, data), componentData),
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const updateOrCreateComponent = (componentUID, value) => {
 | 
				
			||||||
 | 
					  if (value === null) {
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // update
 | 
				
			||||||
 | 
					  if (has('id', value)) {
 | 
				
			||||||
 | 
					    // TODO: verify the compo is associated with the entity
 | 
				
			||||||
 | 
					    return updateComponent(componentUID, { id: value.id }, value);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // create
 | 
				
			||||||
 | 
					  return createComponent(componentUID, value);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const deleteComponent = async (uid, componentToDelete) => {
 | 
				
			||||||
 | 
					  await deleteComponents(uid, componentToDelete);
 | 
				
			||||||
 | 
					  await strapi.query(uid).delete({ where: { id: componentToDelete.id } });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
  omitComponentData,
 | 
					  omitComponentData,
 | 
				
			||||||
  createComponents,
 | 
					  createComponents,
 | 
				
			||||||
 | 
				
			|||||||
@ -70,10 +70,12 @@ const transformParamsToQuery = (uid, params = {}) => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (fields) {
 | 
					  if (fields) {
 | 
				
			||||||
 | 
					    // TODO: handle *.* syntax
 | 
				
			||||||
    query.select = _.castArray(fields);
 | 
					    query.select = _.castArray(fields);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (populate) {
 | 
					  if (populate) {
 | 
				
			||||||
 | 
					    // TODO: handle *.* syntax
 | 
				
			||||||
    const { populate } = params;
 | 
					    const { populate } = params;
 | 
				
			||||||
    query.populate = typeof populate === 'object' ? populate : _.castArray(populate);
 | 
					    query.populate = typeof populate === 'object' ? populate : _.castArray(populate);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -154,10 +154,12 @@ const createContentType = (model, { modelName }, { apiName, pluginName } = {}) =
 | 
				
			|||||||
  Object.assign(model.attributes, {
 | 
					  Object.assign(model.attributes, {
 | 
				
			||||||
    [CREATED_AT_ATTRIBUTE]: {
 | 
					    [CREATED_AT_ATTRIBUTE]: {
 | 
				
			||||||
      type: 'datetime',
 | 
					      type: 'datetime',
 | 
				
			||||||
      // default: () => new Date(),
 | 
					      default: () => new Date(),
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    // TODO: handle on edit set to new date
 | 
				
			||||||
    [UPDATED_AT_ATTRIBUTE]: {
 | 
					    [UPDATED_AT_ATTRIBUTE]: {
 | 
				
			||||||
      type: 'datetime',
 | 
					      type: 'datetime',
 | 
				
			||||||
 | 
					      default: () => new Date(),
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user