mirror of
https://github.com/strapi/strapi.git
synced 2025-11-06 21:29:24 +00:00
add bulk move route
This commit is contained in:
parent
352d16a47f
commit
34372daf8b
@ -1,11 +1,32 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { getService } = require('../utils');
|
const { getService } = require('../utils');
|
||||||
const { validateDeleteManyFoldersFiles } = require('./validation/admin/folder-file');
|
const { ACTIONS } = require('../constants');
|
||||||
|
const {
|
||||||
|
validateDeleteManyFoldersFiles,
|
||||||
|
validateMoveManyFoldersFiles,
|
||||||
|
} = require('./validation/admin/folder-file');
|
||||||
|
|
||||||
|
const folderModel = 'plugin::upload.folder';
|
||||||
|
const fileModel = 'plugin::upload.file';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
async deleteMany(ctx) {
|
async deleteMany(ctx) {
|
||||||
const { body } = ctx.request;
|
const { body } = ctx.request;
|
||||||
|
const {
|
||||||
|
state: { userAbility },
|
||||||
|
} = ctx;
|
||||||
|
|
||||||
|
const pmFolder = strapi.admin.services.permission.createPermissionsManager({
|
||||||
|
ability: ctx.state.userAbility,
|
||||||
|
model: folderModel,
|
||||||
|
});
|
||||||
|
|
||||||
|
const pmFile = strapi.admin.services.permission.createPermissionsManager({
|
||||||
|
ability: userAbility,
|
||||||
|
action: ACTIONS.read,
|
||||||
|
model: fileModel,
|
||||||
|
});
|
||||||
|
|
||||||
await validateDeleteManyFoldersFiles(body);
|
await validateDeleteManyFoldersFiles(body);
|
||||||
|
|
||||||
@ -17,8 +38,55 @@ module.exports = {
|
|||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
data: {
|
data: {
|
||||||
files: deletedFiles,
|
files: await pmFile.sanitizeOutput(deletedFiles),
|
||||||
folders: deletedFolders,
|
folders: await pmFolder.sanitizeOutput(deletedFolders),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async moveMany(ctx) {
|
||||||
|
const { body } = ctx.request;
|
||||||
|
const {
|
||||||
|
state: { userAbility, user },
|
||||||
|
} = ctx;
|
||||||
|
|
||||||
|
const pmFolder = strapi.admin.services.permission.createPermissionsManager({
|
||||||
|
ability: ctx.state.userAbility,
|
||||||
|
model: folderModel,
|
||||||
|
});
|
||||||
|
|
||||||
|
const pmFile = strapi.admin.services.permission.createPermissionsManager({
|
||||||
|
ability: userAbility,
|
||||||
|
action: ACTIONS.read,
|
||||||
|
model: fileModel,
|
||||||
|
});
|
||||||
|
|
||||||
|
await validateMoveManyFoldersFiles(body);
|
||||||
|
const { folderIds = [], fileIds = [], destinationFolderId } = body;
|
||||||
|
|
||||||
|
const uploadService = getService('upload');
|
||||||
|
const folderService = getService('folder');
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedFiles = await Promise.all(
|
||||||
|
fileIds.map(fileId =>
|
||||||
|
uploadService.updateFileInfo(fileId, { folder: destinationFolderId }, { user })
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
data: {
|
||||||
|
files: await pmFile.sanitizeOutput(updatedFiles),
|
||||||
|
folders: await pmFolder.sanitizeOutput(updatedFolders),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -72,7 +72,7 @@ module.exports = {
|
|||||||
model: folderModel,
|
model: folderModel,
|
||||||
});
|
});
|
||||||
|
|
||||||
await validateUpdateFolder(body);
|
await validateUpdateFolder(id)(body);
|
||||||
|
|
||||||
const { update } = getService('folder');
|
const { update } = getService('folder');
|
||||||
|
|
||||||
|
|||||||
@ -1,22 +1,91 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const { intersection, map, isEmpty } = require('lodash/fp');
|
||||||
const { yup, validateYupSchema } = require('@strapi/utils');
|
const { yup, validateYupSchema } = require('@strapi/utils');
|
||||||
|
const { folderExists } = require('./utils');
|
||||||
|
|
||||||
|
const folderModel = 'plugin::upload.folder';
|
||||||
|
|
||||||
const validateDeleteManyFoldersFilesSchema = yup
|
const validateDeleteManyFoldersFilesSchema = yup
|
||||||
.object()
|
.object()
|
||||||
.shape({
|
.shape({
|
||||||
fileIds: yup
|
fileIds: yup.array().of(yup.strapiID().required()),
|
||||||
.array()
|
folderIds: yup.array().of(yup.strapiID().required()),
|
||||||
.min(1)
|
|
||||||
.of(yup.strapiID().required()),
|
|
||||||
folderIds: yup
|
|
||||||
.array()
|
|
||||||
.min(1)
|
|
||||||
.of(yup.strapiID().required()),
|
|
||||||
})
|
})
|
||||||
.noUnknown()
|
.noUnknown()
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
|
const validateStructureMoveManyFoldersFilesSchema = yup
|
||||||
|
.object()
|
||||||
|
.shape({
|
||||||
|
destinationFolderId: yup
|
||||||
|
.strapiID()
|
||||||
|
.nullable()
|
||||||
|
.test('folder-exists', 'destination folder does not exist', folderExists),
|
||||||
|
fileIds: yup.array().of(yup.strapiID().required()),
|
||||||
|
folderIds: yup.array().of(yup.strapiID().required()),
|
||||||
|
})
|
||||||
|
.noUnknown()
|
||||||
|
.required();
|
||||||
|
|
||||||
|
const validateDuplicatesMoveManyFoldersFilesSchema = yup
|
||||||
|
.object()
|
||||||
|
.test('are-folders-unique', 'some folders already exist', async function(value) {
|
||||||
|
const { folderIds, destinationFolderId } = value;
|
||||||
|
if (isEmpty(folderIds)) return true;
|
||||||
|
|
||||||
|
const folders = await strapi.entityService.findMany(folderModel, {
|
||||||
|
fields: ['name'],
|
||||||
|
filters: { id: { $in: folderIds } },
|
||||||
|
});
|
||||||
|
// TODO: handle when parent is null
|
||||||
|
const existingFolders = await strapi.entityService.findMany(folderModel, {
|
||||||
|
fields: ['name'],
|
||||||
|
filters: { parent: { id: destinationFolderId } },
|
||||||
|
});
|
||||||
|
const duplicatedNames = intersection(map('name', folders), map('name', existingFolders));
|
||||||
|
if (duplicatedNames.length > 0) {
|
||||||
|
return this.createError({
|
||||||
|
message: `some folders already exists: ${duplicatedNames.join(', ')}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const validateMoveFoldersNotInsideThemselvesSchema = yup
|
||||||
|
.object()
|
||||||
|
.test('dont-move-inside-self', 'folders cannot be moved inside themselves', async function(
|
||||||
|
value
|
||||||
|
) {
|
||||||
|
const { folderIds, destinationFolderId } = value;
|
||||||
|
if (destinationFolderId === null || isEmpty(folderIds)) return true;
|
||||||
|
|
||||||
|
const destinationFolder = await strapi.entityService.findOne(folderModel, destinationFolderId, {
|
||||||
|
fields: ['path'],
|
||||||
|
});
|
||||||
|
const folders = await strapi.entityService.findMany(folderModel, {
|
||||||
|
fields: ['name', 'path'],
|
||||||
|
filters: { id: { $in: folderIds } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const unmovableFoldersNames = folders
|
||||||
|
.filter(folder => destinationFolder.path.startsWith(folder.path))
|
||||||
|
.map(f => f.name);
|
||||||
|
if (unmovableFoldersNames.length > 0) {
|
||||||
|
return this.createError({
|
||||||
|
message: `folders cannot be moved inside themselves: ${unmovableFoldersNames.join(', ')}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
validateDeleteManyFoldersFiles: validateYupSchema(validateDeleteManyFoldersFilesSchema),
|
validateDeleteManyFoldersFiles: validateYupSchema(validateDeleteManyFoldersFilesSchema),
|
||||||
|
async validateMoveManyFoldersFiles(body) {
|
||||||
|
await validateYupSchema(validateStructureMoveManyFoldersFilesSchema)(body);
|
||||||
|
await validateYupSchema(validateDuplicatesMoveManyFoldersFilesSchema)(body);
|
||||||
|
await validateYupSchema(validateMoveFoldersNotInsideThemselvesSchema)(body);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,25 +1,27 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const { isUndefined, get } = require('lodash/fp');
|
||||||
const { yup, validateYupSchema } = require('@strapi/utils');
|
const { yup, validateYupSchema } = require('@strapi/utils');
|
||||||
const { isNil } = require('lodash/fp');
|
|
||||||
const { getService } = require('../../../utils');
|
const { getService } = require('../../../utils');
|
||||||
|
const { folderExists } = require('./utils');
|
||||||
|
|
||||||
|
const folderModel = 'plugin::upload.folder';
|
||||||
const NO_SLASH_REGEX = /^[^/]+$/;
|
const NO_SLASH_REGEX = /^[^/]+$/;
|
||||||
const NO_SPACES_AROUND = /^(?! ).+(?<! )$/;
|
const NO_SPACES_AROUND = /^(?! ).+(?<! )$/;
|
||||||
|
|
||||||
const folderExists = async folderId => {
|
const isNameUniqueInFolder = (id) => async function(name) {
|
||||||
if (isNil(folderId)) {
|
const { exists } = getService('folder');
|
||||||
return true;
|
const filters = { name, parent: this.parent.parent || null };
|
||||||
|
if (id) {
|
||||||
|
filters.id = { $ne: id };
|
||||||
|
|
||||||
|
if (isUndefined(name)) {
|
||||||
|
const existingFolder = await strapi.entityService.findOne(folderModel, id);
|
||||||
|
filters.name = get('name', existingFolder);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const exists = await getService('folder').exists({ id: folderId });
|
const doesExist = await exists(filters);
|
||||||
|
|
||||||
return exists;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isNameUniqueInFolder = async function(name) {
|
|
||||||
const { exists } = getService('folder');
|
|
||||||
const doesExist = await exists({ parent: this.parent.parent || null, name });
|
|
||||||
return !doesExist;
|
return !doesExist;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +34,7 @@ const validateCreateFolderSchema = yup
|
|||||||
.matches(NO_SLASH_REGEX, 'name cannot contain slashes')
|
.matches(NO_SLASH_REGEX, 'name cannot contain slashes')
|
||||||
.matches(NO_SPACES_AROUND, 'name cannot start or end with a whitespace')
|
.matches(NO_SPACES_AROUND, 'name cannot start or end with a whitespace')
|
||||||
.required()
|
.required()
|
||||||
.test('is-folder-unique', 'name already taken', isNameUniqueInFolder),
|
.test('is-folder-unique', 'folder already exists', isNameUniqueInFolder()),
|
||||||
parent: yup
|
parent: yup
|
||||||
.strapiID()
|
.strapiID()
|
||||||
.nullable()
|
.nullable()
|
||||||
@ -41,7 +43,8 @@ const validateCreateFolderSchema = yup
|
|||||||
.noUnknown()
|
.noUnknown()
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
const validateUpdateFolderSchema = yup
|
const validateUpdateFolderSchema = id =>
|
||||||
|
yup
|
||||||
.object()
|
.object()
|
||||||
.shape({
|
.shape({
|
||||||
name: yup
|
name: yup
|
||||||
@ -49,7 +52,7 @@ const validateUpdateFolderSchema = yup
|
|||||||
.min(1)
|
.min(1)
|
||||||
.matches(NO_SLASH_REGEX, 'name cannot contain slashes')
|
.matches(NO_SLASH_REGEX, 'name cannot contain slashes')
|
||||||
.matches(NO_SPACES_AROUND, 'name cannot start or end with a whitespace')
|
.matches(NO_SPACES_AROUND, 'name cannot start or end with a whitespace')
|
||||||
.test('is-folder-unique', 'name already taken', isNameUniqueInFolder),
|
.test('is-folder-unique', 'folder already exists', isNameUniqueInFolder(id)),
|
||||||
parent: yup
|
parent: yup
|
||||||
.strapiID()
|
.strapiID()
|
||||||
.nullable()
|
.nullable()
|
||||||
@ -60,5 +63,5 @@ const validateUpdateFolderSchema = yup
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
validateCreateFolder: validateYupSchema(validateCreateFolderSchema),
|
validateCreateFolder: validateYupSchema(validateCreateFolderSchema),
|
||||||
validateUpdateFolder: validateYupSchema(validateUpdateFolderSchema),
|
validateUpdateFolder: id => validateYupSchema(validateUpdateFolderSchema(id)),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { isNil } = require('lodash/fp');
|
||||||
|
const { getService } = require('../../../utils');
|
||||||
|
|
||||||
|
const folderExists = async folderId => {
|
||||||
|
if (isNil(folderId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exists = await getService('folder').exists({ id: folderId });
|
||||||
|
|
||||||
|
return exists;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
folderExists,
|
||||||
|
};
|
||||||
@ -171,5 +171,21 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
path: '/actions/bulk-move',
|
||||||
|
handler: 'admin-folder-file.moveMany',
|
||||||
|
config: {
|
||||||
|
policies: [
|
||||||
|
'admin::isAuthenticatedAdmin',
|
||||||
|
{
|
||||||
|
name: 'admin::hasPermissions',
|
||||||
|
config: {
|
||||||
|
actions: ['plugin::upload.read'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
433
packages/core/upload/tests/admin/folder-file.test.e2e.js
Normal file
433
packages/core/upload/tests/admin/folder-file.test.e2e.js
Normal file
@ -0,0 +1,433 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const { createTestBuilder } = require('../../../../../test/helpers/builder');
|
||||||
|
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
|
||||||
|
const { createAuthRequest } = require('../../../../../test/helpers/request');
|
||||||
|
|
||||||
|
let strapi;
|
||||||
|
let rq;
|
||||||
|
let data = {
|
||||||
|
folders: [],
|
||||||
|
files: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const createFolder = async (name, parent = null) => {
|
||||||
|
const res = await rq({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/upload/folders',
|
||||||
|
body: { name, parent },
|
||||||
|
});
|
||||||
|
return res.body.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createAFile = async (parent = null) => {
|
||||||
|
const res = await rq({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/upload',
|
||||||
|
formData: {
|
||||||
|
files: fs.createReadStream(path.join(__dirname, '../utils/rec.jpg')),
|
||||||
|
fileInfo: JSON.stringify({ folder: parent }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return res.body[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Bulk actions fot folders & files', () => {
|
||||||
|
const builder = createTestBuilder();
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
strapi = await createStrapiInstance();
|
||||||
|
rq = await createAuthRequest({ strapi });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await rq({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/upload/actions/bulk-delete',
|
||||||
|
body: {
|
||||||
|
folderIds: data.folders.map(f => f.id),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await strapi.destroy();
|
||||||
|
await builder.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('delete', () => {
|
||||||
|
test('Can delete folders and files', async () => {
|
||||||
|
const folder1 = await createFolder('folder-a-1', null);
|
||||||
|
const folder1a = await createFolder('folder-a-1a', folder1.id);
|
||||||
|
const folder1b = await createFolder('folder-a-1b', folder1.id);
|
||||||
|
const folder1a1 = await createFolder('folder-a-1a1', folder1a.id);
|
||||||
|
const file1 = await createAFile(null);
|
||||||
|
const file1b = await createAFile(folder1b.id);
|
||||||
|
const file1a = await createAFile(folder1a.id);
|
||||||
|
const file1a1 = await createAFile(folder1a1.id);
|
||||||
|
|
||||||
|
const res = await rq({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/upload/actions/bulk-delete',
|
||||||
|
body: {
|
||||||
|
fileIds: [file1.id],
|
||||||
|
folderIds: [folder1a.id],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.body.data).toMatchObject({
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
alternativeText: null,
|
||||||
|
caption: null,
|
||||||
|
createdAt: expect.anything(),
|
||||||
|
ext: '.jpg',
|
||||||
|
folderPath: '/',
|
||||||
|
formats: null,
|
||||||
|
hash: expect.anything(),
|
||||||
|
height: 20,
|
||||||
|
id: file1.id,
|
||||||
|
mime: 'image/jpeg',
|
||||||
|
name: 'rec.jpg',
|
||||||
|
previewUrl: null,
|
||||||
|
provider: 'local',
|
||||||
|
provider_metadata: null,
|
||||||
|
size: 0.27,
|
||||||
|
updatedAt: expect.anything(),
|
||||||
|
url: expect.anything(),
|
||||||
|
width: 20,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
folders: [
|
||||||
|
{
|
||||||
|
id: folder1a.id,
|
||||||
|
name: 'folder-a-1a',
|
||||||
|
path: expect.anything(),
|
||||||
|
uid: expect.anything(),
|
||||||
|
createdAt: expect.anything(),
|
||||||
|
updatedAt: expect.anything(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const resFolder = await rq({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/upload/folders?pagination[pageSize]=100',
|
||||||
|
});
|
||||||
|
|
||||||
|
const existingfoldersIds = resFolder.body.results.map(f => f.id);
|
||||||
|
expect(existingfoldersIds).toEqual(expect.not.arrayContaining([folder1a.id, folder1a1.id]));
|
||||||
|
expect(existingfoldersIds).toEqual(expect.arrayContaining([folder1.id, folder1b.id]));
|
||||||
|
|
||||||
|
const resFiles = await rq({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/upload/files',
|
||||||
|
qs: {
|
||||||
|
pageSize: 100,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const existingfilesIds = resFiles.body.results.map(f => f.id);
|
||||||
|
expect(existingfilesIds).toEqual(
|
||||||
|
expect.not.arrayContaining([file1.id, file1a.id, file1a1.id])
|
||||||
|
);
|
||||||
|
expect(existingfilesIds).toEqual(expect.arrayContaining([file1b.id]));
|
||||||
|
|
||||||
|
data.folders.push(folder1, folder1b);
|
||||||
|
data.files.push(file1b);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('move', () => {
|
||||||
|
test('Can move folders and files into another folder', async () => {
|
||||||
|
const folder1 = await createFolder('folder-b-1', null);
|
||||||
|
const folder1a = await createFolder('folder-b-1a', folder1.id);
|
||||||
|
const folder1b = await createFolder('folder-b-1b', folder1.id);
|
||||||
|
const folder1a1 = await createFolder('folder-b-1a1', folder1a.id);
|
||||||
|
const file1 = await createAFile(null);
|
||||||
|
const file1a = await createAFile(folder1a.id);
|
||||||
|
const file1b = await createAFile(folder1b.id);
|
||||||
|
const file1a1 = await createAFile(folder1a1.id);
|
||||||
|
|
||||||
|
const res = await rq({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/upload/actions/bulk-move',
|
||||||
|
body: {
|
||||||
|
destinationFolderId: folder1b.id,
|
||||||
|
fileIds: [file1a.id],
|
||||||
|
folderIds: [folder1a.id],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.body.data).toMatchObject({
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
alternativeText: null,
|
||||||
|
caption: null,
|
||||||
|
createdAt: expect.anything(),
|
||||||
|
ext: '.jpg',
|
||||||
|
folderPath: folder1b.path,
|
||||||
|
formats: null,
|
||||||
|
hash: expect.anything(),
|
||||||
|
height: 20,
|
||||||
|
id: file1a.id,
|
||||||
|
mime: 'image/jpeg',
|
||||||
|
name: 'rec.jpg',
|
||||||
|
previewUrl: null,
|
||||||
|
provider: 'local',
|
||||||
|
provider_metadata: null,
|
||||||
|
size: 0.27,
|
||||||
|
updatedAt: expect.anything(),
|
||||||
|
url: expect.anything(),
|
||||||
|
width: 20,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
folders: [
|
||||||
|
{
|
||||||
|
id: folder1a.id,
|
||||||
|
name: 'folder-b-1a',
|
||||||
|
path: `${folder1b.path}/${folder1a.uid}`,
|
||||||
|
uid: expect.anything(),
|
||||||
|
createdAt: expect.anything(),
|
||||||
|
updatedAt: expect.anything(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
body: { results: folderResults },
|
||||||
|
} = await rq({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/upload/folders?pagination[pageSize]=100&populate=parent',
|
||||||
|
qs: {
|
||||||
|
pagination: { pageSize: 100 },
|
||||||
|
populate: 'parent',
|
||||||
|
sort: 'id:asc',
|
||||||
|
filters: { id: { $in: [folder1.id, folder1a.id, folder1b.id, folder1a1.id] } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(folderResults[0]).toMatchObject({ ...folder1, parent: null });
|
||||||
|
expect(folderResults[1]).toMatchObject({
|
||||||
|
...folder1a,
|
||||||
|
path: `${folder1b.path}/${folder1a.uid}`,
|
||||||
|
parent: { id: folder1b.id },
|
||||||
|
updatedAt: expect.anything(),
|
||||||
|
});
|
||||||
|
expect(folderResults[2]).toMatchObject({ ...folder1b, parent: { id: folder1.id } });
|
||||||
|
expect(folderResults[3]).toMatchObject({
|
||||||
|
...folder1a1,
|
||||||
|
path: `${folder1b.path}/${folder1a.uid}/${folder1a1.uid}`,
|
||||||
|
parent: { id: folder1a.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
body: { results: fileResults },
|
||||||
|
} = await rq({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/upload/files',
|
||||||
|
qs: {
|
||||||
|
pageSize: 100,
|
||||||
|
populate: 'folder',
|
||||||
|
sort: 'id:asc',
|
||||||
|
filters: { id: { $in: [file1.id, file1a.id, file1b.id, file1a1.id] } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(fileResults[0]).toMatchObject({ ...file1, folder: null });
|
||||||
|
expect(fileResults[1]).toMatchObject({
|
||||||
|
...file1a,
|
||||||
|
folderPath: folder1b.path,
|
||||||
|
folder: { id: folder1b.id },
|
||||||
|
updatedAt: expect.anything(),
|
||||||
|
});
|
||||||
|
expect(fileResults[2]).toMatchObject({ ...file1b, folder: { id: folder1b.id } });
|
||||||
|
expect(fileResults[3]).toMatchObject({
|
||||||
|
...file1a1,
|
||||||
|
folderPath: `${folder1b.path}/${folder1a.uid}/${folder1a1.uid}`,
|
||||||
|
folder: { id: folder1a1.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
data.folders.push(...folderResults);
|
||||||
|
data.files.push(...fileResults);
|
||||||
|
});
|
||||||
|
test('Can move folders and files to the root level', async () => {
|
||||||
|
const folder1 = await createFolder('folder-c-1', null);
|
||||||
|
const folder1a = await createFolder('folder-c-1a', folder1.id);
|
||||||
|
const folder1a1 = await createFolder('folder-c-1a1', folder1a.id);
|
||||||
|
const file1 = await createAFile(null);
|
||||||
|
const file1a = await createAFile(folder1a.id);
|
||||||
|
const file1a1 = await createAFile(folder1a1.id);
|
||||||
|
|
||||||
|
const res = await rq({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/upload/actions/bulk-move',
|
||||||
|
body: {
|
||||||
|
destinationFolderId: null,
|
||||||
|
fileIds: [file1a.id],
|
||||||
|
folderIds: [folder1a.id],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.body.data).toMatchObject({
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
alternativeText: null,
|
||||||
|
caption: null,
|
||||||
|
createdAt: expect.anything(),
|
||||||
|
ext: '.jpg',
|
||||||
|
folderPath: '/',
|
||||||
|
formats: null,
|
||||||
|
hash: expect.anything(),
|
||||||
|
height: 20,
|
||||||
|
id: file1a.id,
|
||||||
|
mime: 'image/jpeg',
|
||||||
|
name: 'rec.jpg',
|
||||||
|
previewUrl: null,
|
||||||
|
provider: 'local',
|
||||||
|
provider_metadata: null,
|
||||||
|
size: 0.27,
|
||||||
|
updatedAt: expect.anything(),
|
||||||
|
url: expect.anything(),
|
||||||
|
width: 20,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
folders: [
|
||||||
|
{
|
||||||
|
id: folder1a.id,
|
||||||
|
name: 'folder-c-1a',
|
||||||
|
path: `/${folder1a.uid}`,
|
||||||
|
uid: expect.anything(),
|
||||||
|
createdAt: expect.anything(),
|
||||||
|
updatedAt: expect.anything(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
body: { results: folderResults },
|
||||||
|
} = await rq({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/upload/folders?pagination[pageSize]=100&populate=parent',
|
||||||
|
qs: {
|
||||||
|
pagination: { pageSize: 100 },
|
||||||
|
populate: 'parent',
|
||||||
|
sort: 'id:asc',
|
||||||
|
filters: { id: { $in: [folder1.id, folder1a.id, folder1a1.id] } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(folderResults[0]).toMatchObject({ ...folder1, parent: null });
|
||||||
|
expect(folderResults[1]).toMatchObject({
|
||||||
|
...folder1a,
|
||||||
|
path: `/${folder1a.uid}`,
|
||||||
|
parent: null,
|
||||||
|
updatedAt: expect.anything(),
|
||||||
|
});
|
||||||
|
expect(folderResults[2]).toMatchObject({
|
||||||
|
...folder1a1,
|
||||||
|
path: `/${folder1a.uid}/${folder1a1.uid}`,
|
||||||
|
parent: { id: folder1a.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
body: { results: fileResults },
|
||||||
|
} = await rq({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/upload/files',
|
||||||
|
qs: {
|
||||||
|
pageSize: 100,
|
||||||
|
populate: 'folder',
|
||||||
|
sort: 'id:asc',
|
||||||
|
filters: { id: { $in: [file1.id, file1a.id, file1a1.id] } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(fileResults[0]).toMatchObject({ ...file1, folder: null });
|
||||||
|
expect(fileResults[1]).toMatchObject({
|
||||||
|
...file1a,
|
||||||
|
folderPath: '/',
|
||||||
|
folder: null,
|
||||||
|
updatedAt: expect.anything(),
|
||||||
|
});
|
||||||
|
expect(fileResults[2]).toMatchObject({
|
||||||
|
...file1a1,
|
||||||
|
folderPath: `/${folder1a.uid}/${folder1a1.uid}`,
|
||||||
|
folder: { id: folder1a1.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
data.folders.push(...folderResults);
|
||||||
|
data.files.push(...fileResults);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Cannot move a folder inside itself (0 level)', async () => {
|
||||||
|
const folder1 = await createFolder('folder-d-1', null);
|
||||||
|
data.folders.push(folder1);
|
||||||
|
|
||||||
|
const res = await rq({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/upload/actions/bulk-move',
|
||||||
|
body: {
|
||||||
|
destinationFolderId: folder1.id,
|
||||||
|
folderIds: [folder1.id],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(res.status).toBe(400);
|
||||||
|
expect(res.body.error.message).toBe('folders cannot be moved inside themselves: folder-d-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Cannot move a folder inside itself (1 level)', async () => {
|
||||||
|
const folder1 = await createFolder('folder-e-1', null);
|
||||||
|
const folder1a = await createFolder('folder-e-1a', folder1.id);
|
||||||
|
data.folders.push(folder1, folder1a);
|
||||||
|
|
||||||
|
const res = await rq({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/upload/actions/bulk-move',
|
||||||
|
body: {
|
||||||
|
destinationFolderId: folder1a.id,
|
||||||
|
folderIds: [folder1.id],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(res.status).toBe(400);
|
||||||
|
expect(res.body.error.message).toBe('folders cannot be moved inside themselves: folder-e-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Cannot move a folder inside itself (2 levels)', async () => {
|
||||||
|
const folder1 = await createFolder('folder-f-1', null);
|
||||||
|
const folder1a = await createFolder('folder-f-1a', folder1.id);
|
||||||
|
const folder1a1 = await createFolder('folder-f-1a1', folder1a.id);
|
||||||
|
data.folders.push(folder1, folder1a, folder1a1);
|
||||||
|
|
||||||
|
const res = await rq({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/upload/actions/bulk-move',
|
||||||
|
body: {
|
||||||
|
destinationFolderId: folder1a1.id,
|
||||||
|
folderIds: [folder1.id],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(res.status).toBe(400);
|
||||||
|
expect(res.body.error.message).toBe('folders cannot be moved inside themselves: folder-f-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Cannot move a folder if it creates a duplicate', async () => {
|
||||||
|
const folder1 = await createFolder('folder-g-1', null);
|
||||||
|
const folder1a = await createFolder('folder-g-1a', folder1.id);
|
||||||
|
const folder2 = await createFolder('folder-g-1a', null);
|
||||||
|
data.folders.push(folder1, folder1a, folder2);
|
||||||
|
|
||||||
|
const res = await rq({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/upload/actions/bulk-move',
|
||||||
|
body: {
|
||||||
|
destinationFolderId: folder1.id,
|
||||||
|
folderIds: [folder2.id],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(res.status).toBe(400);
|
||||||
|
expect(res.body.error.message).toBe('some folders already exists: folder-g-1a');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -126,7 +126,7 @@ describe('Folder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(res.status).toBe(400);
|
expect(res.status).toBe(400);
|
||||||
expect(res.body.error.message).toBe('name already taken');
|
expect(res.body.error.message).toBe('folder already exists');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Cannot create a folder with duplicated name inside a folder', async () => {
|
test('Cannot create a folder with duplicated name inside a folder', async () => {
|
||||||
@ -140,7 +140,7 @@ describe('Folder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(res.status).toBe(400);
|
expect(res.status).toBe(400);
|
||||||
expect(res.body.error.message).toBe('name already taken');
|
expect(res.body.error.message).toBe('folder already exists');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Cannot create a folder inside a folder that does not exist', async () => {
|
test('Cannot create a folder inside a folder that does not exist', async () => {
|
||||||
@ -245,88 +245,6 @@ describe('Folder', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('delete', () => {
|
|
||||||
test('Can delete folders and belonging files', async () => {
|
|
||||||
const folder1 = await createFolder('folder1', null);
|
|
||||||
const folder1a = await createFolder('folder1a', folder1.id);
|
|
||||||
const folder1b = await createFolder('folder1b', folder1.id);
|
|
||||||
const folder1a1 = await createFolder('folder1a1', folder1a.id);
|
|
||||||
const file1 = await createAFile(null);
|
|
||||||
const file1b = await createAFile(folder1b.id);
|
|
||||||
const file1a = await createAFile(folder1a.id);
|
|
||||||
const file1a1 = await createAFile(folder1a1.id);
|
|
||||||
|
|
||||||
const res = await rq({
|
|
||||||
method: 'POST',
|
|
||||||
url: '/upload/actions/bulk-delete',
|
|
||||||
body: {
|
|
||||||
fileIds: [file1.id],
|
|
||||||
folderIds: [folder1a.id],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(res.body.data).toMatchObject({
|
|
||||||
files: [
|
|
||||||
{
|
|
||||||
alternativeText: null,
|
|
||||||
caption: null,
|
|
||||||
createdAt: expect.anything(),
|
|
||||||
ext: '.jpg',
|
|
||||||
folderPath: '/',
|
|
||||||
formats: null,
|
|
||||||
hash: expect.anything(),
|
|
||||||
height: 20,
|
|
||||||
id: file1.id,
|
|
||||||
mime: 'image/jpeg',
|
|
||||||
name: 'rec.jpg',
|
|
||||||
previewUrl: null,
|
|
||||||
provider: 'local',
|
|
||||||
provider_metadata: null,
|
|
||||||
size: 0.27,
|
|
||||||
updatedAt: expect.anything(),
|
|
||||||
url: expect.anything(),
|
|
||||||
width: 20,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
folders: [
|
|
||||||
{
|
|
||||||
id: folder1a.id,
|
|
||||||
name: 'folder1a',
|
|
||||||
path: expect.anything(),
|
|
||||||
uid: expect.anything(),
|
|
||||||
createdAt: expect.anything(),
|
|
||||||
updatedAt: expect.anything(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const resFolder = await rq({
|
|
||||||
method: 'GET',
|
|
||||||
url: '/upload/folders?pagination[pageSize]=100',
|
|
||||||
});
|
|
||||||
|
|
||||||
const existingfoldersIds = resFolder.body.results.map(f => f.id);
|
|
||||||
expect(existingfoldersIds).toEqual(expect.not.arrayContaining([folder1a.id, folder1a1.id]));
|
|
||||||
expect(existingfoldersIds).toEqual(expect.arrayContaining([folder1.id, folder1b.id]));
|
|
||||||
|
|
||||||
const resFiles = await rq({
|
|
||||||
method: 'GET',
|
|
||||||
url: '/upload/files',
|
|
||||||
qs: {
|
|
||||||
pageSize: 100,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const existingfilesIds = resFiles.body.results.map(f => f.id);
|
|
||||||
expect(existingfilesIds).toEqual(
|
|
||||||
expect.not.arrayContaining([file1.id, file1a.id, file1a1.id])
|
|
||||||
);
|
|
||||||
expect(existingfilesIds).toEqual(expect.arrayContaining([file1b.id]));
|
|
||||||
|
|
||||||
data.folders.push(folder1, folder1b);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('update', () => {
|
describe('update', () => {
|
||||||
test('rename a folder', async () => {
|
test('rename a folder', async () => {
|
||||||
const folder = await createFolder('folder-name', null);
|
const folder = await createFolder('folder-name', null);
|
||||||
@ -346,7 +264,7 @@ describe('Folder', () => {
|
|||||||
data.folders.push(res.body.data);
|
data.folders.push(res.body.data);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('cannot rename a folder if duplicated', async () => {
|
test('cannot move and rename a folder if duplicated', async () => {
|
||||||
const folder0 = await createFolder('folder-a-0', null);
|
const folder0 = await createFolder('folder-a-0', null);
|
||||||
const folder1 = await createFolder('folder-a-1', null);
|
const folder1 = await createFolder('folder-a-1', null);
|
||||||
const folder00 = await createFolder('folder-a-00', folder0.id);
|
const folder00 = await createFolder('folder-a-00', folder0.id);
|
||||||
@ -362,11 +280,29 @@ describe('Folder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(res.status).toBe(400);
|
expect(res.status).toBe(400);
|
||||||
expect(res.body.error.message).toBe('name already taken');
|
expect(res.body.error.message).toBe('folder already exists');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cannot move a folder if duplicated', async () => {
|
||||||
|
const folder0 = await createFolder('folder-b-0', null);
|
||||||
|
const folder1 = await createFolder('folder-b-samename', null);
|
||||||
|
await createFolder('folder-b-samename', folder0.id);
|
||||||
|
data.folders.push(folder0, folder1);
|
||||||
|
|
||||||
|
const res = await rq({
|
||||||
|
method: 'PUT',
|
||||||
|
url: `/upload/folders/${folder1.id}`,
|
||||||
|
body: {
|
||||||
|
parent: folder0.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.status).toBe(400);
|
||||||
|
expect(res.body.error.message).toBe('folder already exists');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('cannot move a folder to a folder that does not exist', async () => {
|
test('cannot move a folder to a folder that does not exist', async () => {
|
||||||
const folder = await createFolder('folder-b-0', null);
|
const folder = await createFolder('folder-c-0', null);
|
||||||
data.folders.push(folder);
|
data.folders.push(folder);
|
||||||
|
|
||||||
const res = await rq({
|
const res = await rq({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user