mirror of
https://github.com/strapi/strapi.git
synced 2025-07-31 04:45:54 +00:00
429 lines
14 KiB
JavaScript
429 lines
14 KiB
JavaScript
/**
|
|
* Module dependencies
|
|
*/
|
|
|
|
// Public node modules.
|
|
const _ = require('lodash');
|
|
const mongoose = require('mongoose');
|
|
|
|
// Utils
|
|
const {
|
|
models: { getValuePrimaryKey },
|
|
} = require('strapi-utils');
|
|
|
|
const getModel = function(model, plugin) {
|
|
return (
|
|
_.get(strapi.plugins, [plugin, 'models', model]) ||
|
|
_.get(strapi, ['models', model]) ||
|
|
undefined
|
|
);
|
|
};
|
|
|
|
const removeUndefinedKeys = obj => _.pickBy(obj, _.negate(_.isUndefined));
|
|
|
|
module.exports = {
|
|
update: async function(params) {
|
|
const relationUpdates = [];
|
|
const populate = this.associations.map(x => x.alias);
|
|
const primaryKeyValue = getValuePrimaryKey(params, this.primaryKey);
|
|
|
|
const entry = await this.findOne({ [this.primaryKey]: primaryKeyValue })
|
|
.populate(populate)
|
|
.lean();
|
|
|
|
// Only update fields which are on this document.
|
|
const values =
|
|
params.parseRelationships === false
|
|
? params.values
|
|
: Object.keys(removeUndefinedKeys(params.values)).reduce(
|
|
(acc, attribute) => {
|
|
const currentValue = entry[attribute];
|
|
const newValue = params.values[attribute];
|
|
|
|
const association = this.associations.find(
|
|
x => x.alias === attribute
|
|
);
|
|
|
|
const details = this._attributes[attribute];
|
|
|
|
// set simple attributes
|
|
if (!association && _.get(details, 'isVirtual') !== true) {
|
|
return _.set(acc, attribute, newValue);
|
|
}
|
|
|
|
const assocModel = getModel(
|
|
details.model || details.collection,
|
|
details.plugin
|
|
);
|
|
|
|
switch (association.nature) {
|
|
case 'oneWay': {
|
|
return _.set(
|
|
acc,
|
|
attribute,
|
|
_.get(newValue, assocModel.primaryKey, newValue)
|
|
);
|
|
}
|
|
case 'oneToOne': {
|
|
// if value is the same don't do anything
|
|
if (currentValue === newValue) return acc;
|
|
|
|
// if the value is null, set field to null on both sides
|
|
if (_.isNull(newValue)) {
|
|
const updatePromise = assocModel.updateOne(
|
|
{
|
|
[assocModel.primaryKey]: getValuePrimaryKey(
|
|
currentValue,
|
|
assocModel.primaryKey
|
|
),
|
|
},
|
|
{ [details.via]: null }
|
|
);
|
|
|
|
relationUpdates.push(updatePromise);
|
|
return _.set(acc, attribute, null);
|
|
}
|
|
|
|
// set old relations to null
|
|
const updateLink = this.updateOne(
|
|
{ [attribute]: new mongoose.Types.ObjectId(newValue) },
|
|
{ [attribute]: null }
|
|
).then(() => {
|
|
return assocModel.updateOne(
|
|
{
|
|
[this.primaryKey]: new mongoose.Types.ObjectId(
|
|
newValue
|
|
),
|
|
},
|
|
{ [details.via]: primaryKeyValue }
|
|
);
|
|
});
|
|
|
|
// set new relation
|
|
relationUpdates.push(updateLink);
|
|
return _.set(acc, attribute, newValue);
|
|
}
|
|
case 'oneToMany': {
|
|
// set relation to null for all the ids not in the list
|
|
const attributeIds = currentValue;
|
|
const toRemove = _.differenceWith(
|
|
attributeIds,
|
|
newValue,
|
|
(a, b) => {
|
|
return (
|
|
`${a[assocModel.primaryKey] || a}` ===
|
|
`${b[assocModel.primaryKey] || b}`
|
|
);
|
|
}
|
|
);
|
|
|
|
const updatePromise = assocModel
|
|
.updateMany(
|
|
{
|
|
[assocModel.primaryKey]: {
|
|
$in: toRemove.map(
|
|
val =>
|
|
new mongoose.Types.ObjectId(
|
|
val[assocModel.primaryKey] || val
|
|
)
|
|
),
|
|
},
|
|
},
|
|
{ [details.via]: null }
|
|
)
|
|
.then(() => {
|
|
return assocModel.updateMany(
|
|
{
|
|
[assocModel.primaryKey]: {
|
|
$in: newValue.map(
|
|
val =>
|
|
new mongoose.Types.ObjectId(
|
|
val[assocModel.primaryKey] || val
|
|
)
|
|
),
|
|
},
|
|
},
|
|
{ [details.via]: primaryKeyValue }
|
|
);
|
|
});
|
|
|
|
relationUpdates.push(updatePromise);
|
|
return acc;
|
|
}
|
|
case 'manyToOne': {
|
|
return _.set(
|
|
acc,
|
|
attribute,
|
|
_.get(newValue, assocModel.primaryKey, newValue)
|
|
);
|
|
}
|
|
case 'manyWay':
|
|
case 'manyToMany': {
|
|
if (association.dominant) {
|
|
return _.set(
|
|
acc,
|
|
attribute,
|
|
newValue
|
|
? newValue.map(val => val[assocModel.primaryKey] || val)
|
|
: newValue
|
|
);
|
|
}
|
|
|
|
const updatePomise = assocModel
|
|
.updateMany(
|
|
{
|
|
[assocModel.primaryKey]: {
|
|
$in: currentValue.map(
|
|
val =>
|
|
new mongoose.Types.ObjectId(
|
|
val[assocModel.primaryKey] || val
|
|
)
|
|
),
|
|
},
|
|
},
|
|
{
|
|
$pull: {
|
|
[association.via]: new mongoose.Types.ObjectId(
|
|
primaryKeyValue
|
|
),
|
|
},
|
|
}
|
|
)
|
|
.then(() => {
|
|
return assocModel.updateMany(
|
|
{
|
|
[assocModel.primaryKey]: {
|
|
$in: newValue
|
|
? newValue.map(
|
|
val =>
|
|
new mongoose.Types.ObjectId(
|
|
val[assocModel.primaryKey] || val
|
|
)
|
|
)
|
|
: newValue,
|
|
},
|
|
},
|
|
{
|
|
$addToSet: { [association.via]: [primaryKeyValue] },
|
|
}
|
|
);
|
|
});
|
|
|
|
relationUpdates.push(updatePomise);
|
|
return acc;
|
|
}
|
|
case 'manyMorphToMany':
|
|
case 'manyMorphToOne': {
|
|
// Update the relational array.
|
|
|
|
newValue.forEach(obj => {
|
|
const refModel = strapi.getModel(obj.ref, obj.source);
|
|
|
|
const createRelation = () => {
|
|
return module.exports.addRelationMorph.call(this, {
|
|
id: entry[this.primaryKey],
|
|
alias: association.alias,
|
|
ref: obj.kind || refModel.globalId,
|
|
refId: new mongoose.Types.ObjectId(obj.refId),
|
|
field: obj.field,
|
|
filter: association.filter,
|
|
});
|
|
};
|
|
|
|
// Clear relations to refModel
|
|
const reverseAssoc = refModel.associations.find(
|
|
assoc => assoc.alias === obj.field
|
|
);
|
|
if (
|
|
reverseAssoc &&
|
|
reverseAssoc.nature === 'oneToManyMorph'
|
|
) {
|
|
relationUpdates.push(
|
|
module.exports.removeRelationMorph
|
|
.call(this, {
|
|
alias: association.alias,
|
|
ref: obj.kind || refModel.globalId,
|
|
refId: new mongoose.Types.ObjectId(obj.refId),
|
|
field: obj.field,
|
|
filter: association.filter,
|
|
})
|
|
.then(createRelation)
|
|
);
|
|
} else {
|
|
relationUpdates.push(createRelation());
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
case 'oneToManyMorph':
|
|
case 'manyToManyMorph': {
|
|
const transformToArrayID = array => {
|
|
if (_.isArray(array)) {
|
|
return array.map(value => {
|
|
if (_.isPlainObject(value)) {
|
|
return getValuePrimaryKey(value, this.primaryKey);
|
|
}
|
|
|
|
return value;
|
|
});
|
|
}
|
|
|
|
if (
|
|
association.type === 'model' ||
|
|
(association.type === 'collection' && _.isObject(array))
|
|
) {
|
|
return _.isEmpty(array)
|
|
? []
|
|
: transformToArrayID([array]);
|
|
}
|
|
|
|
return [];
|
|
};
|
|
|
|
// Compare array of ID to find deleted files.
|
|
const attributeValue = transformToArrayID(currentValue).map(
|
|
id => id.toString()
|
|
);
|
|
const storedValue = transformToArrayID(newValue).map(id =>
|
|
id.toString()
|
|
);
|
|
|
|
const toAdd = _.difference(storedValue, attributeValue);
|
|
const toRemove = _.difference(attributeValue, storedValue);
|
|
|
|
const model = getModel(
|
|
details.model || details.collection,
|
|
details.plugin
|
|
);
|
|
|
|
// Remove relations in the other side.
|
|
toAdd.forEach(id => {
|
|
relationUpdates.push(
|
|
module.exports.addRelationMorph.call(model, {
|
|
id,
|
|
alias: association.via,
|
|
ref: this.globalId,
|
|
refId: entry._id,
|
|
field: association.alias,
|
|
filter: association.filter,
|
|
})
|
|
);
|
|
});
|
|
|
|
// Remove relations in the other side.
|
|
toRemove.forEach(id => {
|
|
relationUpdates.push(
|
|
module.exports.removeRelationMorph.call(model, {
|
|
id,
|
|
alias: association.via,
|
|
ref: this.globalId,
|
|
refId: entry._id,
|
|
field: association.alias,
|
|
})
|
|
);
|
|
});
|
|
break;
|
|
}
|
|
case 'oneMorphToOne':
|
|
case 'oneMorphToMany':
|
|
break;
|
|
default:
|
|
}
|
|
|
|
return acc;
|
|
},
|
|
{}
|
|
);
|
|
|
|
// Update virtuals fields.
|
|
await Promise.all(relationUpdates).then(() =>
|
|
this.updateOne({ [this.primaryKey]: primaryKeyValue }, values, {
|
|
strict: false,
|
|
})
|
|
);
|
|
|
|
const updatedEntity = await this.findOne({
|
|
[this.primaryKey]: primaryKeyValue,
|
|
}).populate(populate);
|
|
|
|
return updatedEntity && updatedEntity.toObject
|
|
? updatedEntity.toObject()
|
|
: updatedEntity;
|
|
},
|
|
|
|
async addRelationMorph(params) {
|
|
const { alias, id } = params;
|
|
|
|
let entry = await this.findOne({
|
|
[this.primaryKey]: id,
|
|
});
|
|
|
|
if (!entry) return Promise.resolve();
|
|
|
|
// if association already exists ignore
|
|
const relationExists = entry[alias].find(obj => {
|
|
if (
|
|
obj.kind === params.ref &&
|
|
obj.ref.toString() === params.refId.toString() &&
|
|
obj.field === params.field
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
if (relationExists) return Promise.resolve();
|
|
|
|
entry[alias].push({
|
|
ref: new mongoose.Types.ObjectId(params.refId),
|
|
kind: params.ref,
|
|
[params.filter]: params.field,
|
|
});
|
|
|
|
await entry.save();
|
|
},
|
|
|
|
async removeRelationMorph(params) {
|
|
const { alias } = params;
|
|
|
|
let opts;
|
|
// if entry id is provided simply query it
|
|
if (params.id) {
|
|
opts = {
|
|
_id: params.id,
|
|
};
|
|
} else {
|
|
opts = {
|
|
[alias]: {
|
|
$elemMatch: {
|
|
ref: params.refId,
|
|
kind: params.ref,
|
|
[params.filter]: params.field,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
const entries = await this.find(opts);
|
|
|
|
const updates = entries.map(entry => {
|
|
entry[alias] = entry[alias].filter(obj => {
|
|
if (
|
|
obj.kind === params.ref &&
|
|
obj.ref.toString() === params.refId.toString() &&
|
|
obj.field === params.field
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
return entry.save();
|
|
});
|
|
|
|
await Promise.all(updates);
|
|
},
|
|
};
|