387 lines
12 KiB
JavaScript
Raw Normal View History

/**
* Module dependencies
*/
// Public node modules.
const _ = require('lodash');
const mongoose = require('mongoose');
// Utils
2019-07-08 11:06:11 +02:00
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 = {
2019-07-08 11:06:11 +02:00
update: async function(params) {
const relationUpdates = [];
2019-08-01 14:40:02 +02:00
const populate = this.associations.map(x => x.alias);
const primaryKeyValue = getValuePrimaryKey(params, this.primaryKey);
const entry = await this.findOne({ [this.primaryKey]: primaryKeyValue })
.populate(populate)
2018-05-17 14:56:53 +02:00
.lean();
// Only update fields which are on this document.
2019-07-08 11:06:11 +02:00
const values =
params.parseRelationships === false
? params.values
: Object.keys(removeUndefinedKeys(params.values)).reduce(
(acc, attribute) => {
const currentValue = entry[attribute];
const newValue = params.values[attribute];
2019-07-08 11:06:11 +02:00
const association = this.associations.find(
x => x.alias === attribute
2019-07-08 11:06:11 +02:00
);
const details = this._attributes[attribute];
2019-07-08 11:06:11 +02:00
// set simple attributes
if (!association && _.get(details, 'isVirtual') !== true) {
return _.set(acc, attribute, newValue);
2019-07-08 11:06:11 +02:00
}
const assocModel = getModel(
details.model || details.collection,
details.plugin
);
2019-07-08 11:06:11 +02:00
switch (association.nature) {
case 'oneWay': {
return _.set(
acc,
attribute,
_.get(newValue, assocModel.primaryKey, newValue)
2019-07-08 11:06:11 +02:00
);
}
2019-07-08 11:06:11 +02:00
case 'oneToOne': {
// if value is the same don't do anything
if (currentValue === newValue) return acc;
2019-07-08 11:06:11 +02:00
// if the value is null, set field to null on both sides
if (_.isNull(newValue)) {
2019-07-08 11:06:11 +02:00
const updatePromise = assocModel.updateOne(
{
[assocModel.primaryKey]: getValuePrimaryKey(
currentValue,
2019-07-08 11:06:11 +02:00
assocModel.primaryKey
),
},
{ [details.via]: null }
);
relationUpdates.push(updatePromise);
return _.set(acc, attribute, null);
2019-07-08 11:06:11 +02:00
}
// set old relations to null
const updateLink = this.updateOne(
{ [attribute]: new mongoose.Types.ObjectId(newValue) },
{ [attribute]: null }
2019-07-08 11:06:11 +02:00
).then(() => {
return assocModel.updateOne(
{
[this.primaryKey]: new mongoose.Types.ObjectId(
newValue
2019-07-08 11:06:11 +02:00
),
},
{ [details.via]: primaryKeyValue }
);
});
// set new relation
relationUpdates.push(updateLink);
return _.set(acc, attribute, newValue);
}
2019-07-08 11:06:11 +02:00
case 'oneToMany': {
// set relation to null for all the ids not in the list
const attributeIds = currentValue;
2019-07-08 11:06:11 +02:00
const toRemove = _.differenceWith(
attributeIds,
newValue,
2019-07-08 11:06:11 +02:00
(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(
2019-07-08 11:06:11 +02:00
val =>
new mongoose.Types.ObjectId(
val[assocModel.primaryKey] || val
)
),
},
},
{ [details.via]: primaryKeyValue }
);
});
relationUpdates.push(updatePromise);
return acc;
}
2019-07-08 11:06:11 +02:00
case 'manyToOne': {
return _.set(
acc,
attribute,
_.get(newValue, assocModel.primaryKey, newValue)
2019-07-08 11:06:11 +02:00
);
}
case 'manyWay':
case 'manyToMany': {
if (association.dominant) {
return _.set(
acc,
attribute,
newValue
? newValue.map(val => val[assocModel.primaryKey] || val)
: newValue
2019-07-08 11:06:11 +02:00
);
}
const updatePomise = assocModel
.updateMany(
{
[assocModel.primaryKey]: {
$in: currentValue.map(
2019-07-08 11:06:11 +02:00
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(
2019-10-01 17:56:52 +02:00
val =>
new mongoose.Types.ObjectId(
val[assocModel.primaryKey] || val
)
2019-07-08 11:06:11 +02:00
)
: newValue,
2019-07-08 11:06:11 +02:00
},
},
{
$addToSet: { [association.via]: [primaryKeyValue] },
}
);
});
relationUpdates.push(updatePomise);
return acc;
}
case 'manyMorphToMany':
2019-08-01 14:40:02 +02:00
case 'manyMorphToOne': {
2019-07-08 11:06:11 +02:00
// Update the relational array.
acc[attribute] = newValue.map(obj => {
2019-08-01 14:40:02 +02:00
const refModel = strapi.getModel(obj.ref, obj.source);
2019-07-08 11:06:11 +02:00
return {
2019-08-01 14:40:02 +02:00
ref: new mongoose.Types.ObjectId(obj.refId),
kind: obj.kind || refModel.globalId,
2019-07-08 11:06:11 +02:00
[association.filter]: obj.field,
};
});
break;
2019-08-01 14:40:02 +02:00
}
2019-07-08 11:06:11 +02:00
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 =>
2019-07-08 11:06:11 +02:00
id.toString()
);
const toAdd = _.difference(storedValue, attributeValue);
const toRemove = _.difference(attributeValue, storedValue);
2019-07-08 11:06:11 +02:00
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,
2019-07-08 11:06:11 +02:00
field: association.alias,
filter: association.filter,
2019-07-08 11:06:11 +02:00
})
);
});
// 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,
2019-07-08 11:06:11 +02:00
field: association.alias,
})
);
});
break;
}
case 'oneMorphToOne':
case 'oneMorphToMany':
break;
default:
}
2019-07-08 11:06:11 +02:00
return acc;
},
{}
);
// Update virtuals fields.
2019-07-08 11:06:11 +02:00
await Promise.all(relationUpdates).then(() =>
this.updateOne({ [this.primaryKey]: primaryKeyValue }, values, {
strict: false,
})
2019-07-08 11:06:11 +02:00
);
const updatedEntity = await this.findOne({
[this.primaryKey]: primaryKeyValue,
}).populate(populate);
2019-08-01 15:59:21 +02:00
return updatedEntity && updatedEntity.toObject
? updatedEntity.toObject()
: updatedEntity;
},
async addRelationMorph(params) {
const { alias, id } = params;
2019-07-08 11:06:11 +02:00
let entry = await this.findOne({
[this.primaryKey]: id,
2019-07-08 11:06:11 +02:00
});
2018-05-17 14:56:53 +02:00
if (!entry) {
throw new Error(
`Relation ${params.field} cannot be created because the target entity doesnt exist`
2019-07-08 11:06:11 +02:00
);
}
// if association already exists ignore
const relationExists = entry[alias].find(obj => {
2019-07-08 11:06:11 +02:00
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, id } = params;
const entry = await this.findOne({
[this.primaryKey]: id,
});
if (!entry) return Promise.resolve();
// Filter the association array and remove the association.
entry[alias] = entry[alias].filter(obj => {
2019-07-08 11:06:11 +02:00
if (
obj.kind === params.ref &&
obj.ref.toString() === params.refId.toString() &&
obj.field === params.field
) {
return false;
}
return true;
});
entry.save();
2019-07-08 11:06:11 +02:00
},
2018-05-16 12:07:02 +02:00
};