2023-02-09 11:35:50 +01:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
const {
|
|
|
|
curry,
|
|
|
|
isString,
|
|
|
|
isArray,
|
|
|
|
eq,
|
|
|
|
constant,
|
|
|
|
split,
|
|
|
|
isObject,
|
|
|
|
trim,
|
|
|
|
isNil,
|
|
|
|
cloneDeep,
|
|
|
|
join,
|
|
|
|
first,
|
|
|
|
} = require('lodash/fp');
|
|
|
|
|
|
|
|
const traverseFactory = require('./factory');
|
|
|
|
|
|
|
|
const isKeyword =
|
|
|
|
(keyword) =>
|
|
|
|
({ key, attribute }) => {
|
|
|
|
return !attribute && keyword === key;
|
|
|
|
};
|
|
|
|
const isStringArray = (value) => isArray(value) && value.every(isString);
|
|
|
|
|
|
|
|
const populate = traverseFactory()
|
|
|
|
// Array of strings ['foo', 'foo.bar'] => map(recurse), then filter out empty items
|
|
|
|
.intercept(isStringArray, async (visitor, options, populate, { recurse }) => {
|
|
|
|
return Promise.all(populate.map((nestedPopulate) => recurse(visitor, options, nestedPopulate)));
|
|
|
|
})
|
|
|
|
// Return wildcards as is
|
|
|
|
.intercept(eq('*'), constant('*'))
|
|
|
|
// Parse string values
|
|
|
|
.parse(isString, () => {
|
|
|
|
const tokenize = split('.');
|
|
|
|
const recompose = join('.');
|
|
|
|
|
|
|
|
return {
|
|
|
|
transform: trim,
|
|
|
|
|
|
|
|
remove(key, data) {
|
|
|
|
const [root] = tokenize(data);
|
|
|
|
|
|
|
|
return root === key ? undefined : data;
|
|
|
|
},
|
|
|
|
|
|
|
|
set(key, value, data) {
|
|
|
|
const [root] = tokenize(data);
|
|
|
|
|
|
|
|
if (root !== key) {
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
return isNil(value) ? root : `${root}.${value}`;
|
|
|
|
},
|
|
|
|
|
|
|
|
keys(data) {
|
|
|
|
return [first(tokenize(data))];
|
|
|
|
},
|
|
|
|
|
|
|
|
get(key, data) {
|
|
|
|
const [root, ...rest] = tokenize(data);
|
|
|
|
|
|
|
|
return key === root ? recompose(rest) : undefined;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
})
|
|
|
|
// Parse object values
|
|
|
|
.parse(isObject, () => ({
|
|
|
|
transform: cloneDeep,
|
|
|
|
|
|
|
|
remove(key, data) {
|
|
|
|
const { [key]: ignored, ...rest } = data;
|
|
|
|
|
|
|
|
return rest;
|
|
|
|
},
|
|
|
|
|
|
|
|
set(key, value, data) {
|
|
|
|
return { ...data, [key]: value };
|
|
|
|
},
|
|
|
|
|
|
|
|
keys(data) {
|
|
|
|
return Object.keys(data);
|
|
|
|
},
|
|
|
|
|
|
|
|
get(key, data) {
|
|
|
|
return data[key];
|
|
|
|
},
|
|
|
|
}))
|
|
|
|
.ignore(({ key, attribute }) => {
|
|
|
|
return ['sort', 'filters', 'fields'].includes(key) && !attribute;
|
|
|
|
})
|
|
|
|
.on(
|
|
|
|
// Handle recursion on populate."populate"
|
|
|
|
isKeyword('populate'),
|
|
|
|
async ({ key, visitor, path, value, schema }, { set, recurse }) => {
|
|
|
|
const newValue = await recurse(visitor, { schema, path }, value);
|
|
|
|
|
|
|
|
set(key, newValue);
|
|
|
|
}
|
|
|
|
)
|
|
|
|
.on(isKeyword('on'), async ({ key, visitor, path, value }, { set, recurse }) => {
|
|
|
|
const newOn = {};
|
|
|
|
|
|
|
|
for (const [uid, subPopulate] of Object.entries(value)) {
|
|
|
|
const model = strapi.getModel(uid);
|
|
|
|
const newPath = { ...path, raw: `${path.raw}[${uid}]` };
|
|
|
|
|
|
|
|
const newSubPopulate = await recurse(visitor, { schema: model, path: newPath }, subPopulate);
|
|
|
|
|
|
|
|
newOn[uid] = newSubPopulate;
|
|
|
|
}
|
|
|
|
|
|
|
|
set(key, newOn);
|
|
|
|
})
|
|
|
|
// Handle populate on relation
|
|
|
|
.onRelation(async ({ key, value, attribute, visitor, path, schema }, { set, recurse }) => {
|
|
|
|
const isMorphRelation = attribute.relation.toLowerCase().startsWith('morph');
|
|
|
|
|
|
|
|
if (isMorphRelation) {
|
|
|
|
// Don't traverse values that cannot be parsed
|
|
|
|
if (!isObject(value) || !isObject(value?.on)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there is a populate fragment defined, traverse it
|
|
|
|
const newValue = await recurse(visitor, { schema, path }, { on: value.on });
|
|
|
|
|
|
|
|
set(key, { on: newValue });
|
|
|
|
}
|
|
|
|
|
|
|
|
const targetSchemaUID = attribute.target;
|
|
|
|
const targetSchema = strapi.getModel(targetSchemaUID);
|
|
|
|
|
|
|
|
const newValue = await recurse(visitor, { schema: targetSchema, path }, value);
|
|
|
|
|
|
|
|
set(key, newValue);
|
|
|
|
})
|
|
|
|
// Handle populate on media
|
|
|
|
.onMedia(async ({ key, path, visitor, value }, { recurse, set }) => {
|
|
|
|
const targetSchemaUID = 'plugin::upload.file';
|
|
|
|
const targetSchema = strapi.getModel(targetSchemaUID);
|
|
|
|
|
|
|
|
const newValue = await recurse(visitor, { schema: targetSchema, path }, value);
|
|
|
|
|
|
|
|
set(key, newValue);
|
|
|
|
})
|
|
|
|
// Handle populate on components
|
|
|
|
.onComponent(async ({ key, value, visitor, path, attribute }, { recurse, set }) => {
|
|
|
|
const targetSchema = strapi.getModel(attribute.component);
|
|
|
|
|
|
|
|
const newValue = await recurse(visitor, { schema: targetSchema, path }, value);
|
|
|
|
|
|
|
|
set(key, newValue);
|
|
|
|
})
|
|
|
|
// Handle populate on dynamic zones
|
|
|
|
.onDynamicZone(async ({ key, value, attribute, schema, visitor, path }, { set, recurse }) => {
|
|
|
|
if (isObject(value)) {
|
|
|
|
const { components } = attribute;
|
|
|
|
const { on, ...properties } = value;
|
|
|
|
|
2023-03-16 14:30:07 +01:00
|
|
|
const newValue = {};
|
|
|
|
|
2023-02-09 11:35:50 +01:00
|
|
|
// Handle legacy DZ params
|
|
|
|
let newProperties = properties;
|
|
|
|
|
|
|
|
for (const componentUID of components) {
|
|
|
|
const componentSchema = strapi.getModel(componentUID);
|
|
|
|
newProperties = await recurse(visitor, { schema: componentSchema, path }, newProperties);
|
|
|
|
}
|
|
|
|
|
2023-03-16 14:30:07 +01:00
|
|
|
Object.assign(newValue, newProperties);
|
|
|
|
|
2023-02-09 11:35:50 +01:00
|
|
|
// Handle new morph fragment syntax
|
2023-03-16 14:30:07 +01:00
|
|
|
if (on) {
|
|
|
|
const newOn = await recurse(visitor, { schema, path }, { on });
|
2023-02-09 11:35:50 +01:00
|
|
|
|
2023-03-16 14:30:07 +01:00
|
|
|
// Recompose both syntaxes
|
|
|
|
Object.assign(newValue, newOn);
|
|
|
|
}
|
2023-02-09 11:35:50 +01:00
|
|
|
|
|
|
|
set(key, newValue);
|
|
|
|
} else {
|
|
|
|
const newValue = await recurse(visitor, { schema, path }, value);
|
|
|
|
|
|
|
|
set(key, newValue);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
module.exports = curry(populate.traverse);
|