@strapi/data-transfer
Version:
Data transfer capabilities for Strapi
172 lines (169 loc) • 7.37 kB
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