mirror of
https://github.com/strapi/strapi.git
synced 2025-08-30 19:56:05 +00:00
Merge pull request #4601 from strapi/dynamic-zone/mongoose-support
Dynamic zone/mongoose support
This commit is contained in:
commit
afa0f50a23
@ -7,10 +7,7 @@
|
||||
},
|
||||
"options": {
|
||||
"increments": true,
|
||||
"timestamps": [
|
||||
"created_at",
|
||||
"updated_at"
|
||||
],
|
||||
"timestamps": ["created_at", "updated_at"],
|
||||
"comment": ""
|
||||
},
|
||||
"attributes": {
|
||||
@ -38,21 +35,12 @@
|
||||
"collection": "category"
|
||||
},
|
||||
"price_range": {
|
||||
"enum": [
|
||||
"very_cheap",
|
||||
"cheap",
|
||||
"average",
|
||||
"expensive",
|
||||
"very_expensive"
|
||||
],
|
||||
"enum": ["very_cheap", "cheap", "average", "expensive", "very_expensive"],
|
||||
"type": "enumeration"
|
||||
},
|
||||
"body": {
|
||||
"type": "dynamiczone",
|
||||
"components": [
|
||||
"default.closingperiod",
|
||||
"default.restaurantservice"
|
||||
]
|
||||
"components": ["default.closingperiod", "default.restaurantservice"]
|
||||
},
|
||||
"description": {
|
||||
"type": "richtext",
|
||||
@ -61,7 +49,6 @@
|
||||
"opening_times": {
|
||||
"component": "default.openingtimes",
|
||||
"type": "component",
|
||||
"required": true,
|
||||
"repeatable": true,
|
||||
"min": 1,
|
||||
"max": 10
|
||||
@ -72,7 +59,6 @@
|
||||
},
|
||||
"services": {
|
||||
"component": "default.restaurantservice",
|
||||
"required": true,
|
||||
"repeatable": true,
|
||||
"type": "component"
|
||||
},
|
||||
@ -83,4 +69,4 @@
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,11 @@
|
||||
"type": "date",
|
||||
"required": true
|
||||
},
|
||||
"media": {
|
||||
"model": "file",
|
||||
"via": "related",
|
||||
"plugin": "upload"
|
||||
},
|
||||
"dish": {
|
||||
"component": "default.dish",
|
||||
"type": "component"
|
||||
|
@ -10,6 +10,11 @@
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"media": {
|
||||
"model": "file",
|
||||
"via": "related",
|
||||
"plugin": "upload"
|
||||
},
|
||||
"is_available": {
|
||||
"type": "boolean",
|
||||
"required": true,
|
||||
|
@ -6,6 +6,7 @@ const mongoose = require('mongoose');
|
||||
const utilsModels = require('strapi-utils').models;
|
||||
const utils = require('./utils');
|
||||
const relations = require('./relations');
|
||||
const { findComponentByGlobalId } = require('./utils/helpers');
|
||||
|
||||
module.exports = ({ models, target, plugin = false }, ctx) => {
|
||||
const { instance } = ctx;
|
||||
@ -27,13 +28,18 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
|
||||
global[definition.globalName] = {};
|
||||
}
|
||||
|
||||
const componentAttributes = Object.keys(definition.attributes).filter(
|
||||
key => definition.attributes[key].type === 'component'
|
||||
const componentAttributes = Object.keys(definition.attributes).filter(key =>
|
||||
['component', 'dynamiczone'].includes(definition.attributes[key].type)
|
||||
);
|
||||
|
||||
const scalarAttributes = Object.keys(definition.attributes).filter(key => {
|
||||
const { type } = definition.attributes[key];
|
||||
return type !== undefined && type !== null && type !== 'component';
|
||||
return (
|
||||
type !== undefined &&
|
||||
type !== null &&
|
||||
type !== 'component' &&
|
||||
type !== 'dynamiczone'
|
||||
);
|
||||
});
|
||||
|
||||
const relationalAttributes = Object.keys(definition.attributes).filter(
|
||||
@ -43,7 +49,7 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
|
||||
}
|
||||
);
|
||||
|
||||
// handle gorup attrs
|
||||
// handle component and dynamic zone attrs
|
||||
if (componentAttributes.length > 0) {
|
||||
// create join morph collection thingy
|
||||
componentAttributes.forEach(name => {
|
||||
@ -129,8 +135,7 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
|
||||
|
||||
if (_.isFunction(target[model.toLowerCase()][fn])) {
|
||||
schema.pre(key, function() {
|
||||
return target[model.toLowerCase()]
|
||||
[fn](this);
|
||||
return target[model.toLowerCase()][fn](this);
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -252,14 +257,28 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
|
||||
|
||||
componentAttributes.forEach(name => {
|
||||
const attribute = definition.attributes[name];
|
||||
const { type } = attribute;
|
||||
|
||||
if (Array.isArray(returned[name])) {
|
||||
const components = returned[name].map(el => el.ref);
|
||||
// Reformat data by bypassing the many-to-many relationship.
|
||||
returned[name] =
|
||||
attribute.repeatable === true
|
||||
? components
|
||||
: _.first(components) || null;
|
||||
if (type === 'component') {
|
||||
if (Array.isArray(returned[name])) {
|
||||
const components = returned[name].map(el => el.ref);
|
||||
// Reformat data by bypassing the many-to-many relationship.
|
||||
returned[name] =
|
||||
attribute.repeatable === true
|
||||
? components
|
||||
: _.first(components) || null;
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'dynamiczone') {
|
||||
const components = returned[name].map(el => {
|
||||
return {
|
||||
__component: findComponentByGlobalId(el.kind).uid,
|
||||
...el.ref,
|
||||
};
|
||||
});
|
||||
|
||||
returned[name] = components;
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -304,121 +323,38 @@ const createOnFetchPopulateFn = ({
|
||||
componentAttributes,
|
||||
definition,
|
||||
}) => {
|
||||
return function(next) {
|
||||
return function() {
|
||||
const populatedPaths = this.getPopulatedPaths();
|
||||
|
||||
morphAssociations.forEach(association => {
|
||||
if (
|
||||
this._mongooseOptions.populate &&
|
||||
this._mongooseOptions.populate[association.alias]
|
||||
) {
|
||||
if (
|
||||
association.nature === 'oneToManyMorph' ||
|
||||
association.nature === 'manyToManyMorph'
|
||||
) {
|
||||
this._mongooseOptions.populate[association.alias].match = {
|
||||
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,
|
||||
};
|
||||
|
||||
// Select last related to an entity.
|
||||
this._mongooseOptions.populate[association.alias].options = {
|
||||
},
|
||||
options: {
|
||||
sort: '-createdAt',
|
||||
};
|
||||
} else {
|
||||
this._mongooseOptions.populate[
|
||||
association.alias
|
||||
].path = `${association.alias}.ref`;
|
||||
}
|
||||
} else {
|
||||
if (!this._mongooseOptions.populate) {
|
||||
this._mongooseOptions.populate = {};
|
||||
}
|
||||
// Images are not displayed in populated data.
|
||||
// We automatically populate morph relations.
|
||||
if (
|
||||
association.nature === 'oneToManyMorph' ||
|
||||
association.nature === 'manyToManyMorph'
|
||||
) {
|
||||
this._mongooseOptions.populate[association.alias] = {
|
||||
path: association.alias,
|
||||
match: {
|
||||
[`${association.via}.${association.filter}`]: association.alias,
|
||||
[`${association.via}.kind`]: definition.globalId,
|
||||
},
|
||||
options: {
|
||||
sort: '-createdAt',
|
||||
},
|
||||
select: undefined,
|
||||
model: undefined,
|
||||
_docs: {},
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
componentAttributes.forEach(name => {
|
||||
const attr = definition.attributes[name];
|
||||
|
||||
const component = strapi.components[attr.component];
|
||||
|
||||
const assocs = (component.associations || []).filter(
|
||||
assoc => assoc.autoPopulate === true
|
||||
);
|
||||
|
||||
let subpopulates = [];
|
||||
|
||||
assocs.forEach(assoc => {
|
||||
if (isPolymorphic({ assoc })) {
|
||||
if (
|
||||
assoc.nature === 'oneToManyMorph' ||
|
||||
assoc.nature === 'manyToManyMorph'
|
||||
) {
|
||||
subpopulates.push({
|
||||
path: assoc.alias,
|
||||
match: {
|
||||
[`${assoc.via}.${assoc.filter}`]: assoc.alias,
|
||||
[`${assoc.via}.kind`]: definition.globalId,
|
||||
},
|
||||
options: {
|
||||
sort: '-createdAt',
|
||||
},
|
||||
select: undefined,
|
||||
model: undefined,
|
||||
_docs: {},
|
||||
});
|
||||
} else {
|
||||
subpopulates.push({ path: `${assoc.alias}.ref`, _docs: {} });
|
||||
}
|
||||
} else {
|
||||
subpopulates.push({
|
||||
path: assoc.alias,
|
||||
_docs: {},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (
|
||||
this._mongooseOptions.populate &&
|
||||
this._mongooseOptions.populate[name]
|
||||
) {
|
||||
this._mongooseOptions.populate[name].path = `${name}.ref`;
|
||||
this._mongooseOptions.populate[name].populate = subpopulates;
|
||||
} else {
|
||||
_.set(this._mongooseOptions, ['populate', name], {
|
||||
path: `${name}.ref`,
|
||||
populate: subpopulates,
|
||||
_docs: {},
|
||||
},
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (populatedPaths.includes(alias)) {
|
||||
_.set(this._mongooseOptions.populate, [alias, 'path'], `${alias}.ref`);
|
||||
}
|
||||
});
|
||||
|
||||
next();
|
||||
componentAttributes.forEach(key => {
|
||||
this.populate({ path: `${key}.ref` });
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
const isPolymorphic = ({ assoc }) => {
|
||||
return assoc.nature.toLowerCase().indexOf('morph') !== -1;
|
||||
};
|
||||
|
||||
const buildRelation = ({ definition, model, instance, attribute, name }) => {
|
||||
const { nature, verbose } =
|
||||
utilsModels.getNature(attribute, name, undefined, model.toLowerCase()) ||
|
||||
|
@ -10,15 +10,18 @@ const {
|
||||
models: modelUtils,
|
||||
} = require('strapi-utils');
|
||||
|
||||
module.exports = ({ model, modelKey, strapi }) => {
|
||||
const hasPK = obj => _.has(obj, model.primaryKey) || _.has(obj, 'id');
|
||||
const getPK = obj =>
|
||||
_.has(obj, model.primaryKey) ? obj[model.primaryKey] : obj.id;
|
||||
const { findComponentByGlobalId } = require('./utils/helpers');
|
||||
|
||||
const hasPK = (obj, model) => _.has(obj, model.primaryKey) || _.has(obj, 'id');
|
||||
const getPK = (obj, model) =>
|
||||
_.has(obj, model.primaryKey) ? obj[model.primaryKey] : obj.id;
|
||||
|
||||
module.exports = ({ model, modelKey, strapi }) => {
|
||||
const assocKeys = model.associations.map(ast => ast.alias);
|
||||
const componentKeys = Object.keys(model.attributes).filter(key => {
|
||||
return model.attributes[key].type === 'component';
|
||||
});
|
||||
const componentKeys = Object.keys(model.attributes).filter(key =>
|
||||
['component', 'dynamiczone'].includes(model.attributes[key].type)
|
||||
);
|
||||
|
||||
const excludedKeys = assocKeys.concat(componentKeys);
|
||||
|
||||
const defaultPopulate = model.associations
|
||||
@ -38,49 +41,96 @@ module.exports = ({ model, modelKey, strapi }) => {
|
||||
|
||||
for (let key of componentKeys) {
|
||||
const attr = model.attributes[key];
|
||||
const { component, required = false, repeatable = false } = attr;
|
||||
const { type } = attr;
|
||||
|
||||
const componentModel = strapi.components[component];
|
||||
if (type === 'component') {
|
||||
const { component, required = false, repeatable = false } = attr;
|
||||
|
||||
if (required === true && !_.has(values, key)) {
|
||||
const err = new Error(`Component ${key} is required`);
|
||||
err.status = 400;
|
||||
throw err;
|
||||
const componentModel = strapi.components[component];
|
||||
|
||||
if (required === true && !_.has(values, key)) {
|
||||
const err = new Error(`Component ${key} is required`);
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!_.has(values, key)) continue;
|
||||
|
||||
const componentValue = values[key];
|
||||
|
||||
if (repeatable === true) {
|
||||
validateRepeatableInput(componentValue, { key, ...attr });
|
||||
const components = await Promise.all(
|
||||
componentValue.map(value => {
|
||||
return strapi.query(component).create(value);
|
||||
})
|
||||
);
|
||||
|
||||
const componentsArr = components.map(componentEntry => ({
|
||||
kind: componentModel.globalId,
|
||||
ref: componentEntry,
|
||||
}));
|
||||
|
||||
entry[key] = componentsArr;
|
||||
await entry.save();
|
||||
} else {
|
||||
validateNonRepeatableInput(componentValue, { key, ...attr });
|
||||
if (componentValue === null) continue;
|
||||
|
||||
const componentEntry = await strapi
|
||||
.query(component)
|
||||
.create(componentValue);
|
||||
entry[key] = [
|
||||
{
|
||||
kind: componentModel.globalId,
|
||||
ref: componentEntry,
|
||||
},
|
||||
];
|
||||
await entry.save();
|
||||
}
|
||||
}
|
||||
|
||||
if (!_.has(values, key)) continue;
|
||||
if (type === 'dynamiczone') {
|
||||
const { required = false } = attr;
|
||||
|
||||
const componentValue = values[key];
|
||||
if (required === true && !_.has(values, key)) {
|
||||
const err = new Error(`Dynamiczone ${key} is required`);
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (repeatable === true) {
|
||||
validateRepeatableInput(componentValue, { key, ...attr });
|
||||
const components = await Promise.all(
|
||||
componentValue.map(value => {
|
||||
return strapi.query(component).create(value);
|
||||
if (!_.has(values, key)) continue;
|
||||
|
||||
const dynamiczoneValues = values[key];
|
||||
|
||||
validateDynamiczoneInput(dynamiczoneValues, { key, ...attr });
|
||||
|
||||
const dynamiczones = await Promise.all(
|
||||
dynamiczoneValues.map(value => {
|
||||
const component = value.__component;
|
||||
return strapi
|
||||
.query(component)
|
||||
.create(value)
|
||||
.then(entity => {
|
||||
return {
|
||||
__component: value.__component,
|
||||
entity,
|
||||
};
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const componentsArr = components.map(componentEntry => ({
|
||||
kind: componentModel.globalId,
|
||||
ref: componentEntry,
|
||||
}));
|
||||
const componentsArr = dynamiczones.map(({ __component, entity }) => {
|
||||
const componentModel = strapi.components[__component];
|
||||
|
||||
return {
|
||||
kind: componentModel.globalId,
|
||||
ref: entity,
|
||||
};
|
||||
});
|
||||
|
||||
entry[key] = componentsArr;
|
||||
await entry.save();
|
||||
} else {
|
||||
validateNonRepeatableInput(componentValue, { key, ...attr });
|
||||
if (componentValue === null) continue;
|
||||
|
||||
const componentEntry = await strapi
|
||||
.query(component)
|
||||
.create(componentValue);
|
||||
entry[key] = [
|
||||
{
|
||||
kind: componentModel.globalId,
|
||||
ref: componentEntry,
|
||||
},
|
||||
];
|
||||
await entry.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -88,70 +138,185 @@ module.exports = ({ model, modelKey, strapi }) => {
|
||||
async function updateComponents(entry, values) {
|
||||
if (componentKeys.length === 0) return;
|
||||
|
||||
const updateOrCreateComponent = async ({ componentUID, value }) => {
|
||||
// check if value has an id then update else create
|
||||
const query = strapi.query(componentUID);
|
||||
if (hasPK(value, query.model)) {
|
||||
return query.update(
|
||||
{
|
||||
[query.model.primaryKey]: getPK(value, query.model),
|
||||
},
|
||||
value
|
||||
);
|
||||
}
|
||||
return query.create(value);
|
||||
};
|
||||
|
||||
for (let key of componentKeys) {
|
||||
// if key isn't present then don't change the current component data
|
||||
if (!_.has(values, key)) continue;
|
||||
|
||||
const attr = model.attributes[key];
|
||||
const { component, repeatable = false } = attr;
|
||||
const { type } = attr;
|
||||
|
||||
const componentModel = strapi.components[component];
|
||||
const componentValue = values[key];
|
||||
if (type === 'component') {
|
||||
const { component: componentUID, repeatable = false } = attr;
|
||||
|
||||
const updateOrCreateComponent = async value => {
|
||||
// check if value has an id then update else create
|
||||
if (hasPK(value)) {
|
||||
return strapi.query(component).update(
|
||||
{
|
||||
[model.primaryKey]: getPK(value),
|
||||
},
|
||||
value
|
||||
const componentModel = strapi.components[componentUID];
|
||||
const componentValue = values[key];
|
||||
|
||||
if (repeatable === true) {
|
||||
validateRepeatableInput(componentValue, { key, ...attr });
|
||||
|
||||
await deleteOldComponents(entry, componentValue, {
|
||||
key,
|
||||
componentModel,
|
||||
});
|
||||
|
||||
const components = await Promise.all(
|
||||
componentValue.map(value =>
|
||||
updateOrCreateComponent({ componentUID, value })
|
||||
)
|
||||
);
|
||||
}
|
||||
return strapi.query(component).create(value);
|
||||
};
|
||||
|
||||
if (repeatable === true) {
|
||||
validateRepeatableInput(componentValue, { key, ...attr });
|
||||
|
||||
await deleteOldComponents(entry, componentValue, {
|
||||
key,
|
||||
componentModel,
|
||||
});
|
||||
|
||||
const components = await Promise.all(
|
||||
componentValue.map(updateOrCreateComponent)
|
||||
);
|
||||
const componentsArr = components.map(component => ({
|
||||
kind: componentModel.globalId,
|
||||
ref: component,
|
||||
}));
|
||||
|
||||
entry[key] = componentsArr;
|
||||
await entry.save();
|
||||
} else {
|
||||
validateNonRepeatableInput(componentValue, { key, ...attr });
|
||||
|
||||
await deleteOldComponents(entry, componentValue, {
|
||||
key,
|
||||
componentModel,
|
||||
});
|
||||
|
||||
if (componentValue === null) continue;
|
||||
|
||||
const component = await updateOrCreateComponent(componentValue);
|
||||
entry[key] = [
|
||||
{
|
||||
const componentsArr = components.map(component => ({
|
||||
kind: componentModel.globalId,
|
||||
ref: component,
|
||||
},
|
||||
];
|
||||
}));
|
||||
|
||||
entry[key] = componentsArr;
|
||||
await entry.save();
|
||||
} else {
|
||||
validateNonRepeatableInput(componentValue, { key, ...attr });
|
||||
|
||||
await deleteOldComponents(entry, componentValue, {
|
||||
key,
|
||||
componentModel,
|
||||
});
|
||||
|
||||
if (componentValue === null) continue;
|
||||
|
||||
const component = await updateOrCreateComponent({
|
||||
componentUID,
|
||||
value: componentValue,
|
||||
});
|
||||
|
||||
entry[key] = [
|
||||
{
|
||||
kind: componentModel.globalId,
|
||||
ref: component,
|
||||
},
|
||||
];
|
||||
await entry.save();
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'dynamiczone') {
|
||||
const dynamiczoneValues = values[key];
|
||||
|
||||
validateDynamiczoneInput(dynamiczoneValues, { key, ...attr });
|
||||
|
||||
await deleteDynamicZoneOldComponents(entry, dynamiczoneValues, {
|
||||
key,
|
||||
});
|
||||
|
||||
const dynamiczones = await Promise.all(
|
||||
dynamiczoneValues.map(value => {
|
||||
const componentUID = value.__component;
|
||||
return updateOrCreateComponent({ componentUID, value }).then(
|
||||
entity => {
|
||||
return {
|
||||
componentUID,
|
||||
entity,
|
||||
};
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
const componentsArr = dynamiczones.map(({ componentUID, entity }) => {
|
||||
const componentModel = strapi.components[componentUID];
|
||||
|
||||
return {
|
||||
kind: componentModel.globalId,
|
||||
ref: entity,
|
||||
};
|
||||
});
|
||||
|
||||
entry[key] = componentsArr;
|
||||
await entry.save();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
async function deleteDynamicZoneOldComponents(entry, values, { key }) {
|
||||
const idsToKeep = values.reduce((acc, value) => {
|
||||
const component = value.__component;
|
||||
const componentModel = strapi.components[component];
|
||||
if (hasPK(value, componentModel)) {
|
||||
acc.push({
|
||||
id: getPK(value, componentModel).toString(),
|
||||
componentUID: componentModel.uid,
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const allIds = []
|
||||
.concat(entry[key] || [])
|
||||
.filter(el => el.ref)
|
||||
.map(el => ({
|
||||
id: el.ref._id.toString(),
|
||||
componentUID: findComponentByGlobalId(el.kind).uid,
|
||||
}));
|
||||
|
||||
// verify the provided ids are realted to this entity.
|
||||
idsToKeep.forEach(({ id, componentUID }) => {
|
||||
if (
|
||||
!allIds.find(el => el.id === id && el.componentUID === componentUID)
|
||||
) {
|
||||
const err = new Error(
|
||||
`Some of the provided components in ${key} are not related to the entity`
|
||||
);
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
const idsToDelete = allIds.reduce((acc, { id, componentUID }) => {
|
||||
if (
|
||||
!idsToKeep.find(el => el.id === id && el.componentUID === componentUID)
|
||||
) {
|
||||
acc.push({
|
||||
id,
|
||||
componentUID,
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (idsToDelete.length > 0) {
|
||||
const deleteMap = idsToDelete.reduce((map, { id, componentUID }) => {
|
||||
if (!_.has(map, componentUID)) {
|
||||
map[componentUID] = [id];
|
||||
return map;
|
||||
}
|
||||
|
||||
map[componentUID].push(id);
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
await Promise.all(
|
||||
Object.keys(deleteMap).map(componentUID => {
|
||||
return strapi
|
||||
.query(componentUID)
|
||||
.delete({ [`${model.primaryKey}_in`]: deleteMap[componentUID] });
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteOldComponents(
|
||||
entry,
|
||||
componentValue,
|
||||
@ -161,8 +326,12 @@ module.exports = ({ model, modelKey, strapi }) => {
|
||||
? componentValue
|
||||
: [componentValue];
|
||||
|
||||
const idsToKeep = componentArr.filter(hasPK).map(getPK);
|
||||
const allIds = await (entry[key] || [])
|
||||
const idsToKeep = componentArr
|
||||
.filter(val => hasPK(val, componentModel))
|
||||
.map(val => getPK(val, componentModel));
|
||||
|
||||
const allIds = []
|
||||
.concat(entry[key] || [])
|
||||
.filter(el => el.ref)
|
||||
.map(el => el.ref._id);
|
||||
|
||||
@ -194,14 +363,45 @@ module.exports = ({ model, modelKey, strapi }) => {
|
||||
|
||||
for (let key of componentKeys) {
|
||||
const attr = model.attributes[key];
|
||||
const { component } = attr;
|
||||
const componentModel = strapi.components[component];
|
||||
const { type } = attr;
|
||||
|
||||
if (Array.isArray(entry[key]) && entry[key].length > 0) {
|
||||
const idsToDelete = entry[key].map(el => el.ref);
|
||||
await strapi
|
||||
.query(componentModel.uid)
|
||||
.delete({ [`${model.primaryKey}_in`]: idsToDelete });
|
||||
if (type === 'component') {
|
||||
const { component } = attr;
|
||||
const componentModel = strapi.components[component];
|
||||
|
||||
if (Array.isArray(entry[key]) && entry[key].length > 0) {
|
||||
const idsToDelete = entry[key].map(el => el.ref);
|
||||
await strapi
|
||||
.query(componentModel.uid)
|
||||
.delete({ [`${model.primaryKey}_in`]: idsToDelete });
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'dynamiczone') {
|
||||
if (Array.isArray(entry[key]) && entry[key].length > 0) {
|
||||
const idsToDelete = entry[key].map(el => ({
|
||||
componentUID: findComponentByGlobalId(el.kind).uid,
|
||||
id: el.ref,
|
||||
}));
|
||||
|
||||
const deleteMap = idsToDelete.reduce((map, { id, componentUID }) => {
|
||||
if (!_.has(map, componentUID)) {
|
||||
map[componentUID] = [id];
|
||||
return map;
|
||||
}
|
||||
|
||||
map[componentUID].push(id);
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
await Promise.all(
|
||||
Object.keys(deleteMap).map(componentUID => {
|
||||
return strapi.query(componentUID).delete({
|
||||
[`${model.primaryKey}_in`]: idsToDelete[componentUID],
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -221,7 +421,7 @@ module.exports = ({ model, modelKey, strapi }) => {
|
||||
}
|
||||
|
||||
async function findOne(params, populate) {
|
||||
const primaryKey = getPK(params);
|
||||
const primaryKey = getPK(params, model);
|
||||
|
||||
if (primaryKey) {
|
||||
params = {
|
||||
@ -257,13 +457,13 @@ module.exports = ({ model, modelKey, strapi }) => {
|
||||
|
||||
// Create relational data and return the entry.
|
||||
return model.updateRelations({
|
||||
[model.primaryKey]: getPK(entry),
|
||||
[model.primaryKey]: getPK(entry, model),
|
||||
values: relations,
|
||||
});
|
||||
}
|
||||
|
||||
async function update(params, values) {
|
||||
const primaryKey = getPK(params);
|
||||
const primaryKey = getPK(params, model);
|
||||
|
||||
if (primaryKey) {
|
||||
params = {
|
||||
@ -293,7 +493,7 @@ module.exports = ({ model, modelKey, strapi }) => {
|
||||
}
|
||||
|
||||
async function deleteMany(params) {
|
||||
const primaryKey = getPK(params);
|
||||
const primaryKey = getPK(params, model);
|
||||
|
||||
if (primaryKey) return deleteOne(params);
|
||||
|
||||
@ -303,7 +503,7 @@ module.exports = ({ model, modelKey, strapi }) => {
|
||||
|
||||
async function deleteOne(params) {
|
||||
const entry = await model
|
||||
.findOneAndRemove({ [model.primaryKey]: getPK(params) })
|
||||
.findOneAndRemove({ [model.primaryKey]: getPK(params, model) })
|
||||
.populate(defaultPopulate);
|
||||
|
||||
if (!entry) {
|
||||
@ -454,3 +654,56 @@ function validateNonRepeatableInput(value, { key, required }) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function validateDynamiczoneInput(
|
||||
value,
|
||||
{ key, min, max, components, required }
|
||||
) {
|
||||
if (!Array.isArray(value)) {
|
||||
const err = new Error(`Dynamiczone ${key} is invalid. Expected an array`);
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
|
||||
value.forEach(val => {
|
||||
if (typeof val !== 'object' || Array.isArray(val) || val === null) {
|
||||
const err = new Error(
|
||||
`Dynamiczone ${key} has invalid items. Expected each items to be objects`
|
||||
);
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!_.has(val, '__component')) {
|
||||
const err = new Error(
|
||||
`Dynamiczone ${key} has invalid items. Expected each items to have a valid __component key`
|
||||
);
|
||||
err.status = 400;
|
||||
throw err;
|
||||
} else if (!components.includes(val.__component)) {
|
||||
const err = new Error(
|
||||
`Dynamiczone ${key} has invalid items. Each item must have a __component key that is present in the attribute definition`
|
||||
);
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
if (
|
||||
(required === true || (required !== true && value.length > 0)) &&
|
||||
(min && value.length < min)
|
||||
) {
|
||||
const err = new Error(
|
||||
`Dynamiczone ${key} must contain at least ${min} items`
|
||||
);
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
if (max && value.length > max) {
|
||||
const err = new Error(
|
||||
`Dynamiczone ${key} must contain at most ${max} items`
|
||||
);
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
11
packages/strapi-connector-mongoose/lib/utils/helpers.js
Normal file
11
packages/strapi-connector-mongoose/lib/utils/helpers.js
Normal file
@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const findComponentByGlobalId = globalId => {
|
||||
return Object.values(strapi.components).find(
|
||||
compo => compo.globalId === globalId
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
findComponentByGlobalId,
|
||||
};
|
@ -8,6 +8,10 @@ module.exports = async (ctx, next) => {
|
||||
|
||||
const ct = strapi.contentTypes[model];
|
||||
|
||||
if (!ct) {
|
||||
return ctx.send({ error: 'contentType.notFound' }, 404);
|
||||
}
|
||||
|
||||
const target =
|
||||
ct.plugin === 'admin' ? strapi.admin : strapi.plugins[ct.plugin];
|
||||
|
||||
|
@ -576,6 +576,7 @@ describe.each([
|
||||
};
|
||||
|
||||
expect(updateRes.statusCode).toBe(200);
|
||||
|
||||
expect(updateRes.body).toMatchObject(expectedResult);
|
||||
|
||||
const getRes = await rq.get(`/${res.body.id}`);
|
||||
|
@ -16,6 +16,7 @@ module.exports = async (entry, files, { model, source }) => {
|
||||
let tmpModel = entity;
|
||||
let modelName = model;
|
||||
let sourceName;
|
||||
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
if (!tmpModel) return {};
|
||||
const part = path[i];
|
||||
@ -35,7 +36,11 @@ module.exports = async (entry, files, { model, source }) => {
|
||||
tmpModel = strapi.components[attr.component];
|
||||
} else if (attr.type === 'dynamiczone') {
|
||||
const entryIdx = path[i + 1]; // get component index
|
||||
modelName = _.get(entry, [...currentPath, entryIdx]).__component; // get component type
|
||||
const value = _.get(entry, [...currentPath, entryIdx]);
|
||||
|
||||
if (!value) return {};
|
||||
|
||||
modelName = value.__component; // get component type
|
||||
tmpModel = strapi.components[modelName];
|
||||
} else if (_.has(attr, 'model') || _.has(attr, 'collection')) {
|
||||
sourceName = attr.plugin;
|
||||
@ -57,6 +62,7 @@ module.exports = async (entry, files, { model, source }) => {
|
||||
|
||||
if (model) {
|
||||
const id = _.get(entry, path.concat('id'));
|
||||
|
||||
return uploadService.uploadToEntity(
|
||||
{ id, model },
|
||||
{ [field]: files },
|
||||
|
@ -80,7 +80,7 @@ module.exports = {
|
||||
delete file.buffer;
|
||||
file.provider = provider.provider;
|
||||
|
||||
const res = await strapi.plugins['upload'].services.upload.add(file);
|
||||
const res = await this.add(file);
|
||||
|
||||
// Remove temp file
|
||||
if (file.tmpPath) {
|
||||
|
@ -8,7 +8,9 @@ const findModelByAssoc = assoc => {
|
||||
};
|
||||
|
||||
const isAttribute = (model, field) =>
|
||||
_.has(model.allAttributes, field) || model.primaryKey === field;
|
||||
_.has(model.allAttributes, field) ||
|
||||
model.primaryKey === field ||
|
||||
field === 'id';
|
||||
|
||||
/**
|
||||
* Returns the model, attribute name and association from a path of relation
|
||||
@ -131,7 +133,11 @@ const buildQuery = ({ model, filters = {}, ...rest }) => {
|
||||
? value.map(val => castValue({ type, operator, value: val }))
|
||||
: castValue({ type, operator, value: value });
|
||||
|
||||
return { field, operator, value: castedValue };
|
||||
return {
|
||||
field: field === 'id' ? model.primaryKey : field,
|
||||
operator,
|
||||
value: castedValue,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ module.exports = async (entry, files, { model, source }) => {
|
||||
let tmpModel = entity;
|
||||
let modelName = model;
|
||||
let sourceName;
|
||||
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
if (!tmpModel) return {};
|
||||
const part = path[i];
|
||||
@ -35,7 +36,11 @@ module.exports = async (entry, files, { model, source }) => {
|
||||
tmpModel = strapi.components[attr.component];
|
||||
} else if (attr.type === 'dynamiczone') {
|
||||
const entryIdx = path[i + 1]; // get component index
|
||||
modelName = _.get(entry, [...currentPath, entryIdx]).__component; // get component type
|
||||
const value = _.get(entry, [...currentPath, entryIdx]);
|
||||
|
||||
if (!value) return {};
|
||||
|
||||
modelName = value.__component; // get component type
|
||||
tmpModel = strapi.components[modelName];
|
||||
} else if (_.has(attr, 'model') || _.has(attr, 'collection')) {
|
||||
sourceName = attr.plugin;
|
||||
|
Loading…
x
Reference in New Issue
Block a user