'use strict'; const { cloneDeep, isObject, isArray, isNil, curry } = require('lodash/fp'); const traverseEntity = async (visitor, options, entity) => { const { path = null, schema } = options; // End recursion if (!isObject(entity) || isNil(schema)) { return entity; } // Don't mutate the original entity object const copy = cloneDeep(entity); for (const key of Object.keys(copy)) { // Retrieve the attribute definition associated to the key from the schema const attribute = schema.attributes[key]; // If the attribute doesn't exist within the schema, ignore it if (isNil(attribute)) { continue; } const newPath = path ? `${path}.${key}` : key; // Visit the current attribute const visitorOptions = { data: copy, schema, key, value: copy[key], attribute, path: newPath }; const visitorUtils = createVisitorUtils({ data: copy }); await visitor(visitorOptions, visitorUtils); // Extract the value for the current key (after calling the visitor) const value = copy[key]; // Ignore Nil values if (isNil(value)) { continue; } const isRelation = attribute.type === 'relation'; const isComponent = attribute.type === 'component'; const isDynamicZone = attribute.type === 'dynamiczone'; if (isRelation) { const isMorphRelation = attribute.relation.toLowerCase().startsWith('morph'); const traverseTarget = entry => { // Handle polymorphic relationships const targetSchemaUID = isMorphRelation ? entry.__type : attribute.target; const targetSchema = strapi.getModel(targetSchemaUID); const traverseOptions = { schema: targetSchema, path: newPath }; return traverseEntity(visitor, traverseOptions, entry); }; // need to update copy copy[key] = isArray(value) ? await Promise.all(value.map(traverseTarget)) : await traverseTarget(value); } if (isComponent) { const targetSchema = strapi.getModel(attribute.component); const traverseOptions = { schema: targetSchema, path: newPath }; const traverseComponent = entry => traverseEntity(visitor, traverseOptions, entry); copy[key] = isArray(value) ? await Promise.all(value.map(traverseComponent)) : await traverseComponent(value); } if (isDynamicZone && isArray(value)) { const visitDynamicZoneEntry = entry => { const targetSchema = strapi.getModel(entry.__component); const traverseOptions = { schema: targetSchema, path: newPath }; return traverseEntity(visitor, traverseOptions, entry); }; copy[key] = await Promise.all(value.map(visitDynamicZoneEntry)); } } return copy; }; const createVisitorUtils = ({ data }) => ({ remove(key) { delete data[key]; }, set(key, value) { data[key] = value; }, }); module.exports = curry(traverseEntity);