From 1cc853a5eb12c5df2f079581bb9e7fa3c1f6c0b9 Mon Sep 17 00:00:00 2001 From: Marc-Roig Date: Tue, 21 Mar 2023 14:09:30 +0100 Subject: [PATCH] clone components --- .../lib/services/entity-service/components.js | 106 ++++++++++++++++++ .../lib/services/entity-service/index.js | 5 +- 2 files changed, 108 insertions(+), 3 deletions(-) diff --git a/packages/core/strapi/lib/services/entity-service/components.js b/packages/core/strapi/lib/services/entity-service/components.js index ebeb293df7..180126cd26 100644 --- a/packages/core/strapi/lib/services/entity-service/components.js +++ b/packages/core/strapi/lib/services/entity-service/components.js @@ -322,6 +322,91 @@ const deleteComponents = async (uid, entityToDelete, { loadComponents = true } = } }; +const cloneComponents = async (uid, entityToClone, data) => { + const { attributes = {} } = strapi.getModel(uid); + + const componentBody = {}; + const componentData = await getComponents(uid, entityToClone); + + for (const attributeName of Object.keys(attributes)) { + const attribute = attributes[attributeName]; + + // If the attribute is not set or on the component to clone, skip it + if (!has(attributeName, data) && !has(attributeName, componentData)) { + continue; + } + + if (attribute.type === 'component') { + const { component: componentUID, repeatable = false } = attribute; + + const componentValue = has(attributeName, data) + ? data[attributeName] + : componentData[attributeName]; + + 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 mapAsync( + componentValue, + (value) => cloneComponent(componentUID, value), + { concurrency: isDialectMySQL() ? 1 : Infinity } + ); + + componentBody[attributeName] = components.filter(_.negate(_.isNil)).map(({ id }) => { + return { + id, + __pivot: { + field: attributeName, + component_type: componentUID, + }, + }; + }); + } else { + const component = await cloneComponent(componentUID, componentValue); + componentBody[attributeName] = component && { + id: component.id, + __pivot: { + field: attributeName, + component_type: componentUID, + }, + }; + } + + continue; + } + + if (attribute.type === 'dynamiczone') { + const dynamiczoneValues = has(attributeName, data) + ? data[attributeName] + : componentData[attributeName]; + + if (!Array.isArray(dynamiczoneValues)) { + throw new Error('Expected an array to create repeatable component'); + } + // MySQL/MariaDB can cause deadlocks here if concurrency higher than 1 + componentBody[attributeName] = await mapAsync( + dynamiczoneValues, + async (value) => { + const { id } = await cloneComponent(value.__component, value); + return { + id, + __component: value.__component, + __pivot: { + field: attributeName, + }, + }; + }, + { concurrency: isDialectMySQL() ? 1 : Infinity } + ); + continue; + } + } + + return componentBody; +}; /** ************************* Component queries ************************** */ @@ -377,6 +462,26 @@ const deleteComponent = async (uid, componentToDelete) => { await strapi.query(uid).delete({ where: { id: componentToDelete.id } }); }; +const cloneComponent = async (uid, data) => { + const model = strapi.getModel(uid); + + if (!has('id', data)) { + return createComponent(uid, data); + } + + const componentData = await cloneComponents(uid, { id: data.id }, 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.query(uid).clone(data.id, { data: transform(data) }); +}; + module.exports = { omitComponentData, getComponents, @@ -384,4 +489,5 @@ module.exports = { updateComponents, deleteComponents, deleteComponent, + cloneComponents, }; diff --git a/packages/core/strapi/lib/services/entity-service/index.js b/packages/core/strapi/lib/services/entity-service/index.js index 0aec78ff4b..5784775dad 100644 --- a/packages/core/strapi/lib/services/entity-service/index.js +++ b/packages/core/strapi/lib/services/entity-service/index.js @@ -16,6 +16,7 @@ const { createComponents, updateComponents, deleteComponents, + cloneComponents, } = require('./components'); const { pickSelectionParams } = require('./params'); const { applyTransforms } = require('./attributes'); @@ -291,9 +292,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator }) const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams)); // TODO: wrap into transaction - // TODO: Implement cloneComponents - // const componentData = await cloneComponents(uid, validData); - const componentData = await createComponents(uid, validData); + const componentData = await cloneComponents(uid, entityToClone, validData); const entityData = creationPipeline( Object.assign(omitComponentData(model, validData), componentData),