172 lines
7.2 KiB
JavaScript
172 lines
7.2 KiB
JavaScript
import _ from 'lodash';
|
|
import { get, has, pipe, omit, assign } from 'lodash/fp';
|
|
import { contentTypes, async } from '@strapi/utils';
|
|
|
|
const isDialectMySQL = ()=>strapi.db?.dialect.client === 'mysql';
|
|
function omitComponentData(contentType, data) {
|
|
const { attributes } = contentType;
|
|
const componentAttributes = Object.keys(attributes).filter((attributeName)=>contentTypes.isComponentAttribute(attributes[attributeName]));
|
|
return omit(componentAttributes, data);
|
|
}
|
|
// NOTE: we could generalize the logic to allow CRUD of relation directly in the DB layer
|
|
const createComponents = async (uid, data)=>{
|
|
const { attributes = {} } = strapi.getModel(uid);
|
|
const componentBody = {};
|
|
const attributeNames = Object.keys(attributes);
|
|
for (const attributeName of attributeNames){
|
|
const attribute = attributes[attributeName];
|
|
if (!has(attributeName, data) || !contentTypes.isComponentAttribute(attribute)) {
|
|
continue;
|
|
}
|
|
if (attribute.type === 'component') {
|
|
const { component: componentUID, repeatable = false } = attribute;
|
|
const componentValue = data[attributeName];
|
|
if (componentValue === null) {
|
|
continue;
|
|
}
|
|
if (repeatable === true) {
|
|
if (!Array.isArray(componentValue)) {
|
|
throw new Error('Expected an array to create repeatable component');
|
|
}
|
|
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
|
const components = await async.map(componentValue, (value)=>createComponent(componentUID, value), {
|
|
concurrency: isDialectMySQL() && !strapi.db?.inTransaction() ? 1 : Infinity
|
|
});
|
|
componentBody[attributeName] = components.map(({ id })=>{
|
|
return {
|
|
id,
|
|
__pivot: {
|
|
field: attributeName,
|
|
component_type: componentUID
|
|
}
|
|
};
|
|
});
|
|
} else {
|
|
const component = await createComponent(componentUID, componentValue);
|
|
componentBody[attributeName] = {
|
|
id: component.id,
|
|
__pivot: {
|
|
field: attributeName,
|
|
component_type: componentUID
|
|
}
|
|
};
|
|
}
|
|
continue;
|
|
}
|
|
if (attribute.type === 'dynamiczone') {
|
|
const dynamiczoneValues = data[attributeName];
|
|
if (!Array.isArray(dynamiczoneValues)) {
|
|
throw new Error('Expected an array to create repeatable component');
|
|
}
|
|
const createDynamicZoneComponents = async (value)=>{
|
|
const { id } = await createComponent(value.__component, value);
|
|
return {
|
|
id,
|
|
__component: value.__component,
|
|
__pivot: {
|
|
field: attributeName
|
|
}
|
|
};
|
|
};
|
|
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
|
componentBody[attributeName] = await async.map(dynamiczoneValues, createDynamicZoneComponents, {
|
|
concurrency: isDialectMySQL() && !strapi.db?.inTransaction() ? 1 : Infinity
|
|
});
|
|
continue;
|
|
}
|
|
}
|
|
return componentBody;
|
|
};
|
|
const getComponents = async (uid, entity)=>{
|
|
const componentAttributes = contentTypes.getComponentAttributes(strapi.getModel(uid));
|
|
if (_.isEmpty(componentAttributes)) {
|
|
return {};
|
|
}
|
|
return strapi.db.query(uid).load(entity, componentAttributes);
|
|
};
|
|
const deleteComponents = async (uid, entityToDelete, { loadComponents = true } = {})=>{
|
|
const { attributes = {} } = strapi.getModel(uid);
|
|
const attributeNames = Object.keys(attributes);
|
|
for (const attributeName of attributeNames){
|
|
const attribute = attributes[attributeName];
|
|
if (attribute.type === 'component' || attribute.type === 'dynamiczone') {
|
|
let value;
|
|
if (loadComponents) {
|
|
value = await strapi.db.query(uid).load(entityToDelete, attributeName);
|
|
} else {
|
|
value = entityToDelete[attributeName];
|
|
}
|
|
if (!value) {
|
|
continue;
|
|
}
|
|
if (attribute.type === 'component') {
|
|
const { component: componentUID } = attribute;
|
|
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
|
await async.map(_.castArray(value), (subValue)=>deleteComponent(componentUID, subValue), {
|
|
concurrency: isDialectMySQL() && !strapi.db?.inTransaction() ? 1 : Infinity
|
|
});
|
|
} else {
|
|
// delete dynamic zone components
|
|
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
|
await async.map(_.castArray(value), (subValue)=>deleteComponent(subValue.__component, subValue), {
|
|
concurrency: isDialectMySQL() && !strapi.db?.inTransaction() ? 1 : Infinity
|
|
});
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
};
|
|
/** *************************
|
|
Component queries
|
|
************************** */ // components can have nested compos so this must be recursive
|
|
const createComponent = async (uid, data)=>{
|
|
const model = strapi.getModel(uid);
|
|
const componentData = await createComponents(uid, data);
|
|
const transform = pipe(// Make sure we don't save the component with a pre-defined ID
|
|
omit('id'), // Remove the component data from the original data object ...
|
|
(payload)=>omitComponentData(model, payload), // ... and assign the newly created component instead
|
|
assign(componentData));
|
|
return strapi.db.query(uid).create({
|
|
data: transform(data)
|
|
});
|
|
};
|
|
const deleteComponent = async (uid, componentToDelete)=>{
|
|
await deleteComponents(uid, componentToDelete);
|
|
await strapi.db.query(uid).delete({
|
|
where: {
|
|
id: componentToDelete.id
|
|
}
|
|
});
|
|
};
|
|
/**
|
|
* Resolve the component UID of an entity's attribute based
|
|
* on a given path (components & dynamic zones only)
|
|
*/ const resolveComponentUID = ({ paths, strapi: strapi1, data, contentType })=>{
|
|
let value = data;
|
|
let cType = contentType;
|
|
for (const path of paths){
|
|
value = get(path, value);
|
|
// Needed when the value of cType should be computed
|
|
// based on the next value (eg: dynamic zones)
|
|
if (typeof cType === 'function') {
|
|
cType = cType(value);
|
|
}
|
|
if (path in cType.attributes) {
|
|
const attribute = cType.attributes[path];
|
|
if (attribute.type === 'component') {
|
|
cType = strapi1.getModel(attribute.component);
|
|
}
|
|
if (attribute.type === 'dynamiczone') {
|
|
cType = ({ __component })=>strapi1.getModel(__component);
|
|
}
|
|
}
|
|
}
|
|
if ('uid' in cType) {
|
|
return cType.uid;
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
export { createComponents, deleteComponent, deleteComponents, getComponents, omitComponentData, resolveComponentUID };
|
|
//# sourceMappingURL=components.mjs.map
|