diff --git a/packages/core/upload/server/controllers/admin-file.js b/packages/core/upload/server/controllers/admin-file.js index 644549c57d..58a20b9726 100644 --- a/packages/core/upload/server/controllers/admin-file.js +++ b/packages/core/upload/server/controllers/admin-file.js @@ -1,6 +1,7 @@ 'use strict'; const { merge } = require('lodash/fp'); +const { mapAsync } = require('@strapi/utils'); const { getService } = require('../utils'); const { ACTIONS, FILE_MODEL_UID } = require('../constants'); const { findEntityAndCheckPermissions } = require('./utils/find-entity-and-check-permissions'); @@ -25,14 +26,14 @@ module.exports = { const query = pm.addPermissionsQueryTo(merge(defaultQuery, ctx.query)); - const { results, pagination } = await getService('upload').findPage(query); + const { results: files, pagination } = await getService('upload').findPage(query); - const fileService = await getService('file'); - await Promise.all(results.map((file) => fileService.signFileUrls(file))); + // Sign file urls for private providers + const signedFiles = await mapAsync(files, getService('file').signFileUrls); - const sanitizedResults = await pm.sanitizeOutput(results); + const sanitizedFiles = await pm.sanitizeOutput(signedFiles); - return { results: sanitizedResults, pagination }; + return { results: sanitizedFiles, pagination }; }, async findOne(ctx) { diff --git a/packages/core/upload/server/register.js b/packages/core/upload/server/register.js index f36d7b713f..8ecc75301a 100644 --- a/packages/core/upload/server/register.js +++ b/packages/core/upload/server/register.js @@ -17,7 +17,7 @@ module.exports = async ({ strapi }) => { await registerUploadMiddleware({ strapi }); - getService('file').addSignedFileUrlsToAdmin(); + getService('extensions').contentManager.entityManager.addSignedFileUrlsToAdmin(); if (strapi.plugin('graphql')) { require('./graphql')({ strapi }); diff --git a/packages/core/upload/server/services/extensions/content-manager/entity-manager.js b/packages/core/upload/server/services/extensions/content-manager/entity-manager.js index eb2695762e..2050920b62 100644 --- a/packages/core/upload/server/services/extensions/content-manager/entity-manager.js +++ b/packages/core/upload/server/services/extensions/content-manager/entity-manager.js @@ -3,54 +3,59 @@ const { mapAsync } = require('@strapi/utils'); const { getService } = require('../../../utils'); +const getSignedAttribute = (attributeName, entity, model) => { + const { signFileUrls } = getService('file'); + + if (!entity) return entity; + + const attribute = model.attributes[attributeName]; + + switch (attribute?.type) { + case 'media': + if (attribute.multiple) { + return mapAsync(entity, signFileUrls); + } + return signFileUrls(entity); + case 'component': + if (attribute.repeatable) { + return mapAsync(entity, (component) => signEntityMedia(component, attribute.component)); + } + return signEntityMedia(entity, attribute.component); + case 'dynamiczone': + return mapAsync(entity, (component) => signEntityMedia(component, component.__component)); + default: + return entity; + } +}; + /** * * Iterate through an entity manager result * Check which modelAttributes are media and pre sign the image URLs * if they are from the current upload provider + * + * TODO: Create a strapi-utils function to iterate through an entity + * and apply a function on each attribute type + * * @param {Object} entity * @param {Object} modelAttributes - * @param {String} providerConfig * @returns */ const signEntityMedia = async (entity, uid) => { const model = strapi.getModel(uid); - const { signFileUrls } = getService('file'); - for (const [key, value] of Object.entries(entity)) { - // eslint-disable-next-line no-continue - if (!value) continue; + const signedEntity = {}; - const attribute = model.attributes[key]; - - switch (attribute?.type) { - case 'media': - if (attribute.multiple) { - await mapAsync(value, signFileUrls); - } else { - await signFileUrls(value); - } - break; - case 'component': - if (attribute.repeatable) { - await Promise.all( - value.map((component) => signEntityMedia(component, attribute.component)) - ); - } else { - await signEntityMedia(value, attribute.component); - } - break; - case 'dynamiczone': - await Promise.all( - value.map((component) => signEntityMedia(component, component.__component)) - ); - break; - default: - break; - } + // TODO: Use asyncReduce + for (const attributeName of Object.keys(entity)) { + signedEntity[attributeName] = await getSignedAttribute( + attributeName, + entity[attributeName], + model + ); } - return entity; + return signedEntity; }; const addSignedFileUrlsToAdmin = () => { @@ -67,46 +72,39 @@ const addSignedFileUrlsToAdmin = () => { // - What about the webhooks emitted by the entity manager? // - Do we want to sign the file urls in the event payload? // Test for every case in the Content manager so we don't miss any - // Make entity file signing non mutating - // Move this extend into a folder called /extensions // Can we simplify the way to extend the content manager? - - // TOPICS: - // What about the webhooks emitted by the entity manager? - // Do we want to sign the file urls in the event payload? - // We need to do this for create/update/delete/publish/unpublish too no? + // Documentation strapi.container .get('services') .extend(`plugin::content-manager.entity-manager`, (entityManager) => { const update = async (entity, body, uid) => { const updatedEntity = await entityManager.update(entity, body, uid); - await signEntityMedia(updatedEntity, uid); - return updatedEntity; + return signEntityMedia(updatedEntity, uid); }; const publish = async (entity, body, uid) => { const publishedEntity = await entityManager.publish(entity, body, uid); - await signEntityMedia(publishedEntity, uid); - return publishedEntity; + return signEntityMedia(publishedEntity, uid); }; const unpublish = async (entity, body, uid) => { const unpublishedEntity = await entityManager.unpublish(entity, body, uid); - await signEntityMedia(unpublishedEntity, uid); - return unpublishedEntity; + return signEntityMedia(unpublishedEntity, uid); }; const findOneWithCreatorRolesAndCount = async (id, uid) => { // TODO: What if the entity is not found? const entity = await entityManager.findOneWithCreatorRolesAndCount(id, uid); - await signEntityMedia(entity, uid); - return entity; + return signEntityMedia(entity, uid); }; const findWithRelationCountsPage = async (opts, uid) => { const entities = await entityManager.findWithRelationCountsPage(opts, uid); - await mapAsync(entities.results, async (entity) => signEntityMedia(entity, uid)); - return entities; + const results = await mapAsync(entities.results, async (entity) => + signEntityMedia(entity, uid) + ); + + return { ...entities, results }; }; return { diff --git a/packages/core/upload/server/services/file.js b/packages/core/upload/server/services/file.js index b8b5b17cff..938168d936 100644 --- a/packages/core/upload/server/services/file.js +++ b/packages/core/upload/server/services/file.js @@ -1,5 +1,6 @@ 'use strict'; +const { cloneDeep } = require('lodash/fp'); const { mapAsync } = require('@strapi/utils'); const { FOLDER_MODEL_UID, FILE_MODEL_UID } = require('../constants'); const { getService } = require('../utils'); @@ -24,20 +25,17 @@ const deleteByIds = async (ids = []) => { const signFileUrl = async (fileIdentifier) => { const { provider } = strapi.plugins.upload; - const { url } = await provider.getSignedUrl(fileIdentifier); - return url; }; -// TODO: Make this non mutating? const signFileUrls = async (file) => { const { provider } = strapi.plugins.upload; const { provider: providerConfig } = strapi.config.get('plugin.upload'); // Check file provider and if provider is private if (file.provider !== providerConfig || !provider.isPrivate()) { - return; + return file; } const signUrl = async (file) => { @@ -45,11 +43,15 @@ const signFileUrls = async (file) => { file.url = signedUrl.url; }; + const signedFile = cloneDeep(file); + // Sign each file format - await signUrl(file); + await signUrl(signedFile); if (file.formats) { - await mapAsync(Object.values(file.formats), signUrl); + await mapAsync(Object.values(signedFile.formats), signUrl); } + + return signedFile; }; module.exports = {