mirror of
				https://github.com/strapi/strapi.git
				synced 2025-11-03 19:36:20 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			353 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			353 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
'use strict';
 | 
						|
 | 
						|
/**
 | 
						|
 * Module dependencies
 | 
						|
 */
 | 
						|
 | 
						|
// Public node modules.
 | 
						|
const _ = require('lodash');
 | 
						|
 | 
						|
// Utils
 | 
						|
const { models: { getValuePrimaryKey } } = require('strapi-utils');
 | 
						|
 | 
						|
const transformToArrayID = (array, association) => {
 | 
						|
  if(_.isArray(array)) {
 | 
						|
    array = array.map(value => {
 | 
						|
      if (_.isPlainObject(value)) {
 | 
						|
        return value._id || value.id || false;
 | 
						|
      }
 | 
						|
 | 
						|
      return value;
 | 
						|
    });
 | 
						|
 | 
						|
    return array.filter(n => n);
 | 
						|
  }
 | 
						|
 | 
						|
  if (association.type === 'model' || (association.type === 'collection' && _.isObject(array))) {
 | 
						|
    return _.isEmpty(_.toString(array)) ? [] : transformToArrayID([array]);
 | 
						|
  }
 | 
						|
 | 
						|
  return [];
 | 
						|
};
 | 
						|
 | 
						|
const getModel = (model, plugin) => {
 | 
						|
  return _.get(strapi.plugins, [plugin, 'models', model]) || _.get(strapi, ['models', model]) || undefined;
 | 
						|
};
 | 
						|
 | 
						|
const removeUndefinedKeys = obj => _.pickBy(obj, _.negate(_.isUndefined));
 | 
						|
 | 
						|
