diff --git a/packages/core/upload/server/controllers/admin-folder-file.js b/packages/core/upload/server/controllers/admin-folder-file.js index 30c3e6c34b..7261f1e0d9 100644 --- a/packages/core/upload/server/controllers/admin-folder-file.js +++ b/packages/core/upload/server/controllers/admin-folder-file.js @@ -1,5 +1,6 @@ 'use strict'; +const { joinBy } = require('@strapi/utils'); const { getService } = require('../utils'); const { ACTIONS, FOLDER_MODEL_UID, FILE_MODEL_UID } = require('../constants'); const { @@ -43,7 +44,7 @@ module.exports = { async moveMany(ctx) { const { body } = ctx.request; const { - state: { userAbility, user }, + state: { userAbility }, } = ctx; const pmFolder = strapi.admin.services.permission.createPermissionsManager({ @@ -60,25 +61,136 @@ module.exports = { await validateMoveManyFoldersFiles(body); const { folderIds = [], fileIds = [], destinationFolderId } = body; - const uploadService = getService('upload'); - const folderService = getService('folder'); + const trx = await strapi.db.transaction(); + try { + // fetch folders + const existingFolders = await strapi.db + .queryBuilder(FOLDER_MODEL_UID) + .select(['id', 'uid', 'path']) + .where({ id: { $in: folderIds } }) + .transacting(trx) + .forUpdate() + .execute(); - const updatedFolders = []; - // updates are done in order (not in parallele) to avoid mixing queries (path) - for (let folderId of folderIds) { - const updatedFolder = await folderService.update( - folderId, - { parent: destinationFolderId }, - { user } - ); - updatedFolders.push(updatedFolder); + // fetch files + const existingFiles = await strapi.db + .queryBuilder(FILE_MODEL_UID) + .select(['id']) + .where({ id: { $in: fileIds } }) + .transacting(trx) + .forUpdate() + .execute(); + + // fetch destinationFolder path + let destinationFolderPath = '/'; + if (destinationFolderId !== null) { + const destinationFolder = await strapi.db + .queryBuilder(FOLDER_MODEL_UID) + .select('path') + .where({ id: destinationFolderId }) + .transacting(trx) + .first() + .execute(); + destinationFolderPath = destinationFolder.path; + } + + const fileTable = strapi.getModel(FILE_MODEL_UID).collectionName; + const folderTable = strapi.getModel(FOLDER_MODEL_UID).collectionName; + const folderPathColName = strapi.db.metadata.get(FILE_MODEL_UID).attributes.folderPath + .columnName; + const pathColName = strapi.db.metadata.get(FOLDER_MODEL_UID).attributes.path.columnName; + + if (existingFolders.length > 0) { + // update folders' parent relation (delete + insert; upsert not possible) + const joinTable = strapi.db.metadata.get(FOLDER_MODEL_UID).attributes.parent.joinTable; + await strapi.db + .queryBuilder(joinTable.name) + .transacting(trx) + .delete() + .where({ [joinTable.joinColumn.name]: { $in: folderIds } }) + .execute(); + await strapi.db + .queryBuilder(joinTable.name) + .transacting(trx) + .insert( + existingFolders.map(folder => ({ + [joinTable.inverseJoinColumn.name]: destinationFolderId, + [joinTable.joinColumn.name]: folder.id, + })) + ) + .execute(); + + for (const existingFolder of existingFolders) { + // update path for folders themselves & folders below + await strapi.db + .connection(folderTable) + .transacting(trx) + .where(pathColName, 'like', `${existingFolder.path}%`) + .update( + pathColName, + strapi.db.connection.raw('REPLACE(??, ?, ?)', [ + pathColName, + existingFolder.path, + joinBy('/', destinationFolderPath, existingFolder.uid), + ]) + ); + + // update path of files below + await strapi.db + .connection(fileTable) + .transacting(trx) + .where(folderPathColName, 'like', `${existingFolder.path}%`) + .update( + folderPathColName, + strapi.db.connection.raw('REPLACE(??, ?, ?)', [ + folderPathColName, + existingFolder.path, + joinBy('/', destinationFolderPath, existingFolder.uid), + ]) + ); + } + } + + if (existingFiles.length > 0) { + // update files' folder relation (delete + insert; upsert not possible) + const fileJoinTable = strapi.db.metadata.get(FILE_MODEL_UID).attributes.folder.joinTable; + await strapi.db + .queryBuilder(fileJoinTable.name) + .transacting(trx) + .delete() + .where({ [fileJoinTable.joinColumn.name]: { $in: fileIds } }) + .execute(); + await strapi.db + .queryBuilder(fileJoinTable.name) + .transacting(trx) + .insert( + existingFiles.map(file => ({ + [fileJoinTable.inverseJoinColumn.name]: destinationFolderId, + [fileJoinTable.joinColumn.name]: file.id, + })) + ) + .execute(); + + // update files main fields (path + updatedBy) + await strapi.db + .connection(fileTable) + .transacting(trx) + .whereIn('id', fileIds) + .update(folderPathColName, destinationFolderPath); + } + + await trx.commit(); + } catch (e) { + await trx.rollback(); + throw e; } - const updatedFiles = await Promise.all( - fileIds.map(fileId => - uploadService.updateFileInfo(fileId, { folder: destinationFolderId }, { user }) - ) - ); + const updatedFolders = await strapi.entityService.findMany(FOLDER_MODEL_UID, { + filters: { id: { $in: folderIds } }, + }); + const updatedFiles = await strapi.entityService.findMany(FILE_MODEL_UID, { + filters: { id: { $in: fileIds } }, + }); ctx.body = { data: { diff --git a/packages/core/upload/server/services/folder.js b/packages/core/upload/server/services/folder.js index eb8ed95c1a..757b4f0c9f 100644 --- a/packages/core/upload/server/services/folder.js +++ b/packages/core/upload/server/services/folder.js @@ -102,12 +102,18 @@ const update = async (id, { name, parent }, { user }) => { .first() .execute(); - // update parent folder + // update parent folder (delete + insert; upsert not possible) const joinTable = strapi.db.metadata.get(FOLDER_MODEL_UID).attributes.parent.joinTable; await strapi.db .queryBuilder(joinTable.name) .transacting(trx) - .update({ [joinTable.inverseJoinColumn.name]: parent }) + .delete() + .where({ [joinTable.joinColumn.name]: id }) + .execute(); + await strapi.db + .queryBuilder(joinTable.name) + .transacting(trx) + .insert({ [joinTable.inverseJoinColumn.name]: parent, [joinTable.joinColumn.name]: id }) .where({ [joinTable.joinColumn.name]: id }) .execute();