diff --git a/README.md b/README.md index a2e74ba..63e8389 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Strapi plugin populate-deep + This plugin allows for easier population of deep content structures using the rest API. # Installation @@ -7,7 +8,6 @@ This plugin allows for easier population of deep content structures using the re `yarn add strapi-plugin-populate-deep` - # Usages ## Examples @@ -28,25 +28,39 @@ Populate a request with the a custom depth The default max depth is 5 levels deep. -The populate deep option is available for all collections and single types using the findOne and findMany methods. +The populate deep option is available for all collections and single types using the `findOne` and `findMany` methods. # Configuration -The default depth can be customized via the plugin config. To do so create or edit you plugins.js file. +The default depth and custom depth for certain fields can be customized via the plugin config. To do so create or edit you `plugins.js` file. + +To avoid cyclic population you might need to ignore certain fields. + +To avoid too much data, especially when you are working with parent-child relationships, adjust the depth of population of these fields using the `fields` option. + +To find out what collections and fields are populated, set the debug option to true. ## Example configuration `config/plugins.js` -``` +```js module.exports = ({ env }) => ({ 'strapi-plugin-populate-deep': { config: { defaultDepth: 3, // Default is 5 + ignore: ['field1', 'collectionName.field2'], // default is [] + fields: [ + { collectionName: 'collectionName', field: 'field1', depth: 1 }, + { field: 'field2', depth: 3 }, // for all collections + ], + debug: true, // default is false + skipCreatorFields: true, // default is false, skips all fields of model admin::user } }, }); ``` # Contributions + The original idea for getting the populate structure was created by [tomnovotny7](https://github.com/tomnovotny7) and can be found in [this](https://github.com/strapi/strapi/issues/11836) github thread diff --git a/server/bootstrap.js b/server/bootstrap.js index cb357c6..37dd5c5 100644 --- a/server/bootstrap.js +++ b/server/bootstrap.js @@ -7,10 +7,18 @@ module.exports = ({ strapi }) => { if (event.action === 'beforeFindMany' || event.action === 'beforeFindOne') { const populate = event.params?.populate; const defaultDepth = strapi.plugin('strapi-plugin-populate-deep')?.config('defaultDepth') || 5 + const ignore = strapi.plugin('strapi-plugin-populate-deep')?.config('ignore') || [] + const debug = strapi.plugin('strapi-plugin-populate-deep')?.config('debug') || false + + /** @type {import('./helpers').FieldPopulateConfiguration[]} */ + const fields = strapi.plugin('strapi-plugin-populate-deep')?.config('fields') || {} if (populate && populate[0] === 'deep') { const depth = populate[1] ?? defaultDepth - const modelObject = getFullPopulateObject(event.model.uid, depth, []); + debug && console.debug(`[strapi-plugin-populate-deep] DEBUG Deep populating for model ${event.model.uid}`, { + depth, ignore, fields + }) + const modelObject = getFullPopulateObject(event.model.uid, depth, ignore, fields, debug); event.params.populate = modelObject.populate } } diff --git a/server/helpers/index.js b/server/helpers/index.js index 7f026b8..81d559a 100644 --- a/server/helpers/index.js +++ b/server/helpers/index.js @@ -9,7 +9,23 @@ const getModelPopulationAttributes = (model) => { return model.attributes; }; -const getFullPopulateObject = (modelUid, maxDepth = 20, ignore) => { +/** + * @typedef {Object} FieldPopulateConfiguration + * @property {string?} collectionName - optional, if not given, it will be considered for all collections + * @property {string} field + * @property {number} depth + * @exports FieldPopulateConfiguration + */ + +/** + * @param {string} modelUid + * @param {number} maxDepth + * @param {string[]?} ignore + * @param {FieldPopulateConfiguration[]?} fieldPopulateConfigurations + * @param {boolean?} debug + * @returns + */ +const getFullPopulateObject = (modelUid, maxDepth = 20, ignore, fieldPopulateConfigurations, debug = false) => { const skipCreatorFields = strapi.plugin('strapi-plugin-populate-deep')?.config('skipCreatorFields'); if (maxDepth <= 1) { @@ -21,25 +37,41 @@ const getFullPopulateObject = (modelUid, maxDepth = 20, ignore) => { const populate = {}; const model = strapi.getModel(modelUid); - if (ignore && !ignore.includes(model.collectionName)) ignore.push(model.collectionName) + for (const [key, value] of Object.entries( getModelPopulationAttributes(model) )) { - if (ignore?.includes(key)) continue + if (ignore?.includes(key) || ignore?.includes(model.collectionName + '.' + key)) { + debug && console.debug(`[strapi-plugin-populate-deep] DEBUG Ignoring collectionName: ${model.collectionName}, field: ${key}`) + continue + } + + let depth = maxDepth + if (fieldPopulateConfigurations) { + debug && console.debug(`[strapi-plugin-populate-deep] DEBUG collectionName: ${model.collectionName}, field: ${key}`) + const fieldPopulateConfiguration = fieldPopulateConfigurations.find(f => f.field === key && (!f.collectionName || f.collectionName === model.collectionName)) + if (fieldPopulateConfiguration) { + debug && console.debug(`[strapi-plugin-populate-deep] DEBUG Overriding depth for collectionName: ${model.collectionName}, field: ${key} to ${fieldPopulateConfiguration.depth}`) + depth = fieldPopulateConfiguration.depth + } + } + if (value) { if (value.type === "component") { - populate[key] = getFullPopulateObject(value.component, maxDepth - 1); - } else if (value.type === "dynamiczone") { + populate[key] = getFullPopulateObject(value.component, depth - 1, ignore, fieldPopulateConfigurations, debug); + } else if (value.type === "dynamiczone" && depth > 1) { const dynamicPopulate = value.components.reduce((prev, cur) => { - const curPopulate = getFullPopulateObject(cur, maxDepth - 1); + const curPopulate = getFullPopulateObject(cur, depth - 1, ignore, fieldPopulateConfigurations, debug); return curPopulate === true ? prev : merge(prev, curPopulate); }, {}); populate[key] = isEmpty(dynamicPopulate) ? true : dynamicPopulate; - } else if (value.type === "relation") { + } else if (value.type === "relation" && depth > 1) { const relationPopulate = getFullPopulateObject( value.target, - (key === 'localizations') && maxDepth > 2 ? 1 : maxDepth - 1, - ignore + (key === 'localizations') && depth > 2 ? 1 : depth - 1, + ignore, + fieldPopulateConfigurations, + debug, ); if (relationPopulate) { populate[key] = relationPopulate;