module.exports = {
 | 
						|
  findOne: async function (params, populate) {
 | 
						|
    const record = await this
 | 
						|
      .forge({
 | 
						|
        [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
 | 
						|
      })
 | 
						|
      .fetch({
 | 
						|
        withRelated: populate || this.associations.map(x => x.alias)
 | 
						|
      });
 | 
						|
 | 
						|
    const data = record ? record.toJSON() : record;
 | 
						|
 | 
						|
    // Retrieve data manually.
 | 
						|
    if (_.isEmpty(populate)) {
 | 
						|
      const arrayOfPromises = this.associations
 | 
						|
        .filter(association => ['manyMorphToOne', 'manyMorphToMany'].includes(association.nature))
 | 
						|
        .map(() => {
 | 
						|
          return this.morph.forge()
 | 
						|
            .where({
 | 
						|
              [`${this.collectionName}_id`]: getValuePrimaryKey(params, this.primaryKey)
 | 
						|
            })
 | 
						|
            .fetchAll();
 | 
						|
        });
 | 
						|
 | 
						|
      const related = await Promise.all(arrayOfPromises);
 | 
						|
 | 
						|
      related.forEach((value, index) => {
 | 
						|
        data[this.associations[index].alias] = value ? value.toJSON() : value;
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    return data;
 | 
						|
  },
 | 
						|
 | 
						|
  update: async function (params) {
 | 
						|
    const relationUpdates = [];
 | 
						|
    const primaryKeyValue = getValuePrimaryKey(params, this.primaryKey);
 | 
						|
    const response = await module.exports.findOne.call(this, params);
 | 
						|
 | 
						|
    // Only update fields which are on this document.
 | 
						|
    const values = params.parseRelationships === false ? params.values : Object.keys(removeUndefinedKeys(params.values)).reduce((acc, current) => {
 | 
						|
      const property = params.values[current];
 | 
						|
      const association = this.associations.filter(x => x.alias === current)[0];
 | 
						|
      const details = this._attributes[current];
 | 
						|
 | 
						|
      if (!association && _.get(details, 'isVirtual') !== true) {
 | 
						|
        return _.set(acc, current, property);
 | 
						|
      }
 | 
						|
 | 
						|
      const assocModel = getModel(details.model || details.collection, details.plugin);
 | 
						|
      switch (association.nature) {
 | 
						|
        case 'oneWay': {
 | 
						|
          return _.set(acc, current, _.get(property, assocModel.primaryKey, property));
 | 
						|
        }
 | 
						|
        case 'oneToOne': {
 | 
						|
          if (response[current] === property) return acc;
 | 
						|
 | 
						|
          if (_.isNull(property)) {
 | 
						|
            const updatePromise = assocModel.where({
 | 
						|
              [assocModel.primaryKey]: getValuePrimaryKey(response[current], assocModel.primaryKey)
 | 
						|
            }).save({ [details.via]: null }, {method: 'update', patch: true, require: false});
 | 
						|
 | 
						|
            relationUpdates.push(updatePromise);
 | 
						|
            return _.set(acc, current, null);
 | 
						|
          }
 | 
						|
 | 
						|
 | 
						|
          // set old relations to null
 | 
						|
          const updateLink = this.where({ [current]: property })
 | 
						|
            .save({ [current]: null }, {method: 'update', patch: true, require: false})
 | 
						|
            .then(() => {
 | 
						|
              return assocModel
 | 
						|
                .where({ [this.primaryKey]: property })
 | 
						|
                .save({ [details.via] : primaryKeyValue}, {method: 'update', patch: true, require: false});
 | 
						|
            });
 | 
						|
 | 
						|
          // set new relation
 | 
						|
          relationUpdates.push(updateLink);
 | 
						|
          return _.set(acc, current, property);
 | 
						|
        }
 | 
						|
        case 'oneToMany': {
 | 
						|
          // receive array of ids or array of objects with ids
 | 
						|
 | 
						|
          // set relation to null for all the ids not in the list
 | 
						|
          const currentIds = response[current];
 | 
						|
          const toRemove = _.differenceWith(currentIds, property, (a, b) => {
 | 
						|
            return `${a[assocModel.primaryKey] || a}` === `${b[assocModel.primaryKey] || b}`;
 | 
						|
          });
 | 
						|
 | 
						|
          const updatePromise = assocModel
 | 
						|
            .where(assocModel.primaryKey, 'in', toRemove.map(val => val[assocModel.primaryKey]||val))
 | 
						|
            .save({ [details.via] : null }, { method: 'update', patch: true, require: false })
 | 
						|
            .then(() => {
 | 
						|
              return assocModel
 | 
						|
                .where(assocModel.primaryKey, 'in', property.map(val => val[assocModel.primaryKey]||val))
 | 
						|
                .save({ [details.via] : primaryKeyValue }, { method: 'update', patch: true, require: false });
 | 
						|
            });
 | 
						|
 | 
						|
          relationUpdates.push(updatePromise);
 | 
						|
          return acc;
 | 
						|
        }
 | 
						|
        case 'manyToOne': {
 | 
						|
          return _.set(acc, current, _.get(property, assocModel.primaryKey, property));
 | 
						|
        }
 | 
						|
        case 'manyToMany': {
 | 
						|
          const currentValue = transformToArrayID(response[current], association).map(id => id.toString());
 | 
						|
          const storedValue = transformToArrayID(params.values[current], association).map(id => id.toString());
 | 
						|
 | 
						|
          const toAdd = _.difference(storedValue, currentValue);
 | 
						|
          const toRemove = _.difference(currentValue, storedValue);
 | 
						|
 | 
						|
          const collection = this.forge({ [this.primaryKey]: primaryKeyValue })[association.alias]();
 | 
						|
          const updatePromise = collection
 | 
						|
            .detach(toRemove)
 | 
						|
            .then(() => collection.attach(toAdd));
 | 
						|
 | 
						|
          relationUpdates.push(updatePromise);
 | 
						|
          return acc;
 | 
						|
        }
 | 
						|
        case 'manyMorphToMany':
 | 
						|
        case 'manyMorphToOne':
 | 
						|
          // Update the relational array.
 | 
						|
          params.values[current].forEach(obj => {
 | 
						|
            const model = obj.source && obj.source !== 'content-manager' ?
 | 
						|
              strapi.plugins[obj.source].models[obj.ref]:
 | 
						|
              strapi.models[obj.ref];
 | 
						|
 | 
						|
            // Remove existing relationship because only one file
 | 
						|
            // can be related to this field.
 | 
						|
            if (association.nature === 'manyMorphToOne') {
 | 
						|
              relationUpdates.push(
 | 
						|
                module.exports.removeRelationMorph.call(this, {
 | 
						|
                  alias: association.alias,
 | 
						|
                  ref: model.collectionName,
 | 
						|
                  refId: obj.refId,
 | 
						|
                  field: obj.field
 | 
						|
                })
 | 
						|
                  .then(() =>
 | 
						|
                    module.exports.addRelationMorph.call(this, {
 | 
						|
                      id: response[this.primaryKey],
 | 
						|
                      alias: association.alias,
 | 
						|
                      ref: model.collectionName,
 | 
						|
                      refId: obj.refId,
 | 
						|
                      field: obj.field
 | 
						|
                    })
 | 
						|
                  )
 | 
						|
              );
 | 
						|
            } else {
 | 
						|
              relationUpdates.push(module.exports.addRelationMorph.call(this, {
 | 
						|
                id: response[this.primaryKey],
 | 
						|
                alias: association.alias,
 | 
						|
                ref: model.collectionName,
 | 
						|
                refId: obj.refId,
 | 
						|
                field: obj.field
 | 
						|
              }));
 | 
						|
            }
 | 
						|
          });
 | 
						|
          break;
 | 
						|
        case 'oneToManyMorph':
 | 
						|
        case 'manyToManyMorph': {
 | 
						|
          // Compare array of ID to find deleted files.
 | 
						|
          const currentValue = transformToArrayID(response[current], association).map(id => id.toString());
 | 
						|
          const storedValue = transformToArrayID(params.values[current], association).map(id => id.toString());
 | 
						|
 | 
						|
          const toAdd = _.difference(storedValue, currentValue);
 | 
						|
          const toRemove = _.difference(currentValue, storedValue);
 | 
						|
 | 
						|
          const model = getModel(details.collection || details.model, details.plugin);
 | 
						|
 | 
						|
          toAdd.forEach(id => {
 | 
						|
            relationUpdates.push(
 | 
						|
              module.exports.addRelationMorph.call(model, {
 | 
						|
                id,
 | 
						|
                alias: association.via,
 | 
						|
                ref: this.collectionName,
 | 
						|
                refId: response.id,
 | 
						|
                field: association.alias
 | 
						|
              })
 | 
						|
            );
 | 
						|
          });
 | 
						|
 | 
						|
          // Update the relational array.
 | 
						|
          toRemove.forEach(id => {
 | 
						|
            relationUpdates.push(
 | 
						|
              module.exports.removeRelationMorph.call(model, {
 | 
						|
                id,
 | 
						|
                alias: association.via,
 | 
						|
                ref: this.collectionName,
 | 
						|
                refId: response.id,
 | 
						|
                field: association.alias
 | 
						|
              })
 | 
						|
            );
 | 
						|
          });
 | 
						|
          break;
 | 
						|
        }
 | 
						|
        case 'oneMorphToOne':
 | 
						|
        case 'oneMorphToMany':
 | 
						|
          break;
 | 
						|
        default:
 | 
						|
      }
 | 
						|
 | 
						|
      return acc;
 | 
						|
    }, {});
 | 
						|
 | 
						|
    if (!_.isEmpty(values)) {
 | 
						|
      relationUpdates.push(
 | 
						|
        this
 | 
						|
          .forge({
 | 
						|
            [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
 | 
						|
          })
 | 
						|
          .save(values, {
 | 
						|
            patch: true
 | 
						|
          })
 | 
						|
      );
 | 
						|
    } else {
 | 
						|
      relationUpdates.push(Promise.resolve(_.assign(response, params.values)));
 | 
						|
    }
 | 
						|
 | 
						|
    // Update virtuals fields.
 | 
						|
    await Promise.all(relationUpdates);
 | 
						|
 | 
						|
    return await this
 | 
						|
      .forge({
 | 
						|
        [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
 | 
						|
      })
 | 
						|
      .fetch({
 | 
						|
        withRelated: this.associations.map(x => x.alias)
 | 
						|
      });
 | 
						|
  },
 | 
						|
 | 
						|
  addRelation: async function (params) {
 | 
						|
    const association = this.associations.find(x => x.via === params.foreignKey && _.get(params.values, x.alias, null));
 | 
						|
 | 
						|
    if (!association) {
 | 
						|
      // Resolve silently.
 | 
						|
      return Promise.resolve();
 | 
						|
    }
 | 
						|
 | 
						|
    switch (association.nature) {
 | 
						|
      case 'oneToOne':
 | 
						|
      case 'oneToMany':
 | 
						|
      case 'manyToOne':
 | 
						|
        return module.exports.update.call(this, params);
 | 
						|
      case 'manyToMany':
 | 
						|
        return this.forge({
 | 
						|
          [this.primaryKey]: params[this.primaryKey]
 | 
						|
        })[association.alias]().attach(params.values[association.alias]);
 | 
						|
      default:
 | 
						|
        // Resolve silently.
 | 
						|
        return Promise.resolve();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  removeRelation: async function (params) {
 | 
						|
    const association = this.associations.find(x => x.via === params.foreignKey && _.get(params.values, x.alias, null));
 | 
						|
 | 
						|
    if (!association) {
 | 
						|
      // Resolve silently.
 | 
						|
      return Promise.resolve();
 | 
						|
    }
 | 
						|
 | 
						|
    switch (association.nature) {
 | 
						|
      case 'oneToOne':
 | 
						|
      case 'oneToMany':
 | 
						|
      case 'manyToOne':
 | 
						|
        return module.exports.update.call(this, params);
 | 
						|
      case 'manyToMany':
 | 
						|
        return this.forge({
 | 
						|
          [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
 | 
						|
        })[association.alias]().detach(params.values[association.alias]);
 | 
						|
      default:
 | 
						|
        // Resolve silently.
 | 
						|
        return Promise.resolve();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  addRelationMorph: async function (params) {
 | 
						|
    const record = await this.morph.forge()
 | 
						|
      .where({
 | 
						|
        [`${this.collectionName}_id`]: params.id,
 | 
						|
        [`${params.alias}_id`]: params.refId,
 | 
						|
        [`${params.alias}_type`]: params.ref,
 | 
						|
        field: params.field
 | 
						|
      })
 | 
						|
      .fetch({
 | 
						|
        withRelated: this.associations.map(x => x.alias)
 | 
						|
      });
 | 
						|
 | 
						|
    const entry = record ? record.toJSON() : record;
 | 
						|
 | 
						|
    if (entry) {
 | 
						|
      return Promise.resolve();
 | 
						|
    }
 | 
						|
 | 
						|
    return await this.morph.forge({
 | 
						|
      [`${this.collectionName}_id`]: params.id,
 | 
						|
      [`${params.alias}_id`]: params.refId,
 | 
						|
      [`${params.alias}_type`]: params.ref,
 | 
						|
      field: params.field
 | 
						|
    })
 | 
						|
      .save();
 | 
						|
  },
 | 
						|
 | 
						|
  removeRelationMorph: async function (params) {
 | 
						|
    return await this.morph.forge()
 | 
						|
      .where(_.omitBy({
 | 
						|
        [`${this.collectionName}_id`]: params.id,
 | 
						|
        [`${params.alias}_id`]: params.refId,
 | 
						|
        [`${params.alias}_type`]: params.ref,
 | 
						|
        field: params.field
 | 
						|
      }, _.isUndefined))
 | 
						|
      .destroy();
 | 
						|
  }
 | 
						|
};
 |