Merge pull request #5553 from strapi/media-lib/ordering

Keep media order set on creation
This commit is contained in:
Alexandre BODIN 2020-03-23 11:21:57 +01:00 committed by GitHub
commit dbaa1848dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1116 additions and 1228 deletions

View File

@ -44,7 +44,6 @@
"max": 35.12
},
"address": {
"required": true,
"model": "address"
},
"cover": {

View File

@ -416,6 +416,9 @@ module.exports = async ({ ORM, loadedModel, definition, connection, model }) =>
[definition.attributes[morphRelation.alias].filter]: {
type: 'text',
},
order: {
type: 'integer',
},
};
if (connection.options && connection.options.autoMigration !== false) {
@ -423,7 +426,7 @@ module.exports = async ({ ORM, loadedModel, definition, connection, model }) =>
}
}
// Equilize many to many releations
// Equilize many to many relations
const manyRelations = definition.associations.filter(({ nature }) =>
['manyToMany', 'manyWay'].includes(nature)
);

View File

@ -142,7 +142,11 @@ module.exports = ({ models, target }, ctx) => {
}
const { nature, verbose } =
utilsModels.getNature(details, name, undefined, model.toLowerCase()) || {};
utilsModels.getNature({
attribute: details,
attributeName: name,
modelName: model.toLowerCase(),
}) || {};
// Build associations key
utilsModels.defineAssociations(model.toLowerCase(), definition, details, name);
@ -302,6 +306,7 @@ module.exports = ({ models, target }, ctx) => {
: strapi.models[details.model];
const globalId = `${model.collectionName}_morph`;
const filter = _.get(model, ['attributes', details.via, 'filter'], 'field');
loadedModel[name] = function() {
return this.morphOne(
@ -309,7 +314,7 @@ module.exports = ({ models, target }, ctx) => {
details.via,
`${definition.collectionName}`
).query(qb => {
qb.where(_.get(model, ['attributes', details.via, 'filter'], 'field'), name);
qb.where(filter, name);
});
};
break;
@ -320,6 +325,7 @@ module.exports = ({ models, target }, ctx) => {
: strapi.models[details.collection];
const globalId = `${collection.collectionName}_morph`;
const filter = _.get(model, ['attributes', details.via, 'filter'], 'field');
loadedModel[name] = function() {
return this.morphMany(
@ -327,7 +333,7 @@ module.exports = ({ models, target }, ctx) => {
details.via,
`${definition.collectionName}`
).query(qb => {
qb.where(_.get(model, ['attributes', details.via, 'filter'], 'field'), name);
qb.where(filter, name).orderBy('order');
});
};
break;
@ -650,6 +656,7 @@ module.exports = ({ models, target }, ctx) => {
// Push attributes to be aware of model schema.
target[model]._attributes = definition.attributes;
target[model].updateRelations = relations.update;
target[model].deleteRelations = relations.deleteRelations;
await buildDatabaseSchema({
ORM,

View File

@ -132,26 +132,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
throw err;
}
const values = {};
model.associations.map(association => {
switch (association.nature) {
case 'oneWay':
case 'oneToOne':
case 'manyToOne':
case 'oneToManyMorph':
values[association.alias] = null;
break;
case 'manyWay':
case 'oneToMany':
case 'manyToMany':
case 'manyToManyMorph':
values[association.alias] = [];
break;
default:
}
});
await model.updateRelations({ [model.primaryKey]: id, values }, { transacting });
await model.deleteRelations(id, { transacting });
const runDelete = async trx => {
await deleteComponents(entry, { transacting: trx });

View File

@ -12,27 +12,15 @@ const {
models: { getValuePrimaryKey },
} = require('strapi-utils');
const transformToArrayID = (array, association) => {
const transformToArrayID = array => {
if (_.isArray(array)) {
array = array.map(value => {
if (_.isPlainObject(value)) {
return value._id || value.id || false;
return array
.map(value => _.get(value, 'id') || value)
.filter(n => n)
.map(val => _.toString(val));
}
return value;
});
return array.filter(n => n);
}
if (
association.type === 'model' ||
(association.type === 'collection' && _.isObject(array))
) {
return _.isEmpty(_.toString(array)) ? [] : transformToArrayID([array]);
}
return [];
return transformToArrayID([array]);
};
const getModel = (model, plugin) => {
@ -45,6 +33,39 @@ const getModel = (model, plugin) => {
const removeUndefinedKeys = obj => _.pickBy(obj, _.negate(_.isUndefined));
const addRelationMorph = async (model, { params, transacting } = {}) => {
return await model.morph.forge().save(
{
[`${model.collectionName}_id`]: params.id,
[`${params.alias}_id`]: params.refId,
[`${params.alias}_type`]: params.ref,
field: params.field,
order: params.order,
},
{ transacting }
);
};
const removeRelationMorph = async (model, { params, transacting } = {}) => {
return await model.morph
.forge()
.where(
_.omitBy(
{
[`${model.collectionName}_id`]: params.id,
[`${params.alias}_id`]: params.refId,
[`${params.alias}_type`]: params.ref,
field: params.field,
},
_.isUndefined
)
)
.destroy({
require: false,
transacting,
});
};
module.exports = {
async findOne(params, populate, { transacting } = {}) {
const record = await this.forge({
@ -59,17 +80,12 @@ module.exports = {
// Retrieve data manually.
if (_.isEmpty(populate)) {
const arrayOfPromises = this.associations
.filter(association =>
['manyMorphToOne', 'manyMorphToMany'].includes(association.nature)
)
.filter(association => ['manyMorphToOne', 'manyMorphToMany'].includes(association.nature))
.map(() => {
return this.morph
.forge()
.where({
[`${this.collectionName}_id`]: getValuePrimaryKey(
params,
this.primaryKey
),
[`${this.collectionName}_id`]: getValuePrimaryKey(params, this.primaryKey),
})
.fetchAll({
transacting,
@ -94,32 +110,20 @@ module.exports = {
});
// Only update fields which are on this document.
const values =
params.parseRelationships === false
? params.values
: Object.keys(removeUndefinedKeys(params.values)).reduce(
(acc, current) => {
const 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 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
);
const assocModel = getModel(details.model || details.collection, details.plugin);
switch (association.nature) {
case 'oneWay': {
return _.set(
acc,
current,
_.get(property, assocModel.primaryKey, property)
);
return _.set(acc, current, _.get(property, assocModel.primaryKey, property));
}
case 'oneToOne': {
if (response[current] === property) return acc;
@ -158,9 +162,7 @@ module.exports = {
}
)
.then(() => {
return assocModel
.where({ [this.primaryKey]: property })
.save(
return assocModel.where({ [this.primaryKey]: property }).save(
{ [details.via]: primaryKeyValue },
{
method: 'update',
@ -180,16 +182,9 @@ module.exports = {
// 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 toRemove = _.differenceWith(currentIds, property, (a, b) => {
return `${a[assocModel.primaryKey] || a}` === `${b[assocModel.primaryKey] || b}`;
});
const updatePromise = assocModel
.where(
@ -228,25 +223,15 @@ module.exports = {
return acc;
}
case 'manyToOne': {
return _.set(
acc,
current,
_.get(property, assocModel.primaryKey, property)
);
return _.set(acc, current, _.get(property, assocModel.primaryKey, property));
}
case 'manyWay':
case 'manyToMany': {
const currentValue = transformToArrayID(
response[current],
association
).map(id => id.toString());
const storedValue = transformToArrayID(
params.values[current],
association
).map(id => id.toString());
const storedValue = transformToArrayID(response[current]);
const currentValue = transformToArrayID(params.values[current]);
const toAdd = _.difference(storedValue, currentValue);
const toRemove = _.difference(currentValue, storedValue);
const toAdd = _.difference(currentValue, storedValue);
const toRemove = _.difference(storedValue, currentValue);
const collection = this.forge({
[this.primaryKey]: primaryKeyValue,
@ -259,122 +244,123 @@ module.exports = {
relationUpdates.push(updatePromise);
return acc;
}
// media -> model
case 'manyMorphToMany':
case 'manyMorphToOne':
case 'manyMorphToOne': {
// Update the relational array.
params.values[current].forEach(obj => {
const model = strapi.getModel(
const refs = params.values[current];
if (Array.isArray(refs) && refs.length === 0) {
// clear related
relationUpdates.push(
removeRelationMorph(this, { params: { id: primaryKeyValue }, transacting })
);
break;
}
refs.forEach(obj => {
const targetModel = strapi.getModel(
obj.ref,
obj.source && obj.source !== 'content-manager'
? obj.source
: null
obj.source !== 'content-manager' ? obj.source : null
);
const reverseAssoc = model.associations.find(
assoc => assoc.alias === obj.field
);
const reverseAssoc = targetModel.associations.find(assoc => assoc.alias === obj.field);
// Remove existing relationship because only one file
// can be related to this field.
if (
reverseAssoc &&
reverseAssoc.nature === 'oneToManyMorph'
) {
if (reverseAssoc && reverseAssoc.nature === 'oneToManyMorph') {
relationUpdates.push(
module.exports.removeRelationMorph
.call(
this,
{
removeRelationMorph(this, {
params: {
alias: association.alias,
ref: model.collectionName,
ref: targetModel.collectionName,
refId: obj.refId,
field: obj.field,
},
{ transacting }
)
.then(() =>
module.exports.addRelationMorph.call(
this,
{
transacting,
}).then(() =>
addRelationMorph(this, {
params: {
id: response[this.primaryKey],
alias: association.alias,
ref: model.collectionName,
ref: targetModel.collectionName,
refId: obj.refId,
field: obj.field,
order: 1,
},
{ transacting }
)
)
);
} else {
relationUpdates.push(
module.exports.addRelationMorph.call(
this,
{
id: response[this.primaryKey],
alias: association.alias,
ref: model.collectionName,
refId: obj.refId,
field: obj.field,
},
{ transacting }
transacting,
})
)
);
return;
}
const addRelation = async () => {
const maxOrder = await this.morph
.query(qb => {
qb.max('order as order').where({
[`${association.alias}_id`]: obj.refId,
[`${association.alias}_type`]: targetModel.collectionName,
field: obj.field,
});
})
.fetch({ transacting });
const { order = 0 } = maxOrder.toJSON();
await addRelationMorph(this, {
params: {
id: response[this.primaryKey],
alias: association.alias,
ref: targetModel.collectionName,
refId: obj.refId,
field: obj.field,
order: order + 1,
},
transacting,
});
};
relationUpdates.push(addRelation());
});
break;
}
// model -> media
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 currentValue = transformToArrayID(params.values[current]);
const toAdd = _.difference(storedValue, currentValue);
const toRemove = _.difference(currentValue, storedValue);
const model = getModel(details.collection || details.model, details.plugin);
const model = getModel(
details.collection || details.model,
details.plugin
);
toAdd.forEach(id => {
relationUpdates.push(
module.exports.addRelationMorph.call(
model,
{
id,
const promise = removeRelationMorph(model, {
params: {
alias: association.via,
ref: this.collectionName,
refId: response.id,
field: association.alias,
},
{ transacting }
)
);
});
// Update the relational array.
toRemove.forEach(id => {
relationUpdates.push(
module.exports.removeRelationMorph.call(
model,
{
transacting,
}).then(() => {
return Promise.all(
currentValue.map((id, idx) => {
return addRelationMorph(model, {
params: {
id,
alias: association.via,
ref: this.collectionName,
refId: response.id,
field: association.alias,
order: idx + 1,
},
{ transacting }
)
transacting,
});
})
);
});
relationUpdates.push(promise);
break;
}
case 'oneMorphToOne':
@ -385,9 +371,7 @@ module.exports = {
}
return acc;
},
{}
);
}, {});
await Promise.all(relationUpdates);
@ -410,52 +394,29 @@ module.exports = {
return result && result.toJSON ? result.toJSON() : result;
},
async addRelationMorph(params, { transacting } = {}) {
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({
transacting,
});
deleteRelations(id, { transacting }) {
const values = {};
const entry = record ? record.toJSON() : record;
if (entry) {
return Promise.resolve();
this.associations.map(association => {
switch (association.nature) {
case 'oneWay':
case 'oneToOne':
case 'manyToOne':
case 'oneToManyMorph':
values[association.alias] = null;
break;
case 'manyWay':
case 'oneToMany':
case 'manyToMany':
case 'manyToManyMorph':
case 'manyMorphToMany':
case 'manyMorphToOne':
values[association.alias] = [];
break;
default:
}
return await this.morph
.forge({
[`${this.collectionName}_id`]: params.id,
[`${params.alias}_id`]: params.refId,
[`${params.alias}_type`]: params.ref,
field: params.field,
})
.save(null, { transacting });
},
async removeRelationMorph(params, { transacting } = {}) {
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({
require: false,
transacting,
});
return this.updateRelations({ [this.primaryKey]: id, values }, { transacting });
},
};

View File

@ -219,6 +219,7 @@ module.exports = ({ models, target }, ctx) => {
virtuals: true,
transform: function(doc, returned) {
// Remover $numberDecimal nested property.
Object.keys(returned)
.filter(key => returned[key] instanceof mongoose.Types.Decimal128)
.forEach(key => {
@ -239,11 +240,13 @@ module.exports = ({ models, target }, ctx) => {
break;
case 'manyMorphToMany':
case 'manyMorphToOne':
case 'manyMorphToOne': {
returned[association.alias] = returned[association.alias].map(obj =>
refToStrapiRef(obj)
);
break;
}
default:
}
}
@ -297,6 +300,7 @@ module.exports = ({ models, target }, ctx) => {
// Push attributes to be aware of model schema.
target[model]._attributes = definition.attributes;
target[model].updateRelations = relations.update;
target[model].deleteRelations = relations.deleteRelations;
});
};
@ -308,20 +312,8 @@ const createOnFetchPopulateFn = ({ morphAssociations, componentAttributes, defin
const { alias, nature } = association;
if (['oneToManyMorph', 'manyToManyMorph'].includes(nature)) {
this.populate({
path: alias,
match: {
[`${association.via}.${association.filter}`]: association.alias,
[`${association.via}.kind`]: definition.globalId,
},
options: {
sort: '-createdAt',
},
});
return;
}
if (populatedPaths.includes(alias)) {
this.populate(alias);
} else if (populatedPaths.includes(alias)) {
_.set(this._mongooseOptions.populate, [alias, 'path'], `${alias}.ref`);
}
});
@ -343,48 +335,52 @@ const createOnFetchPopulateFn = ({ morphAssociations, componentAttributes, defin
const buildRelation = ({ definition, model, instance, attribute, name }) => {
const { nature, verbose } =
utilsModels.getNature(attribute, name, undefined, model.toLowerCase()) || {};
utilsModels.getNature({
attribute,
attributeName: name,
modelName: model.toLowerCase(),
}) || {};
// Build associations key
utilsModels.defineAssociations(model.toLowerCase(), definition, attribute, name);
const getRef = (name, plugin) => {
return plugin ? strapi.plugins[plugin].models[name].globalId : strapi.models[name].globalId;
};
const setField = (name, val) => {
definition.loadedModel[name] = val;
};
const { ObjectId } = instance.Schema.Types;
switch (verbose) {
case 'hasOne': {
const ref = attribute.plugin
? strapi.plugins[attribute.plugin].models[attribute.model].globalId
: strapi.models[attribute.model].globalId;
const ref = getRef(attribute.model, attribute.plugin);
setField(name, { type: ObjectId, ref });
definition.loadedModel[name] = {
type: instance.Schema.Types.ObjectId,
ref,
};
break;
}
case 'hasMany': {
const FK = _.find(definition.associations, {
alias: name,
});
const ref = attribute.plugin
? strapi.plugins[attribute.plugin].models[attribute.collection].globalId
: strapi.models[attribute.collection].globalId;
const ref = getRef(attribute.collection, attribute.plugin);
if (FK) {
definition.loadedModel[name] = {
setField(name, {
type: 'virtual',
ref,
via: FK.via,
justOne: false,
};
});
// Set this info to be able to see if this field is a real database's field.
attribute.isVirtual = true;
} else {
definition.loadedModel[name] = [
{
type: instance.Schema.Types.ObjectId,
ref,
},
];
setField(name, [{ type: ObjectId, ref }]);
}
break;
}
@ -392,9 +388,8 @@ const buildRelation = ({ definition, model, instance, attribute, name }) => {
const FK = _.find(definition.associations, {
alias: name,
});
const ref = attribute.plugin
? strapi.plugins[attribute.plugin].models[attribute.model].globalId
: strapi.models[attribute.model].globalId;
const ref = getRef(attribute.model, attribute.plugin);
if (
FK &&
@ -403,38 +398,26 @@ const buildRelation = ({ definition, model, instance, attribute, name }) => {
FK.nature !== 'oneWay' &&
FK.nature !== 'oneToMorph'
) {
definition.loadedModel[name] = {
setField(name, {
type: 'virtual',
ref,
via: FK.via,
justOne: true,
};
});
// Set this info to be able to see if this field is a real database's field.
attribute.isVirtual = true;
} else {
definition.loadedModel[name] = {
type: instance.Schema.Types.ObjectId,
ref,
};
setField(name, { type: ObjectId, ref });
}
break;
}
case 'belongsToMany': {
const targetModel = attribute.plugin
? strapi.plugins[attribute.plugin].models[attribute.collection]
: strapi.models[attribute.collection];
const ref = targetModel.globalId;
const ref = getRef(attribute.collection, attribute.plugin);
if (nature === 'manyWay') {
definition.loadedModel[name] = [
{
type: instance.Schema.Types.ObjectId,
ref,
},
];
setField(name, [{ type: ObjectId, ref }]);
} else {
const FK = _.find(definition.associations, {
alias: name,
@ -442,84 +425,47 @@ const buildRelation = ({ definition, model, instance, attribute, name }) => {
// One-side of the relationship has to be a virtual field to be bidirectional.
if ((FK && _.isUndefined(FK.via)) || attribute.dominant !== true) {
definition.loadedModel[name] = {
setField(name, {
type: 'virtual',
ref,
via: FK.via,
};
});
// Set this info to be able to see if this field is a real database's field.
attribute.isVirtual = true;
} else {
definition.loadedModel[name] = [
{
type: instance.Schema.Types.ObjectId,
ref,
},
];
setField(name, [{ type: ObjectId, ref }]);
}
}
break;
}
case 'morphOne': {
const FK = _.find(definition.associations, {
alias: name,
});
const ref = attribute.plugin
? strapi.plugins[attribute.plugin].models[attribute.model].globalId
: strapi.models[attribute.model].globalId;
definition.loadedModel[name] = {
type: 'virtual',
ref,
via: `${FK.via}.ref`,
justOne: true,
};
// Set this info to be able to see if this field is a real database's field.
attribute.isVirtual = true;
const ref = getRef(attribute.model, attribute.plugin);
setField(name, { type: ObjectId, ref });
break;
}
case 'morphMany': {
const FK = _.find(definition.associations, {
alias: name,
});
const ref = attribute.plugin
? strapi.plugins[attribute.plugin].models[attribute.collection].globalId
: strapi.models[attribute.collection].globalId;
definition.loadedModel[name] = {
type: 'virtual',
ref,
via: `${FK.via}.ref`,
};
// Set this info to be able to see if this field is a real database's field.
attribute.isVirtual = true;
const ref = getRef(attribute.collection, attribute.plugin);
setField(name, [{ type: ObjectId, ref }]);
break;
}
case 'belongsToMorph': {
definition.loadedModel[name] = {
setField(name, {
kind: String,
[attribute.filter]: String,
ref: {
type: instance.Schema.Types.ObjectId,
refPath: `${name}.kind`,
},
};
ref: { type: ObjectId, refPath: `${name}.kind` },
});
break;
}
case 'belongsToManyMorph': {
definition.loadedModel[name] = [
setField(name, [
{
kind: String,
[attribute.filter]: String,
ref: {
type: instance.Schema.Types.ObjectId,
refPath: `${name}.kind`,
ref: { type: ObjectId, refPath: `${name}.kind` },
},
},
];
]);
break;
}
default:

View File

@ -474,29 +474,7 @@ module.exports = ({ model, modelKey, strapi }) => {
await deleteComponents(entry);
await Promise.all(
model.associations.map(async association => {
if (!association.via || !entry._id || association.dominant) {
return true;
}
const search =
_.endsWith(association.nature, 'One') || association.nature === 'oneToMany'
? { [association.via]: entry._id }
: { [association.via]: { $in: [entry._id] } };
const update =
_.endsWith(association.nature, 'One') || association.nature === 'oneToMany'
? { [association.via]: null }
: { $pull: { [association.via]: entry._id } };
// Retrieve model.
const model = association.plugin
? strapi.plugins[association.plugin].models[association.model || association.collection]
: strapi.models[association.model || association.collection];
return model.updateMany(search, update);
})
);
await model.deleteRelations(entry);
return entry.toObject ? entry.toObject() : null;
}

View File

@ -19,372 +19,39 @@ const getModel = function(model, plugin) {
);
};
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 => {
const transformToArrayID = (array, pk) => {
if (_.isArray(array)) {
return array.map(value => {
if (_.isPlainObject(value)) {
return getValuePrimaryKey(value, this.primaryKey);
return array
.map(value => value && (getValuePrimaryKey(value, pk) || value))
.filter(n => n)
.map(val => _.toString(val));
}
return value;
});
}
return transformToArrayID([array]);
};
if (
association.type === 'model' ||
(association.type === 'collection' && _.isObject(array))
) {
return _.isEmpty(array)
? []
: transformToArrayID([array]);
}
const removeUndefinedKeys = (obj = {}) => _.pickBy(obj, _.negate(_.isUndefined));
return [];
};
const addRelationMorph = async (model, params) => {
const { id, alias, refId, ref, field, filter } = params;
// 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;
await model.updateMany(
{
[model.primaryKey]: id,
},
{
$push: {
[alias]: {
ref: new mongoose.Types.ObjectId(refId),
kind: ref,
[filter]: field,
},
{}
);
// 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 removeRelationMorph = async (model, params) => {
const { alias } = params;
let opts;
@ -405,24 +72,403 @@ module.exports = {
};
}
const entries = await this.find(opts);
await model.updateMany(opts, {
$pull: {
[alias]: {
ref: params.refId,
kind: params.ref,
[params.filter]: params.field,
},
},
});
};
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;
module.exports = {
async update(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 = 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);
}
return true;
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 }
);
});
return entry.save();
// 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}`;
});
await Promise.all(updates);
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;
}
// media -> model
case 'manyMorphToMany':
case 'manyMorphToOne': {
newValue.forEach(obj => {
const refModel = strapi.getModel(obj.ref, obj.source);
const createRelation = () => {
return addRelationMorph(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(
removeRelationMorph(this, {
alias: association.alias,
ref: obj.kind || refModel.globalId,
refId: new mongoose.Types.ObjectId(obj.refId),
field: obj.field,
filter: association.filter,
})
.then(createRelation)
.then(() => {
// set field inside refModel
return refModel.updateMany(
{
[refModel.primaryKey]: new mongoose.Types.ObjectId(obj.refId),
},
{
[obj.field]: new mongoose.Types.ObjectId(entry[this.primaryKey]),
}
);
})
);
} else {
relationUpdates.push(
createRelation().then(() => {
// push to field inside refModel
return refModel.updateMany(
{
[refModel.primaryKey]: new mongoose.Types.ObjectId(obj.refId),
},
{
$push: { [obj.field]: new mongoose.Types.ObjectId(entry[this.primaryKey]) },
}
);
})
);
}
});
break;
}
// model -> media
case 'oneToManyMorph':
case 'manyToManyMorph': {
// Compare array of ID to find deleted files.
const currentIds = transformToArrayID(currentValue, this.primaryKey);
const newIds = transformToArrayID(newValue, this.primaryKey);
const toAdd = _.difference(newIds, currentIds);
const toRemove = _.difference(currentIds, newIds);
const model = getModel(details.model || details.collection, details.plugin);
_.set(acc, attribute, newIds);
const addPromise = Promise.all(
toAdd.map(id => {
return addRelationMorph(model, {
id,
alias: association.via,
ref: this.globalId,
refId: entry._id,
field: association.alias,
filter: association.filter,
});
})
);
relationUpdates.push(addPromise);
toRemove.forEach(id => {
relationUpdates.push(
removeRelationMorph(model, {
id,
alias: association.via,
ref: this.globalId,
refId: entry._id,
field: association.alias,
filter: association.filter,
})
);
});
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;
},
deleteRelations(entry) {
const primaryKeyValue = entry[this.primaryKey];
return Promise.all(
this.associations.map(async association => {
const { nature, via, dominant } = association;
// TODO: delete all the ref to the model
switch (nature) {
case 'oneWay':
case 'manyWay': {
return;
}
case 'oneToMany':
case 'oneToOne': {
if (!via) {
return;
}
const targetModel = strapi.db.getModel(
association.model || association.collection,
association.plugin
);
return targetModel.updateMany({ [via]: primaryKeyValue }, { [via]: null });
}
case 'manyToMany':
case 'manyToOne': {
if (!via || dominant) {
return;
}
const targetModel = strapi.db.getModel(
association.model || association.collection,
association.plugin
);
return targetModel.updateMany(
{ [via]: primaryKeyValue },
{ $pull: { [via]: primaryKeyValue } }
);
}
case 'oneToManyMorph':
case 'manyToManyMorph': {
// delete relation inside of the ref model
const targetModel = strapi.db.getModel(
association.model || association.collection,
association.plugin
);
// ignore them ghost relations
if (!targetModel) return;
const element = {
ref: primaryKeyValue,
kind: this.globalId,
[association.filter]: association.alias,
};
return targetModel.updateMany(
{ [via]: { $elemMatch: element } },
{ $pull: { [via]: element } }
);
}
case 'manyMorphToMany':
case 'manyMorphToOne': {
// delete relation inside of the ref model
// console.log(entry[association.alias]);
if (Array.isArray(entry[association.alias])) {
return Promise.all(
entry[association.alias].map(val => {
const targetModel = strapi.db.getModelByGlobalId(val.kind);
// ignore them ghost relations
if (!targetModel) return;
const field = val[association.filter];
const reverseAssoc = targetModel.associations.find(
assoc => assoc.alias === field
);
if (reverseAssoc && reverseAssoc.nature === 'oneToManyMorph') {
return targetModel.updateMany(
{
[targetModel.primaryKey]: val.ref && (val.ref._id || val.ref),
},
{
[field]: null,
}
);
}
return targetModel.updateMany(
{
[targetModel.primaryKey]: val.ref && (val.ref._id || val.ref),
},
{
$pull: { [field]: primaryKeyValue },
}
);
})
);
}
return;
}
case 'oneMorphToOne':
case 'oneMorphToMany': {
return;
}
}
})
);
},
};

View File

@ -117,6 +117,12 @@ class DatabaseManager {
return model.collectionName === collectionName;
});
}
getModelByGlobalId(globalId) {
return Array.from(this.models.values()).find(model => {
return model.globalId === globalId;
});
}
}
function createDatabaseManager(strapi) {

View File

@ -269,7 +269,7 @@ module.exports = {
const { id, model, field } = params;
const arr = Array.isArray(files) ? files : [files];
return Promise.all(
const enhancedFiles = await Promise.all(
arr.map(file => {
return this.enhanceFile(
file,
@ -282,7 +282,9 @@ module.exports = {
}
);
})
).then(files => this.uploadFileAndPersist(files));
);
await Promise.all(enhancedFiles.map(file => this.uploadFileAndPersist(file)));
},
getSettings() {

View File

@ -0,0 +1,68 @@
const { getNature } = require('../models');
describe('getNature', () => {
describe('oneWay', () => {
test('oneWay', () => {
global.strapi = {
models: {
baseModel: {
attributes: {
test: {
model: 'modelName',
},
},
},
modelName: {},
},
plugins: {},
};
const nature = getNature({
attribute: global.strapi.models.baseModel.attributes.test,
attributeName: 'test',
modelName: 'baseModel',
});
expect(nature).toEqual({
nature: 'oneWay',
verbose: 'belongsTo',
});
});
});
describe('oneToOne', () => {
test('oneToOne', () => {
global.strapi = {
models: {
baseModel: {
attributes: {
test: {
model: 'modelName',
via: 'reverseAttribute',
},
},
},
modelName: {
attributes: {
reverseAttribute: {
model: 'baseModel',
},
},
},
},
plugins: {},
};
const nature = getNature({
attribute: global.strapi.models.baseModel.attributes.test,
attributeName: 'test',
modelName: 'baseModel',
});
expect(nature).toEqual({
nature: 'oneToOne',
verbose: 'belongsTo',
});
});
});
});

View File

@ -13,7 +13,6 @@ const isNumeric = value => {
return !_.isObject(value) && !isNaN(parseFloat(value)) && isFinite(value);
};
/* eslint-disable prefer-template */
/*
* Set of utils for models
*/
@ -38,58 +37,41 @@ module.exports = {
* Find relation nature with verbose
*/
getNature: (association, key, models, currentModelName) => {
try {
getNature: ({ attribute, attributeName, modelName }) => {
const types = {
current: '',
other: '',
};
if (_.isUndefined(models)) {
models = association.plugin
? strapi.plugins[association.plugin].models
: strapi.models;
}
const models = attribute.plugin ? strapi.plugins[attribute.plugin].models : strapi.models;
const pluginModels = Object.values(strapi.plugins).reduce((acc, plugin) => {
return acc.concat(Object.values(plugin.models));
}, []);
const allModels = Object.values(strapi.models).concat(pluginModels);
if (
(_.has(association, 'collection') && association.collection === '*') ||
(_.has(association, 'model') && association.model === '*')
(_.has(attribute, 'collection') && attribute.collection === '*') ||
(_.has(attribute, 'model') && attribute.model === '*')
) {
if (association.model) {
if (attribute.model) {
types.current = 'morphToD';
} else {
types.current = 'morphTo';
}
const flattenedPluginsModels = Object.keys(strapi.plugins).reduce(
(acc, current) => {
Object.keys(strapi.plugins[current].models).forEach(model => {
acc[`${current}_${model}`] =
strapi.plugins[current].models[model];
});
return acc;
},
{}
);
const allModels = _.merge({}, strapi.models, flattenedPluginsModels);
// We have to find if they are a model linked to this key
_.forIn(allModels, model => {
_.forEach(allModels, model => {
_.forIn(model.attributes, attribute => {
if (
_.has(attribute, 'via') &&
attribute.via === key &&
attribute.model === currentModelName
) {
if (_.has(attribute, 'collection')) {
if (_.has(attribute, 'via') && attribute.via === attributeName) {
if (_.has(attribute, 'collection') && attribute.collection === modelName) {
types.other = 'collection';
// Break loop
return false;
} else if (_.has(attribute, 'model')) {
types.other = 'model';
} else if (_.has(attribute, 'model') && attribute.model === modelName) {
types.other = 'modelD';
// Break loop
return false;
@ -97,22 +79,14 @@ module.exports = {
}
});
});
} else if (
_.has(association, 'via') &&
_.has(association, 'collection')
) {
const relatedAttribute =
models[association.collection].attributes[association.via];
} else if (_.has(attribute, 'via') && _.has(attribute, 'collection')) {
const relatedAttribute = models[attribute.collection].attributes[attribute.via];
if (!relatedAttribute) {
throw new Error(
`The attribute \`${
association.via
}\` is missing in the model ${_.upperFirst(
association.collection
)} ${
association.plugin ? '(plugin - ' + association.plugin + ')' : ''
}`
`The attribute \`${attribute.via}\` is missing in the model ${_.upperFirst(
attribute.collection
)} ${attribute.plugin ? '(plugin - ' + attribute.plugin + ')' : ''}`
);
}
@ -130,59 +104,44 @@ module.exports = {
!_.has(relatedAttribute, 'via')
) {
types.other = 'collectionD';
} else if (
_.has(relatedAttribute, 'model') &&
relatedAttribute.model !== '*'
) {
} else if (_.has(relatedAttribute, 'model') && relatedAttribute.model !== '*') {
types.other = 'model';
} else if (
_.has(relatedAttribute, 'collection') ||
_.has(relatedAttribute, 'model')
) {
} else if (_.has(relatedAttribute, 'collection') || _.has(relatedAttribute, 'model')) {
types.other = 'morphTo';
}
} else if (_.has(association, 'via') && _.has(association, 'model')) {
} else if (_.has(attribute, 'via') && _.has(attribute, 'model')) {
types.current = 'modelD';
// We have to find if they are a model linked to this key
const model = models[association.model];
// We have to find if they are a model linked to this attributeName
const model = models[attribute.model];
const attribute = model.attributes[association.via];
const reverseAttribute = model.attributes[attribute.via];
if (
_.has(attribute, 'via') &&
attribute.via === key &&
_.has(attribute, 'collection') &&
attribute.collection !== '*'
_.has(reverseAttribute, 'via') &&
reverseAttribute.via === attributeName &&
_.has(reverseAttribute, 'collection') &&
reverseAttribute.collection !== '*'
) {
types.other = 'collection';
} else if (_.has(attribute, 'model') && attribute.model !== '*') {
} else if (_.has(reverseAttribute, 'model') && reverseAttribute.model !== '*') {
types.other = 'model';
} else if (
_.has(attribute, 'collection') ||
_.has(attribute, 'model')
) {
} else if (_.has(reverseAttribute, 'collection') || _.has(reverseAttribute, 'model')) {
types.other = 'morphTo';
}
} else if (_.has(association, 'model')) {
} else if (_.has(attribute, 'model')) {
types.current = 'model';
// We have to find if they are a model linked to this key
// We have to find if they are a model linked to this attributeName
_.forIn(models, model => {
_.forIn(model.attributes, attribute => {
if (_.has(attribute, 'via') && attribute.via === key) {
if (
_.has(attribute, 'collection') &&
attribute.collection === currentModelName
) {
if (_.has(attribute, 'via') && attribute.via === attributeName) {
if (_.has(attribute, 'collection') && attribute.collection === modelName) {
types.other = 'collection';
// Break loop
return false;
} else if (
_.has(attribute, 'model') &&
attribute.model === currentModelName
) {
} else if (_.has(attribute, 'model') && attribute.model === modelName) {
types.other = 'modelD';
// Break loop
@ -191,25 +150,19 @@ module.exports = {
}
});
});
} else if (_.has(association, 'collection')) {
} else if (_.has(attribute, 'collection')) {
types.current = 'collectionD';
// We have to find if they are a model linked to this key
// We have to find if they are a model linked to this attributeName
_.forIn(models, model => {
_.forIn(model.attributes, attribute => {
if (_.has(attribute, 'via') && attribute.via === key) {
if (
_.has(attribute, 'collection') &&
attribute.collection === currentModelName
) {
if (_.has(attribute, 'via') && attribute.via === attributeName) {
if (_.has(attribute, 'collection') && attribute.collection === modelName) {
types.other = 'collection';
// Break loop
return false;
} else if (
_.has(attribute, 'model') &&
attribute.model === currentModelName
) {
} else if (_.has(attribute, 'model') && attribute.model === modelName) {
types.other = 'modelD';
// Break loop
@ -252,7 +205,7 @@ module.exports = {
};
} else if (
types.current === 'morphTo' &&
(types.other === 'model' || _.has(association, 'model'))
(types.other === 'model' || _.has(attribute, 'model'))
) {
return {
nature: 'manyMorphToOne',
@ -260,7 +213,7 @@ module.exports = {
};
} else if (
types.current === 'morphTo' &&
(types.other === 'collection' || _.has(association, 'collection'))
(types.other === 'collection' || _.has(attribute, 'collection'))
) {
return {
nature: 'manyMorphToMany',
@ -294,10 +247,7 @@ module.exports = {
nature: 'oneToMany',
verbose: 'hasMany',
};
} else if (
types.current === 'collection' &&
types.other === 'collection'
) {
} else if (types.current === 'collection' && types.other === 'collection') {
return {
nature: 'manyToMany',
verbose: 'belongsToMany',
@ -323,15 +273,6 @@ module.exports = {
}
return undefined;
} catch (e) {
strapi.log.error(
`Something went wrong in the model \`${_.upperFirst(
currentModelName
)}\` with the attribute \`${key}\``
);
strapi.log.error(e);
strapi.stop();
}
},
/**
@ -347,9 +288,7 @@ module.exports = {
return a.collection < b.collection ? -1 : 1;
})
.map(table =>
_.snakeCase(
`${pluralize.plural(table.collection)} ${pluralize.plural(table.via)}`
)
_.snakeCase(`${pluralize.plural(table.collection)} ${pluralize.plural(table.via)}`)
)
.join('__');
},
@ -373,32 +312,21 @@ module.exports = {
// Get relation nature
let details;
const targetName = association.model || association.collection || '';
const infos = this.getNature(
association,
key,
undefined,
model.toLowerCase()
);
const infos = this.getNature({
attribute: association,
attributeName: key,
modelName: model.toLowerCase(),
});
if (targetName !== '*') {
if (association.plugin) {
details = _.get(
strapi.plugins,
[
association.plugin,
'models',
targetName,
'attributes',
association.via,
],
[association.plugin, 'models', targetName, 'attributes', association.via],
{}
);
} else {
details = _.get(
strapi.models,
[targetName, 'attributes', association.via],
{}
);
details = _.get(strapi.models, [targetName, 'attributes', association.via], {});
}
}
@ -418,14 +346,11 @@ module.exports = {
if (infos.nature === 'manyToMany' && definition.orm === 'bookshelf') {
ast.tableCollectionName =
_.get(association, 'collectionName') ||
this.getCollectionName(details, association);
_.get(association, 'collectionName') || this.getCollectionName(details, association);
}
if (infos.nature === 'manyWay' && definition.orm === 'bookshelf') {
ast.tableCollectionName = `${
definition.collectionName
}__${_.snakeCase(key)}`;
ast.tableCollectionName = `${definition.collectionName}__${_.snakeCase(key)}`;
}
definition.associations.push(ast);
@ -447,37 +372,25 @@ module.exports = {
return;
}
const pluginsModels = Object.keys(strapi.plugins).reduce(
(acc, current) => {
const pluginsModels = Object.keys(strapi.plugins).reduce((acc, current) => {
Object.keys(strapi.plugins[current].models).forEach(entity => {
Object.keys(
strapi.plugins[current].models[entity].attributes
).forEach(attribute => {
const attr =
strapi.plugins[current].models[entity].attributes[attribute];
Object.keys(strapi.plugins[current].models[entity].attributes).forEach(attribute => {
const attr = strapi.plugins[current].models[entity].attributes[attribute];
if (
(attr.collection || attr.model || '').toLowerCase() ===
model.toLowerCase()
) {
if ((attr.collection || attr.model || '').toLowerCase() === model.toLowerCase()) {
acc.push(strapi.plugins[current].models[entity].globalId);
}
});
});
return acc;
},
[]
);
}, []);
const appModels = Object.keys(strapi.models).reduce((acc, entity) => {
Object.keys(strapi.models[entity].attributes).forEach(attribute => {
const attr = strapi.models[entity].attributes[attribute];
if (
(attr.collection || attr.model || '').toLowerCase() ===
model.toLowerCase()
) {
if ((attr.collection || attr.model || '').toLowerCase() === model.toLowerCase()) {
acc.push(strapi.models[entity].globalId);
}
});
@ -485,29 +398,19 @@ module.exports = {
return acc;
}, []);
const componentModels = Object.keys(strapi.components).reduce(
(acc, entity) => {
Object.keys(strapi.components[entity].attributes).forEach(
attribute => {
const componentModels = Object.keys(strapi.components).reduce((acc, entity) => {
Object.keys(strapi.components[entity].attributes).forEach(attribute => {
const attr = strapi.components[entity].attributes[attribute];
if (
(attr.collection || attr.model || '').toLowerCase() ===
model.toLowerCase()
) {
if ((attr.collection || attr.model || '').toLowerCase() === model.toLowerCase()) {
acc.push(strapi.components[entity].globalId);
}
}
);
});
return acc;
},
[]
);
}, []);
const models = _.uniq(
appModels.concat(pluginsModels).concat(componentModels)
);
const models = _.uniq(appModels.concat(pluginsModels).concat(componentModels));
definition.associations.push({
alias: key,
@ -519,9 +422,7 @@ module.exports = {
});
} catch (e) {
strapi.log.error(
`Something went wrong in the model \`${_.upperFirst(
model
)}\` with the attribute \`${key}\``
`Something went wrong in the model \`${_.upperFirst(model)}\` with the attribute \`${key}\``
);
strapi.log.error(e);
strapi.stop();
@ -559,9 +460,7 @@ module.exports = {
const connector = models[model].orm;
if (!connector) {
throw new Error(
`Impossible to determine the ORM used for the model ${model}.`
);
throw new Error(`Impossible to determine the ORM used for the model ${model}.`);
}
const convertor = strapi.db.connectors.get(connector).getQueryParams;
@ -614,17 +513,7 @@ module.exports = {
if (
_.includes(
[
'ne',
'lt',
'gt',
'lte',
'gte',
'contains',
'containss',
'in',
'nin',
],
['ne', 'lt', 'gt', 'lte', 'gte', 'contains', 'containss', 'in', 'nin'],
_.last(suffix)
)
) {

View File

@ -289,7 +289,9 @@ class Strapi extends EventEmitter {
stop(exitCode = 1) {
// Destroy server and available connections.
if (_.has(this, 'server.destroy')) {
this.server.destroy();
}
if (this.config.autoReload) {
process.send('stop');