mirror of
https://github.com/strapi/strapi.git
synced 2025-12-12 15:32:42 +00:00
Merge pull request #13053 from strapi/ML-folder/update-file-location
Ml folder/update file location
This commit is contained in:
commit
def1cedc5b
@ -40,7 +40,8 @@
|
||||
"react-redux": "7.2.3",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "5.2.0",
|
||||
"sharp": "0.30.1"
|
||||
"sharp": "0.30.1",
|
||||
"uuid": "8.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
|
||||
14
packages/core/upload/server/constants.js
Normal file
14
packages/core/upload/server/constants.js
Normal file
@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
const ACTIONS = {
|
||||
read: 'plugin::upload.read',
|
||||
readSettings: 'plugin::upload.settings.read',
|
||||
create: 'plugin::upload.assets.create',
|
||||
update: 'plugin::upload.assets.update',
|
||||
download: 'plugin::upload.assets.download',
|
||||
copyLink: 'plugin::upload.assets.copy-link',
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
ACTIONS,
|
||||
};
|
||||
@ -89,5 +89,16 @@ module.exports = {
|
||||
relation: 'morphToMany',
|
||||
configurable: false,
|
||||
},
|
||||
folder: {
|
||||
type: 'relation',
|
||||
relation: 'manyToOne',
|
||||
target: 'plugin::upload.folder',
|
||||
inversedBy: 'files',
|
||||
},
|
||||
folderPath: {
|
||||
type: 'string',
|
||||
min: 1,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const schema = require('./schema');
|
||||
|
||||
module.exports = {
|
||||
schema,
|
||||
};
|
||||
54
packages/core/upload/server/content-types/folder/schema.js
Normal file
54
packages/core/upload/server/content-types/folder/schema.js
Normal file
@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
collectionName: 'folders',
|
||||
info: {
|
||||
singularName: 'folder',
|
||||
pluralName: 'folders',
|
||||
displayName: 'Folder',
|
||||
},
|
||||
options: {},
|
||||
pluginOptions: {
|
||||
'content-manager': {
|
||||
visible: false,
|
||||
},
|
||||
'content-type-builder': {
|
||||
visible: false,
|
||||
},
|
||||
},
|
||||
attributes: {
|
||||
name: {
|
||||
type: 'string',
|
||||
min: 1,
|
||||
required: true,
|
||||
},
|
||||
uid: {
|
||||
type: 'string',
|
||||
unique: true,
|
||||
required: true,
|
||||
},
|
||||
parent: {
|
||||
type: 'relation',
|
||||
relation: 'manyToOne',
|
||||
target: 'plugin::upload.folder',
|
||||
inversedBy: 'children',
|
||||
},
|
||||
children: {
|
||||
type: 'relation',
|
||||
relation: 'oneToMany',
|
||||
target: 'plugin::upload.folder',
|
||||
mappedBy: 'parent',
|
||||
},
|
||||
files: {
|
||||
type: 'relation',
|
||||
relation: 'oneToMany',
|
||||
target: 'plugin::upload.file',
|
||||
mappedBy: 'folder',
|
||||
},
|
||||
path: {
|
||||
type: 'string',
|
||||
min: 1,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -1,7 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const file = require('./file');
|
||||
const folder = require('./folder');
|
||||
|
||||
module.exports = {
|
||||
file,
|
||||
folder,
|
||||
};
|
||||
|
||||
@ -1,207 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const { contentTypes: contentTypesUtils } = require('@strapi/utils');
|
||||
const { ApplicationError, NotFoundError, ForbiddenError } = require('@strapi/utils').errors;
|
||||
const { getService } = require('../utils');
|
||||
const validateSettings = require('./validation/settings');
|
||||
const validateUploadBody = require('./validation/upload');
|
||||
|
||||
const { CREATED_BY_ATTRIBUTE } = contentTypesUtils.constants;
|
||||
|
||||
const ACTIONS = {
|
||||
read: 'plugin::upload.read',
|
||||
readSettings: 'plugin::upload.settings.read',
|
||||
create: 'plugin::upload.assets.create',
|
||||
update: 'plugin::upload.assets.update',
|
||||
download: 'plugin::upload.assets.download',
|
||||
copyLink: 'plugin::upload.assets.copy-link',
|
||||
};
|
||||
|
||||
const fileModel = 'plugin::upload.file';
|
||||
|
||||
module.exports = {
|
||||
async find(ctx) {
|
||||
const {
|
||||
state: { userAbility },
|
||||
} = ctx;
|
||||
|
||||
const pm = strapi.admin.services.permission.createPermissionsManager({
|
||||
ability: userAbility,
|
||||
action: ACTIONS.read,
|
||||
model: fileModel,
|
||||
});
|
||||
|
||||
if (!pm.isAllowed) {
|
||||
return ctx.forbidden();
|
||||
}
|
||||
|
||||
const query = pm.addPermissionsQueryTo(ctx.query);
|
||||
|
||||
const { results, pagination } = await getService('upload').findPage(query);
|
||||
|
||||
const sanitizedResults = await pm.sanitizeOutput(results);
|
||||
|
||||
return { results: sanitizedResults, pagination };
|
||||
},
|
||||
|
||||
async findOne(ctx) {
|
||||
const {
|
||||
state: { userAbility },
|
||||
params: { id },
|
||||
} = ctx;
|
||||
|
||||
const { pm, file } = await findEntityAndCheckPermissions(
|
||||
userAbility,
|
||||
ACTIONS.read,
|
||||
fileModel,
|
||||
id
|
||||
);
|
||||
|
||||
ctx.body = await pm.sanitizeOutput(file);
|
||||
},
|
||||
|
||||
async destroy(ctx) {
|
||||
const { id } = ctx.params;
|
||||
const { userAbility } = ctx.state;
|
||||
|
||||
const { pm, file } = await findEntityAndCheckPermissions(
|
||||
userAbility,
|
||||
ACTIONS.update,
|
||||
fileModel,
|
||||
id
|
||||
);
|
||||
|
||||
await getService('upload').remove(file);
|
||||
|
||||
ctx.body = await pm.sanitizeOutput(file, { action: ACTIONS.read });
|
||||
},
|
||||
|
||||
async updateSettings(ctx) {
|
||||
const {
|
||||
request: { body },
|
||||
state: { userAbility },
|
||||
} = ctx;
|
||||
|
||||
if (userAbility.cannot(ACTIONS.readSettings, fileModel)) {
|
||||
return ctx.forbidden();
|
||||
}
|
||||
|
||||
const data = await validateSettings(body);
|
||||
|
||||
await getService('upload').setSettings(data);
|
||||
|
||||
ctx.body = { data };
|
||||
},
|
||||
|
||||
async getSettings(ctx) {
|
||||
const {
|
||||
state: { userAbility },
|
||||
} = ctx;
|
||||
|
||||
if (userAbility.cannot(ACTIONS.readSettings, fileModel)) {
|
||||
return ctx.forbidden();
|
||||
}
|
||||
|
||||
const data = await getService('upload').getSettings();
|
||||
|
||||
ctx.body = { data };
|
||||
},
|
||||
|
||||
async updateFileInfo(ctx) {
|
||||
const {
|
||||
state: { userAbility, user },
|
||||
query: { id },
|
||||
request: { body },
|
||||
} = ctx;
|
||||
|
||||
const uploadService = getService('upload');
|
||||
const { pm } = await findEntityAndCheckPermissions(userAbility, ACTIONS.update, fileModel, id);
|
||||
|
||||
const data = await validateUploadBody(body);
|
||||
const file = await uploadService.updateFileInfo(id, data.fileInfo, { user });
|
||||
|
||||
ctx.body = await pm.sanitizeOutput(file, { action: ACTIONS.read });
|
||||
},
|
||||
|
||||
async replaceFile(ctx) {
|
||||
const {
|
||||
state: { userAbility, user },
|
||||
query: { id },
|
||||
request: { body, files: { files } = {} },
|
||||
} = ctx;
|
||||
|
||||
const uploadService = getService('upload');
|
||||
const { pm } = await findEntityAndCheckPermissions(userAbility, ACTIONS.update, fileModel, id);
|
||||
|
||||
if (Array.isArray(files)) {
|
||||
throw new ApplicationError('Cannot replace a file with multiple ones');
|
||||
}
|
||||
|
||||
const data = await validateUploadBody(body);
|
||||
const replacedFiles = await uploadService.replace(id, { data, file: files }, { user });
|
||||
|
||||
ctx.body = await pm.sanitizeOutput(replacedFiles, { action: ACTIONS.read });
|
||||
},
|
||||
|
||||
async uploadFiles(ctx) {
|
||||
const {
|
||||
state: { userAbility, user },
|
||||
request: { body, files: { files } = {} },
|
||||
} = ctx;
|
||||
|
||||
const uploadService = getService('upload');
|
||||
const pm = strapi.admin.services.permission.createPermissionsManager({
|
||||
ability: userAbility,
|
||||
action: ACTIONS.create,
|
||||
model: fileModel,
|
||||
});
|
||||
|
||||
if (!pm.isAllowed) {
|
||||
return ctx.forbidden();
|
||||
}
|
||||
|
||||
const data = await validateUploadBody(body);
|
||||
const uploadedFiles = await uploadService.upload({ data, files }, { user });
|
||||
|
||||
ctx.body = await pm.sanitizeOutput(uploadedFiles, { action: ACTIONS.read });
|
||||
},
|
||||
|
||||
async upload(ctx) {
|
||||
const {
|
||||
query: { id },
|
||||
request: { files: { files } = {} },
|
||||
} = ctx;
|
||||
|
||||
if (id && (_.isEmpty(files) || files.size === 0)) {
|
||||
return this.updateFileInfo(ctx);
|
||||
}
|
||||
|
||||
if (_.isEmpty(files) || files.size === 0) {
|
||||
throw new ApplicationError('Files are empty');
|
||||
}
|
||||
|
||||
await (id ? this.replaceFile : this.uploadFiles)(ctx);
|
||||
},
|
||||
};
|
||||
|
||||
const findEntityAndCheckPermissions = async (ability, action, model, id) => {
|
||||
const file = await getService('upload').findOne(id, [CREATED_BY_ATTRIBUTE]);
|
||||
|
||||
if (_.isNil(file)) {
|
||||
throw new NotFoundError();
|
||||
}
|
||||
|
||||
const pm = strapi.admin.services.permission.createPermissionsManager({ ability, action, model });
|
||||
|
||||
const creatorId = _.get(file, [CREATED_BY_ATTRIBUTE, 'id']);
|
||||
const author = creatorId ? await strapi.admin.services.user.findOne(creatorId, ['roles']) : null;
|
||||
|
||||
const fileWithRoles = _.set(_.cloneDeep(file), 'createdBy', author);
|
||||
|
||||
if (pm.ability.cannot(pm.action, pm.toSubject(fileWithRoles))) {
|
||||
throw new ForbiddenError();
|
||||
}
|
||||
|
||||
return { pm, file };
|
||||
};
|
||||
68
packages/core/upload/server/controllers/admin-file.js
Normal file
68
packages/core/upload/server/controllers/admin-file.js
Normal file
@ -0,0 +1,68 @@
|
||||
'use strict';
|
||||
|
||||
const { getService } = require('../utils');
|
||||
const { ACTIONS } = require('../constants');
|
||||
const { findEntityAndCheckPermissions } = require('./utils/find-entity-and-check-permissions');
|
||||
|
||||
const fileModel = 'plugin::upload.file';
|
||||
|
||||
module.exports = {
|
||||
async find(ctx) {
|
||||
const {
|
||||
state: { userAbility },
|
||||
} = ctx;
|
||||
|
||||
const pm = strapi.admin.services.permission.createPermissionsManager({
|
||||
ability: userAbility,
|
||||
action: ACTIONS.read,
|
||||
model: fileModel,
|
||||
});
|
||||
|
||||
if (!pm.isAllowed) {
|
||||
return ctx.forbidden();
|
||||
}
|
||||
|
||||
const query = pm.addPermissionsQueryTo(ctx.query);
|
||||
|
||||
const { results, pagination } = await getService('upload').findPage(query);
|
||||
|
||||
const sanitizedResults = await pm.sanitizeOutput(results);
|
||||
|
||||
return { results: sanitizedResults, pagination };
|
||||
},
|
||||
|
||||
async findOne(ctx) {
|
||||
const {
|
||||
state: { userAbility },
|
||||
params: { id },
|
||||
} = ctx;
|
||||
|
||||
const { pm, file } = await findEntityAndCheckPermissions(
|
||||
userAbility,
|
||||
ACTIONS.read,
|
||||
fileModel,
|
||||
id
|
||||
);
|
||||
|
||||
ctx.body = await pm.sanitizeOutput(file);
|
||||
},
|
||||
|
||||
async destroy(ctx) {
|
||||
const { id } = ctx.params;
|
||||
const { userAbility } = ctx.state;
|
||||
|
||||
const { pm, file } = await findEntityAndCheckPermissions(
|
||||
userAbility,
|
||||
ACTIONS.update,
|
||||
fileModel,
|
||||
id
|
||||
);
|
||||
|
||||
const [body] = await Promise.all([
|
||||
pm.sanitizeOutput(file, { action: ACTIONS.read }),
|
||||
getService('upload').remove(file),
|
||||
]);
|
||||
|
||||
ctx.body = body;
|
||||
},
|
||||
};
|
||||
76
packages/core/upload/server/controllers/admin-folder.js
Normal file
76
packages/core/upload/server/controllers/admin-folder.js
Normal file
@ -0,0 +1,76 @@
|
||||
'use strict';
|
||||
|
||||
const { setCreatorFields, pipeAsync } = require('@strapi/utils');
|
||||
const { getService } = require('../utils');
|
||||
const { validateCreateFolder, validateDeleteManyFolders } = require('./validation/admin/folder');
|
||||
|
||||
const folderModel = 'plugin::upload.folder';
|
||||
|
||||
module.exports = {
|
||||
async find(ctx) {
|
||||
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
|
||||
ability: ctx.state.userAbility,
|
||||
model: folderModel,
|
||||
});
|
||||
|
||||
const { results, pagination } = await strapi.entityService.findWithRelationCounts(folderModel, {
|
||||
...ctx.query,
|
||||
populate: {
|
||||
children: {
|
||||
count: true,
|
||||
},
|
||||
files: {
|
||||
count: true,
|
||||
},
|
||||
parent: true,
|
||||
createdBy: true,
|
||||
updatedBy: true,
|
||||
},
|
||||
});
|
||||
|
||||
ctx.body = {
|
||||
results: await permissionsManager.sanitizeOutput(results),
|
||||
pagination,
|
||||
};
|
||||
},
|
||||
async create(ctx) {
|
||||
const { user } = ctx.state;
|
||||
const { body, query } = ctx.request;
|
||||
|
||||
await validateCreateFolder(body);
|
||||
|
||||
const { setPathAndUID } = getService('folder');
|
||||
|
||||
// TODO: wrap with a transaction
|
||||
const enrichFolder = pipeAsync(setPathAndUID, setCreatorFields({ user }));
|
||||
const enrichedFolder = await enrichFolder(body);
|
||||
|
||||
const folder = await strapi.entityService.create(folderModel, {
|
||||
...query,
|
||||
data: enrichedFolder,
|
||||
});
|
||||
|
||||
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
|
||||
ability: ctx.state.userAbility,
|
||||
model: folderModel,
|
||||
});
|
||||
|
||||
ctx.body = {
|
||||
data: await permissionsManager.sanitizeOutput(folder),
|
||||
};
|
||||
},
|
||||
// deleteMany WIP
|
||||
async deleteMany(ctx) {
|
||||
const { body } = ctx.request;
|
||||
|
||||
await validateDeleteManyFolders(body);
|
||||
|
||||
const { deleteByIds } = getService('folder');
|
||||
|
||||
const deletedFolders = await deleteByIds(body.ids);
|
||||
|
||||
ctx.body = {
|
||||
data: deletedFolders,
|
||||
};
|
||||
},
|
||||
};
|
||||
40
packages/core/upload/server/controllers/admin-settings.js
Normal file
40
packages/core/upload/server/controllers/admin-settings.js
Normal file
@ -0,0 +1,40 @@
|
||||
'use strict';
|
||||
|
||||
const { getService } = require('../utils');
|
||||
const { ACTIONS } = require('../constants');
|
||||
const validateSettings = require('./validation/admin/settings');
|
||||
|
||||
const fileModel = 'plugin::upload.file';
|
||||
|
||||
module.exports = {
|
||||
async updateSettings(ctx) {
|
||||
const {
|
||||
request: { body },
|
||||
state: { userAbility },
|
||||
} = ctx;
|
||||
|
||||
if (userAbility.cannot(ACTIONS.readSettings, fileModel)) {
|
||||
return ctx.forbidden();
|
||||
}
|
||||
|
||||
const data = await validateSettings(body);
|
||||
|
||||
await getService('upload').setSettings(data);
|
||||
|
||||
ctx.body = { data };
|
||||
},
|
||||
|
||||
async getSettings(ctx) {
|
||||
const {
|
||||
state: { userAbility },
|
||||
} = ctx;
|
||||
|
||||
if (userAbility.cannot(ACTIONS.readSettings, fileModel)) {
|
||||
return ctx.forbidden();
|
||||
}
|
||||
|
||||
const data = await getService('upload').getSettings();
|
||||
|
||||
ctx.body = { data };
|
||||
},
|
||||
};
|
||||
88
packages/core/upload/server/controllers/admin-upload.js
Normal file
88
packages/core/upload/server/controllers/admin-upload.js
Normal file
@ -0,0 +1,88 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const { ApplicationError } = require('@strapi/utils').errors;
|
||||
const { getService } = require('../utils');
|
||||
const { ACTIONS } = require('../constants');
|
||||
const validateUploadBody = require('./validation/admin/upload');
|
||||
const { findEntityAndCheckPermissions } = require('./utils/find-entity-and-check-permissions');
|
||||
|
||||
const fileModel = 'plugin::upload.file';
|
||||
|
||||
module.exports = {
|
||||
async updateFileInfo(ctx) {
|
||||
const {
|
||||
state: { userAbility, user },
|
||||
query: { id },
|
||||
request: { body },
|
||||
} = ctx;
|
||||
|
||||
const uploadService = getService('upload');
|
||||
const { pm } = await findEntityAndCheckPermissions(userAbility, ACTIONS.update, fileModel, id);
|
||||
|
||||
const data = await validateUploadBody(body);
|
||||
const file = await uploadService.updateFileInfo(id, data.fileInfo, { user });
|
||||
|
||||
ctx.body = await pm.sanitizeOutput(file, { action: ACTIONS.read });
|
||||
},
|
||||
|
||||
async replaceFile(ctx) {
|
||||
const {
|
||||
state: { userAbility, user },
|
||||
query: { id },
|
||||
request: { body, files: { files } = {} },
|
||||
} = ctx;
|
||||
|
||||
const uploadService = getService('upload');
|
||||
const { pm } = await findEntityAndCheckPermissions(userAbility, ACTIONS.update, fileModel, id);
|
||||
|
||||
if (Array.isArray(files)) {
|
||||
throw new ApplicationError('Cannot replace a file with multiple ones');
|
||||
}
|
||||
|
||||
const data = await validateUploadBody(body);
|
||||
const replacedFiles = await uploadService.replace(id, { data, file: files }, { user });
|
||||
|
||||
ctx.body = await pm.sanitizeOutput(replacedFiles, { action: ACTIONS.read });
|
||||
},
|
||||
|
||||
async uploadFiles(ctx) {
|
||||
const {
|
||||
state: { userAbility, user },
|
||||
request: { body, files: { files } = {} },
|
||||
} = ctx;
|
||||
|
||||
const uploadService = getService('upload');
|
||||
const pm = strapi.admin.services.permission.createPermissionsManager({
|
||||
ability: userAbility,
|
||||
action: ACTIONS.create,
|
||||
model: fileModel,
|
||||
});
|
||||
|
||||
if (!pm.isAllowed) {
|
||||
return ctx.forbidden();
|
||||
}
|
||||
|
||||
const data = await validateUploadBody(body);
|
||||
const uploadedFiles = await uploadService.upload({ data, files }, { user });
|
||||
|
||||
ctx.body = await pm.sanitizeOutput(uploadedFiles, { action: ACTIONS.read });
|
||||
},
|
||||
|
||||
async upload(ctx) {
|
||||
const {
|
||||
query: { id },
|
||||
request: { files: { files } = {} },
|
||||
} = ctx;
|
||||
|
||||
if (_.isEmpty(files) || files.size === 0) {
|
||||
if (id) {
|
||||
return this.updateFileInfo(ctx);
|
||||
}
|
||||
|
||||
throw new ApplicationError('Files are empty');
|
||||
}
|
||||
|
||||
await (id ? this.replaceFile : this.uploadFiles)(ctx);
|
||||
},
|
||||
};
|
||||
@ -1,19 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const { omit, isArray, isPlainObject } = require('lodash/fp');
|
||||
const utils = require('@strapi/utils');
|
||||
const { getService } = require('../utils');
|
||||
const validateSettings = require('./validation/settings');
|
||||
const validateUploadBody = require('./validation/upload');
|
||||
const validateUploadBody = require('./validation/content-api/upload');
|
||||
|
||||
const { sanitize } = utils;
|
||||
const { ValidationError } = utils.errors;
|
||||
|
||||
const removeFolderPath = data => {
|
||||
if (isArray(data)) return data.map(omit('folderPath'));
|
||||
if (isPlainObject(data)) return omit('folderPath', data);
|
||||
return data;
|
||||
};
|
||||
|
||||
const sanitizeOutput = (data, ctx) => {
|
||||
const schema = strapi.getModel('plugin::upload.file');
|
||||
const { auth } = ctx.state;
|
||||
|
||||
return sanitize.contentAPI.output(data, schema, { auth });
|
||||
return sanitize.contentAPI.output(removeFolderPath(data), schema, { auth });
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
@ -57,24 +63,6 @@ module.exports = {
|
||||
ctx.body = await sanitizeOutput(file, ctx);
|
||||
},
|
||||
|
||||
async updateSettings(ctx) {
|
||||
const {
|
||||
request: { body },
|
||||
} = ctx;
|
||||
|
||||
const data = await validateSettings(body);
|
||||
|
||||
await getService('upload').setSettings(data);
|
||||
|
||||
ctx.body = { data };
|
||||
},
|
||||
|
||||
async getSettings(ctx) {
|
||||
const data = await getService('upload').getSettings();
|
||||
|
||||
ctx.body = { data };
|
||||
},
|
||||
|
||||
async updateFileInfo(ctx) {
|
||||
const {
|
||||
query: { id },
|
||||
@ -135,15 +123,4 @@ module.exports = {
|
||||
|
||||
await (id ? this.replaceFile : this.uploadFiles)(ctx);
|
||||
},
|
||||
|
||||
async search(ctx) {
|
||||
const { id } = ctx.params;
|
||||
const entries = await strapi.query('plugin::upload.file').findMany({
|
||||
where: {
|
||||
$or: [{ hash: { $contains: id } }, { name: { $contains: id } }],
|
||||
},
|
||||
});
|
||||
|
||||
ctx.body = await sanitizeOutput(entries, ctx);
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
const adminApi = require('./admin-api');
|
||||
const adminFile = require('./admin-file');
|
||||
const adminFolder = require('./admin-folder');
|
||||
const adminSettings = require('./admin-settings');
|
||||
const adminUpload = require('./admin-upload');
|
||||
const contentApi = require('./content-api');
|
||||
|
||||
module.exports = {
|
||||
'admin-api': adminApi,
|
||||
'admin-file': adminFile,
|
||||
'admin-folder': adminFolder,
|
||||
'admin-settings': adminSettings,
|
||||
'admin-upload': adminUpload,
|
||||
'content-api': contentApi,
|
||||
};
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const { CREATED_BY_ATTRIBUTE } = require('@strapi/utils').contentTypes.constants;
|
||||
const { NotFoundError, ForbiddenError } = require('@strapi/utils').errors;
|
||||
const { getService } = require('../../utils');
|
||||
|
||||
const findEntityAndCheckPermissions = async (ability, action, model, id) => {
|
||||
const file = await getService('upload').findOne(id, [CREATED_BY_ATTRIBUTE, 'folder']);
|
||||
|
||||
if (_.isNil(file)) {
|
||||
throw new NotFoundError();
|
||||
}
|
||||
|
||||
const pm = strapi.admin.services.permission.createPermissionsManager({ ability, action, model });
|
||||
|
||||
const creatorId = _.get(file, [CREATED_BY_ATTRIBUTE, 'id']);
|
||||
const author = creatorId ? await strapi.admin.services.user.findOne(creatorId, ['roles']) : null;
|
||||
|
||||
const fileWithRoles = _.set(_.cloneDeep(file), 'createdBy', author);
|
||||
|
||||
if (pm.ability.cannot(pm.action, pm.toSubject(fileWithRoles))) {
|
||||
throw new ForbiddenError();
|
||||
}
|
||||
|
||||
return { pm, file };
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
findEntityAndCheckPermissions,
|
||||
};
|
||||
@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
const { yup, validateYupSchema } = require('@strapi/utils');
|
||||
const { getService } = require('../../../utils');
|
||||
|
||||
const NO_SLASH_REGEX = /^[^/]+$/;
|
||||
const NO_SPACES_AROUND = /^(?! ).+(?<! )$/;
|
||||
|
||||
const validateCreateFolderSchema = yup
|
||||
.object()
|
||||
.shape({
|
||||
name: yup
|
||||
.string()
|
||||
.min(1)
|
||||
.matches(NO_SLASH_REGEX, 'name cannot contain slashes')
|
||||
.matches(NO_SPACES_AROUND, 'name cannot start or end with a whitespace')
|
||||
.required(),
|
||||
parent: yup.strapiID().nullable(),
|
||||
})
|
||||
.test('is-folder-unique', 'name already taken', async folder => {
|
||||
const { exists } = getService('folder');
|
||||
const doesExist = await exists({ parent: folder.parent || null, name: folder.name });
|
||||
return !doesExist;
|
||||
})
|
||||
.noUnknown()
|
||||
.required();
|
||||
|
||||
const validateDeleteManyFoldersSchema = yup
|
||||
.object()
|
||||
.shape({
|
||||
ids: yup
|
||||
.array()
|
||||
.min(1)
|
||||
.of(yup.strapiID().required())
|
||||
.required(),
|
||||
})
|
||||
.noUnknown()
|
||||
.required();
|
||||
|
||||
module.exports = {
|
||||
validateCreateFolder: validateYupSchema(validateCreateFolderSchema),
|
||||
validateDeleteManyFolders: validateYupSchema(validateDeleteManyFoldersSchema),
|
||||
};
|
||||
@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
const { yup, validateYupSchema } = require('@strapi/utils');
|
||||
const { isNil } = require('lodash/fp');
|
||||
const { getService } = require('../../../utils');
|
||||
|
||||
const fileInfoSchema = yup.object({
|
||||
name: yup.string().nullable(),
|
||||
alternativeText: yup.string().nullable(),
|
||||
caption: yup.string().nullable(),
|
||||
folder: yup
|
||||
.strapiID()
|
||||
.nullable()
|
||||
.test('folder-exists', 'the folder does not exist', async folderId => {
|
||||
if (isNil(folderId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return getService('folder').exists({ id: folderId });
|
||||
}),
|
||||
});
|
||||
|
||||
const uploadSchema = yup.object({
|
||||
fileInfo: fileInfoSchema,
|
||||
});
|
||||
|
||||
const multiUploadSchema = yup.object({
|
||||
fileInfo: yup.array().of(fileInfoSchema),
|
||||
});
|
||||
|
||||
const validateUploadBody = (data = {}, isMulti = false) => {
|
||||
const schema = isMulti ? multiUploadSchema : uploadSchema;
|
||||
|
||||
return validateYupSchema(schema, { strict: false })(data);
|
||||
};
|
||||
|
||||
module.exports = validateUploadBody;
|
||||
@ -2,11 +2,13 @@
|
||||
|
||||
const { yup, validateYupSchema } = require('@strapi/utils');
|
||||
|
||||
const fileInfoSchema = yup.object({
|
||||
name: yup.string().nullable(),
|
||||
alternativeText: yup.string().nullable(),
|
||||
caption: yup.string().nullable(),
|
||||
});
|
||||
const fileInfoSchema = yup
|
||||
.object({
|
||||
name: yup.string().nullable(),
|
||||
alternativeText: yup.string().nullable(),
|
||||
caption: yup.string().nullable(),
|
||||
})
|
||||
.noUnknown();
|
||||
|
||||
const uploadSchema = yup.object({
|
||||
fileInfo: fileInfoSchema,
|
||||
@ -45,7 +45,7 @@ module.exports = ({ strapi }) => {
|
||||
const formatFile = async (upload, extraInfo, metas) => {
|
||||
const uploadService = getUploadService('upload');
|
||||
const { filename, mimetype, createReadStream } = await upload;
|
||||
const currentFile = uploadService.formatFileInfo(
|
||||
const currentFile = await uploadService.formatFileInfo(
|
||||
{
|
||||
filename,
|
||||
type: mimetype,
|
||||
|
||||
@ -6,7 +6,7 @@ module.exports = {
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/settings',
|
||||
handler: 'admin-api.getSettings',
|
||||
handler: 'admin-settings.getSettings',
|
||||
config: {
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
@ -22,7 +22,7 @@ module.exports = {
|
||||
{
|
||||
method: 'PUT',
|
||||
path: '/settings',
|
||||
handler: 'admin-api.updateSettings',
|
||||
handler: 'admin-settings.updateSettings',
|
||||
config: {
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
@ -38,7 +38,7 @@ module.exports = {
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/',
|
||||
handler: 'admin-api.upload',
|
||||
handler: 'admin-upload.upload',
|
||||
config: {
|
||||
policies: ['admin::isAuthenticatedAdmin'],
|
||||
},
|
||||
@ -46,7 +46,7 @@ module.exports = {
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/files',
|
||||
handler: 'admin-api.find',
|
||||
handler: 'admin-file.find',
|
||||
config: {
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
@ -62,7 +62,7 @@ module.exports = {
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/files/:id',
|
||||
handler: 'admin-api.findOne',
|
||||
handler: 'admin-file.findOne',
|
||||
config: {
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
@ -78,7 +78,7 @@ module.exports = {
|
||||
{
|
||||
method: 'DELETE',
|
||||
path: '/files/:id',
|
||||
handler: 'admin-api.destroy',
|
||||
handler: 'admin-file.destroy',
|
||||
config: {
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
@ -91,5 +91,53 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/folders',
|
||||
handler: 'admin-folder.find',
|
||||
config: {
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
{
|
||||
name: 'admin::hasPermissions',
|
||||
config: {
|
||||
actions: ['plugin::upload.read'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/folders',
|
||||
handler: 'admin-folder.create',
|
||||
config: {
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
{
|
||||
name: 'admin::hasPermissions',
|
||||
config: {
|
||||
actions: ['plugin::upload.read'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/folders/batch-delete',
|
||||
handler: 'admin-folder.deleteMany',
|
||||
config: {
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
{
|
||||
name: 'admin::hasPermissions',
|
||||
config: {
|
||||
actions: ['plugin::upload.read'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
27
packages/core/upload/server/services/__tests__/file.test.js
Normal file
27
packages/core/upload/server/services/__tests__/file.test.js
Normal file
@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
const { getFolderPath } = require('../file');
|
||||
|
||||
const folderPath = '/9bc2352b-e29b-4ba3-810f-7b91033222de';
|
||||
|
||||
describe('file', () => {
|
||||
describe('getFolderPath', () => {
|
||||
beforeAll(() => {
|
||||
global.strapi = {
|
||||
entityService: {
|
||||
findOne: jest.fn(() => ({ path: folderPath })),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
test.each([
|
||||
[[1, 'myFile.txt'], folderPath],
|
||||
[[undefined, 'myFile.txt'], '/'],
|
||||
[[null, 'myFile.txt'], '/'],
|
||||
])('inputs %s should give %s', async (args, expectedResult) => {
|
||||
const result = await getFolderPath(...args);
|
||||
|
||||
expect(result).toBe(expectedResult);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
const { setPathAndUID } = require('../folder');
|
||||
|
||||
const folderUID = '9bc2352b-e29b-4ba3-810f-7b91033222de';
|
||||
const uuidRegex = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;
|
||||
const rootPathRegex = /^\/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;
|
||||
const folderPathRegex = new RegExp(
|
||||
'^/' + folderUID + '/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$',
|
||||
'i'
|
||||
);
|
||||
|
||||
describe('folder', () => {
|
||||
describe('setPathAndUID', () => {
|
||||
beforeAll(() => {
|
||||
global.strapi = {
|
||||
entityService: {
|
||||
findOne: jest.fn(() => ({ path: `/${folderUID}` })),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
test.each([
|
||||
[{ parent: 1 }, folderPathRegex],
|
||||
[{}, rootPathRegex],
|
||||
[{ parent: null }, rootPathRegex],
|
||||
])('inputs %s', async (folder, expectedPath) => {
|
||||
const clonedFolder = { ...folder };
|
||||
const result = await setPathAndUID(clonedFolder);
|
||||
|
||||
expect(result).toBe(clonedFolder);
|
||||
expect(result).toMatchObject({
|
||||
...folder,
|
||||
uid: expect.stringMatching(uuidRegex),
|
||||
path: expect.stringMatching(expectedPath),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -3,15 +3,29 @@
|
||||
const uploadService = require('../upload')({});
|
||||
|
||||
describe('Upload service', () => {
|
||||
beforeAll(() => {
|
||||
global.strapi = {
|
||||
plugins: {
|
||||
upload: {
|
||||
services: {
|
||||
file: {
|
||||
getFolderPath: () => '/a-folder-path',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('formatFileInfo', () => {
|
||||
test('Generates hash', () => {
|
||||
test('Generates hash', async () => {
|
||||
const fileData = {
|
||||
filename: 'File Name.png',
|
||||
type: 'image/png',
|
||||
size: 1000 * 1000,
|
||||
};
|
||||
|
||||
expect(uploadService.formatFileInfo(fileData)).toMatchObject({
|
||||
expect(await uploadService.formatFileInfo(fileData)).toMatchObject({
|
||||
name: 'File Name.png',
|
||||
hash: expect.stringContaining('File_Name'),
|
||||
ext: '.png',
|
||||
@ -20,14 +34,14 @@ describe('Upload service', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('Replaces reserved and unsafe characters for URLs and files in hash', () => {
|
||||
test('Replaces reserved and unsafe characters for URLs and files in hash', async () => {
|
||||
const fileData = {
|
||||
filename: 'File%&Näme<>:"|?*.png',
|
||||
type: 'image/png',
|
||||
size: 1000 * 1000,
|
||||
};
|
||||
|
||||
expect(uploadService.formatFileInfo(fileData)).toMatchObject({
|
||||
expect(await uploadService.formatFileInfo(fileData)).toMatchObject({
|
||||
name: 'File%&Näme<>:"|?*.png',
|
||||
hash: expect.stringContaining('File_and_Naeme'),
|
||||
ext: '.png',
|
||||
@ -36,7 +50,7 @@ describe('Upload service', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('Overrides name with fileInfo', () => {
|
||||
test('Overrides name with fileInfo', async () => {
|
||||
const fileData = {
|
||||
filename: 'File Name.png',
|
||||
type: 'image/png',
|
||||
@ -47,7 +61,7 @@ describe('Upload service', () => {
|
||||
name: 'Custom File Name.png',
|
||||
};
|
||||
|
||||
expect(uploadService.formatFileInfo(fileData, fileInfo)).toMatchObject({
|
||||
expect(await uploadService.formatFileInfo(fileData, fileInfo)).toMatchObject({
|
||||
name: fileInfo.name,
|
||||
hash: expect.stringContaining('Custom_File_Name'),
|
||||
ext: '.png',
|
||||
@ -56,7 +70,7 @@ describe('Upload service', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('Sets alternativeText and caption', () => {
|
||||
test('Sets alternativeText and caption', async () => {
|
||||
const fileData = {
|
||||
filename: 'File Name.png',
|
||||
type: 'image/png',
|
||||
@ -68,7 +82,7 @@ describe('Upload service', () => {
|
||||
caption: 'caption this',
|
||||
};
|
||||
|
||||
expect(uploadService.formatFileInfo(fileData, fileInfo)).toMatchObject({
|
||||
expect(await uploadService.formatFileInfo(fileData, fileInfo)).toMatchObject({
|
||||
name: 'File Name.png',
|
||||
caption: fileInfo.caption,
|
||||
alternativeText: fileInfo.alternativeText,
|
||||
@ -79,7 +93,7 @@ describe('Upload service', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('Set a path folder', () => {
|
||||
test('Set a path folder', async () => {
|
||||
const fileData = {
|
||||
filename: 'File Name.png',
|
||||
type: 'image/png',
|
||||
@ -90,7 +104,7 @@ describe('Upload service', () => {
|
||||
path: 'folder',
|
||||
};
|
||||
|
||||
expect(uploadService.formatFileInfo(fileData, {}, fileMetas)).toMatchObject({
|
||||
expect(await uploadService.formatFileInfo(fileData, {}, fileMetas)).toMatchObject({
|
||||
name: 'File Name.png',
|
||||
ext: '.png',
|
||||
mime: 'image/png',
|
||||
|
||||
15
packages/core/upload/server/services/file.js
Normal file
15
packages/core/upload/server/services/file.js
Normal file
@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
const folderModel = 'plugin::upload.folder';
|
||||
|
||||
const getFolderPath = async folderId => {
|
||||
if (!folderId) return '/';
|
||||
|
||||
const parentFolder = await strapi.entityService.findOne(folderModel, folderId);
|
||||
|
||||
return parentFolder.path;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getFolderPath,
|
||||
};
|
||||
49
packages/core/upload/server/services/folder.js
Normal file
49
packages/core/upload/server/services/folder.js
Normal file
@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
|
||||
const uuid = require('uuid').v4;
|
||||
const { joinBy } = require('@strapi/utils');
|
||||
|
||||
const folderModel = 'plugin::upload.folder';
|
||||
|
||||
const generateUID = () => uuid();
|
||||
|
||||
const setPathAndUID = async folder => {
|
||||
const uid = generateUID();
|
||||
let parentPath = '/';
|
||||
if (folder.parent) {
|
||||
const parentFolder = await strapi.entityService.findOne(folderModel, folder.parent);
|
||||
parentPath = parentFolder.path;
|
||||
}
|
||||
|
||||
return Object.assign(folder, {
|
||||
uid,
|
||||
path: joinBy('/', parentPath, uid),
|
||||
});
|
||||
};
|
||||
|
||||
const deleteByIds = async ids => {
|
||||
const deletedFolders = [];
|
||||
for (const id of ids) {
|
||||
const deletedFolder = await strapi.entityService.delete(folderModel, id);
|
||||
|
||||
deletedFolders.push(deletedFolder);
|
||||
}
|
||||
|
||||
return deletedFolders;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a folder exists in database
|
||||
* @param params query params to find the folder
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
const exists = async (params = {}) => {
|
||||
const count = await strapi.query(folderModel).count({ where: params });
|
||||
return count > 0;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
exists,
|
||||
deleteByIds,
|
||||
setPathAndUID,
|
||||
};
|
||||
@ -1,11 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
const providerService = require('./provider');
|
||||
const uploadService = require('./upload');
|
||||
const provider = require('./provider');
|
||||
const upload = require('./upload');
|
||||
const imageManipulation = require('./image-manipulation');
|
||||
const folder = require('./folder');
|
||||
const file = require('./file');
|
||||
|
||||
module.exports = {
|
||||
provider: providerService,
|
||||
upload: uploadService,
|
||||
provider,
|
||||
upload,
|
||||
folder,
|
||||
file,
|
||||
'image-manipulation': imageManipulation,
|
||||
};
|
||||
|
||||
@ -63,16 +63,19 @@ module.exports = ({ strapi }) => ({
|
||||
strapi.eventHub.emit(event, { media: sanitizedData });
|
||||
},
|
||||
|
||||
formatFileInfo({ filename, type, size }, fileInfo = {}, metas = {}) {
|
||||
async formatFileInfo({ filename, type, size }, fileInfo = {}, metas = {}) {
|
||||
const fileService = getService('file');
|
||||
|
||||
const ext = path.extname(filename);
|
||||
const basename = path.basename(fileInfo.name || filename, ext);
|
||||
|
||||
const usedName = fileInfo.name || filename;
|
||||
|
||||
const entity = {
|
||||
name: usedName,
|
||||
alternativeText: fileInfo.alternativeText,
|
||||
caption: fileInfo.caption,
|
||||
folder: fileInfo.folder,
|
||||
folderPath: await fileService.getFolderPath(fileInfo.folder),
|
||||
hash: generateFileName(basename),
|
||||
ext,
|
||||
mime: type,
|
||||
@ -103,7 +106,7 @@ module.exports = ({ strapi }) => ({
|
||||
},
|
||||
|
||||
async enhanceFile(file, fileInfo = {}, metas = {}) {
|
||||
const currentFile = this.formatFileInfo(
|
||||
const currentFile = await this.formatFileInfo(
|
||||
{
|
||||
filename: file.name,
|
||||
type: file.type,
|
||||
@ -198,17 +201,22 @@ module.exports = ({ strapi }) => ({
|
||||
return this.add(fileData, { user });
|
||||
},
|
||||
|
||||
async updateFileInfo(id, { name, alternativeText, caption }, { user } = {}) {
|
||||
async updateFileInfo(id, { name, alternativeText, caption, folder }, { user } = {}) {
|
||||
const dbFile = await this.findOne(id);
|
||||
|
||||
if (!dbFile) {
|
||||
throw new NotFoundError();
|
||||
}
|
||||
|
||||
const fileService = getService('file');
|
||||
|
||||
const newName = _.isNil(name) ? dbFile.name : name;
|
||||
const newInfos = {
|
||||
name: _.isNil(name) ? dbFile.name : name,
|
||||
name: newName,
|
||||
alternativeText: _.isNil(alternativeText) ? dbFile.alternativeText : alternativeText,
|
||||
caption: _.isNil(caption) ? dbFile.caption : caption,
|
||||
folder: _.isUndefined(folder) ? dbFile.folder : folder,
|
||||
folderPath: _.isUndefined(folder) ? dbFile.path : await fileService.getFolderPath(folder),
|
||||
};
|
||||
|
||||
return this.update(id, newInfos, { user });
|
||||
@ -222,7 +230,6 @@ module.exports = ({ strapi }) => ({
|
||||
);
|
||||
|
||||
const dbFile = await this.findOne(id);
|
||||
|
||||
if (!dbFile) {
|
||||
throw new NotFoundError();
|
||||
}
|
||||
@ -236,7 +243,7 @@ module.exports = ({ strapi }) => ({
|
||||
const { fileInfo } = data;
|
||||
fileData = await this.enhanceFile(file, fileInfo);
|
||||
|
||||
// keep a constant hash
|
||||
// keep a constant hash and extension so the file url doesn't change when the file is replaced
|
||||
_.assign(fileData, {
|
||||
hash: dbFile.hash,
|
||||
ext: dbFile.ext,
|
||||
|
||||
387
packages/core/upload/tests/admin/file-folder.test.e2e.js
Normal file
387
packages/core/upload/tests/admin/file-folder.test.e2e.js
Normal file
@ -0,0 +1,387 @@
|
||||
'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: [],
|
||||
};
|
||||
|
||||
describe('File', () => {
|
||||
const builder = createTestBuilder();
|
||||
|
||||
beforeAll(async () => {
|
||||
strapi = await createStrapiInstance();
|
||||
rq = await createAuthRequest({ strapi });
|
||||
|
||||
// create 2 folders
|
||||
for (let i = 1; i <= 2; i += 1) {
|
||||
const folderRes = await rq({
|
||||
method: 'POST',
|
||||
url: '/upload/folders',
|
||||
body: { name: `my folder ${i}` },
|
||||
});
|
||||
data.folders.push(folderRes.body.data);
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await rq({
|
||||
method: 'POST',
|
||||
url: '/upload/folders/batch-delete',
|
||||
body: {
|
||||
ids: data.folders.map(f => f.id),
|
||||
},
|
||||
});
|
||||
|
||||
await strapi.destroy();
|
||||
await builder.cleanup();
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
test('Can create a file at root level', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: '/upload',
|
||||
formData: {
|
||||
files: fs.createReadStream(path.join(__dirname, '../utils/rec.jpg')),
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body)).toBe(true);
|
||||
expect(res.body.length).toBe(1);
|
||||
|
||||
const { body: file } = await rq({
|
||||
method: 'GET',
|
||||
url: `/upload/files/${res.body[0].id}`,
|
||||
});
|
||||
|
||||
expect(file).toMatchObject({
|
||||
id: expect.anything(),
|
||||
name: 'rec.jpg',
|
||||
ext: '.jpg',
|
||||
mime: 'image/jpeg',
|
||||
hash: expect.any(String),
|
||||
size: expect.any(Number),
|
||||
width: expect.any(Number),
|
||||
height: expect.any(Number),
|
||||
url: expect.any(String),
|
||||
provider: 'local',
|
||||
folder: null,
|
||||
folderPath: '/',
|
||||
});
|
||||
|
||||
data.files.push(file);
|
||||
});
|
||||
|
||||
test('Can create a file inside a folder', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: '/upload',
|
||||
formData: {
|
||||
files: fs.createReadStream(path.join(__dirname, '../utils/rec.jpg')),
|
||||
fileInfo: JSON.stringify({
|
||||
folder: data.folders[0].id,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body)).toBe(true);
|
||||
expect(res.body.length).toBe(1);
|
||||
|
||||
const { body: file } = await rq({
|
||||
method: 'GET',
|
||||
url: `/upload/files/${res.body[0].id}`,
|
||||
});
|
||||
|
||||
expect(file).toMatchObject({
|
||||
id: expect.anything(),
|
||||
name: 'rec.jpg',
|
||||
ext: '.jpg',
|
||||
mime: 'image/jpeg',
|
||||
hash: expect.any(String),
|
||||
size: expect.any(Number),
|
||||
width: expect.any(Number),
|
||||
height: expect.any(Number),
|
||||
url: expect.any(String),
|
||||
provider: 'local',
|
||||
folder: { id: data.folders[0].id },
|
||||
folderPath: data.folders[0].path,
|
||||
});
|
||||
|
||||
data.files.push(file);
|
||||
});
|
||||
|
||||
test("Cannot create a file inside a folder that doesn't exist", async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: '/upload',
|
||||
formData: {
|
||||
files: fs.createReadStream(path.join(__dirname, '../utils/rec.jpg')),
|
||||
fileInfo: JSON.stringify({
|
||||
folder: '1234', // id that doesn't exist
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error.message).toBe('the folder does not exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Update info', () => {
|
||||
describe('Move a file from a folder to another folder', () => {
|
||||
test('when replacing the file', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: `/upload?id=${data.files[1].id}`,
|
||||
formData: {
|
||||
files: fs.createReadStream(path.join(__dirname, '../utils/rec.pdf')),
|
||||
fileInfo: JSON.stringify({ folder: data.folders[1].id }),
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toMatchObject({ id: data.files[1].id });
|
||||
|
||||
const { body: file } = await rq({
|
||||
method: 'GET',
|
||||
url: `/upload/files/${res.body.id}`,
|
||||
});
|
||||
|
||||
expect(file).toMatchObject({
|
||||
id: expect.anything(),
|
||||
name: 'rec.pdf',
|
||||
ext: '.jpg',
|
||||
mime: 'application/pdf',
|
||||
hash: expect.any(String),
|
||||
size: expect.any(Number),
|
||||
width: expect.any(Number),
|
||||
height: expect.any(Number),
|
||||
url: expect.any(String),
|
||||
provider: 'local',
|
||||
folder: { id: data.folders[1].id },
|
||||
folderPath: data.folders[1].path,
|
||||
});
|
||||
data.files[1] = file;
|
||||
});
|
||||
|
||||
test('without replacing the file', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: `/upload?id=${data.files[1].id}`,
|
||||
formData: {
|
||||
fileInfo: JSON.stringify({ folder: data.folders[0].id }),
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toMatchObject({ id: data.files[1].id });
|
||||
|
||||
const { body: file } = await rq({
|
||||
method: 'GET',
|
||||
url: `/upload/files/${res.body.id}`,
|
||||
});
|
||||
|
||||
expect(file).toMatchObject({
|
||||
id: expect.anything(),
|
||||
name: 'rec.pdf',
|
||||
ext: '.jpg',
|
||||
mime: 'application/pdf',
|
||||
hash: expect.any(String),
|
||||
size: expect.any(Number),
|
||||
width: expect.any(Number),
|
||||
height: expect.any(Number),
|
||||
url: expect.any(String),
|
||||
provider: 'local',
|
||||
folder: { id: data.folders[0].id },
|
||||
folderPath: data.folders[0].path,
|
||||
});
|
||||
data.files[1] = file;
|
||||
});
|
||||
});
|
||||
describe('Move a file from root level to a folder', () => {
|
||||
test('when replacing the file', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: `/upload?id=${data.files[0].id}`,
|
||||
formData: {
|
||||
files: fs.createReadStream(path.join(__dirname, '../utils/rec.pdf')),
|
||||
fileInfo: JSON.stringify({ folder: data.folders[0].id }),
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toMatchObject({ id: data.files[0].id });
|
||||
|
||||
const { body: file } = await rq({
|
||||
method: 'GET',
|
||||
url: `/upload/files/${res.body.id}`,
|
||||
});
|
||||
|
||||
expect(file).toMatchObject({
|
||||
id: expect.anything(),
|
||||
name: 'rec.pdf',
|
||||
ext: '.jpg',
|
||||
mime: 'application/pdf',
|
||||
hash: expect.any(String),
|
||||
size: expect.any(Number),
|
||||
width: expect.any(Number),
|
||||
height: expect.any(Number),
|
||||
url: expect.any(String),
|
||||
provider: 'local',
|
||||
folder: { id: data.folders[0].id },
|
||||
folderPath: data.folders[0].path,
|
||||
});
|
||||
data.files[0] = file;
|
||||
});
|
||||
|
||||
test('without replacing the file', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: `/upload?id=${data.files[1].id}`,
|
||||
formData: {
|
||||
fileInfo: JSON.stringify({ folder: data.folders[1].id }),
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toMatchObject({ id: data.files[1].id });
|
||||
|
||||
const { body: file } = await rq({
|
||||
method: 'GET',
|
||||
url: `/upload/files/${res.body.id}`,
|
||||
});
|
||||
|
||||
expect(file).toMatchObject({
|
||||
id: expect.anything(),
|
||||
name: 'rec.pdf',
|
||||
ext: '.jpg',
|
||||
mime: 'application/pdf',
|
||||
hash: expect.any(String),
|
||||
size: expect.any(Number),
|
||||
width: expect.any(Number),
|
||||
height: expect.any(Number),
|
||||
url: expect.any(String),
|
||||
provider: 'local',
|
||||
folder: { id: data.folders[1].id },
|
||||
folderPath: data.folders[1].path,
|
||||
});
|
||||
data.files[1] = file;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Move a file from folder to the root level', () => {
|
||||
test('when replacing the file', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: `/upload?id=${data.files[0].id}`,
|
||||
formData: {
|
||||
files: fs.createReadStream(path.join(__dirname, '../utils/rec.jpg')),
|
||||
fileInfo: JSON.stringify({ folder: null }),
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toMatchObject({ id: data.files[0].id });
|
||||
|
||||
const { body: file } = await rq({
|
||||
method: 'GET',
|
||||
url: `/upload/files/${res.body.id}`,
|
||||
});
|
||||
|
||||
expect(file).toMatchObject({
|
||||
id: expect.anything(),
|
||||
name: 'rec.jpg',
|
||||
ext: '.jpg',
|
||||
mime: 'image/jpeg',
|
||||
hash: expect.any(String),
|
||||
size: expect.any(Number),
|
||||
width: expect.any(Number),
|
||||
height: expect.any(Number),
|
||||
url: expect.any(String),
|
||||
provider: 'local',
|
||||
folder: null,
|
||||
folderPath: '/',
|
||||
});
|
||||
data.files[0] = file;
|
||||
});
|
||||
|
||||
test('without replacing the file', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: `/upload?id=${data.files[1].id}`,
|
||||
formData: {
|
||||
fileInfo: JSON.stringify({ folder: null }),
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toMatchObject({ id: data.files[1].id });
|
||||
|
||||
const { body: file } = await rq({
|
||||
method: 'GET',
|
||||
url: `/upload/files/${res.body.id}`,
|
||||
});
|
||||
|
||||
expect(file).toMatchObject({
|
||||
id: expect.anything(),
|
||||
name: 'rec.pdf',
|
||||
ext: '.jpg',
|
||||
mime: 'application/pdf',
|
||||
hash: expect.any(String),
|
||||
size: expect.any(Number),
|
||||
width: expect.any(Number),
|
||||
height: expect.any(Number),
|
||||
url: expect.any(String),
|
||||
provider: 'local',
|
||||
folder: null,
|
||||
folderPath: '/',
|
||||
});
|
||||
data.files[1] = file;
|
||||
});
|
||||
});
|
||||
|
||||
describe("Cannot create a file inside a folder that doesn't exist", () => {
|
||||
test('when replacing the file', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: `/upload?id=${data.files[1].id}`,
|
||||
formData: {
|
||||
files: fs.createReadStream(path.join(__dirname, '../utils/rec.jpg')),
|
||||
fileInfo: JSON.stringify({
|
||||
folder: '1234', // id that doesn't exist
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error.message).toBe('the folder does not exist');
|
||||
});
|
||||
|
||||
test('whithout replacing the file', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: `/upload?id=${data.files[1].id}`,
|
||||
formData: {
|
||||
fileInfo: JSON.stringify({
|
||||
folder: '1234', // id that does not exist
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error.message).toBe('the folder does not exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
109
packages/core/upload/tests/admin/file-image.test.e2e.js
Normal file
109
packages/core/upload/tests/admin/file-image.test.e2e.js
Normal file
@ -0,0 +1,109 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Helpers.
|
||||
const { createTestBuilder } = require('../../../../../test/helpers/builder');
|
||||
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
|
||||
const { createAuthRequest } = require('../../../../../test/helpers/request');
|
||||
|
||||
const builder = createTestBuilder();
|
||||
let strapi;
|
||||
let rq;
|
||||
|
||||
const dogModel = {
|
||||
displayName: 'Dog',
|
||||
singularName: 'dog',
|
||||
pluralName: 'dogs',
|
||||
kind: 'collectionType',
|
||||
attributes: {
|
||||
profilePicture: {
|
||||
type: 'media',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('Upload', () => {
|
||||
beforeAll(async () => {
|
||||
await builder.addContentType(dogModel).build();
|
||||
strapi = await createStrapiInstance();
|
||||
rq = await createAuthRequest({ strapi });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await strapi.destroy();
|
||||
await builder.cleanup();
|
||||
});
|
||||
|
||||
describe('POST /upload => Upload a file', () => {
|
||||
test('Simple image upload', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: '/upload',
|
||||
formData: {
|
||||
files: fs.createReadStream(path.join(__dirname, '../utils/rec.jpg')),
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body)).toBe(true);
|
||||
expect(res.body.length).toBe(1);
|
||||
expect(res.body[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.anything(),
|
||||
name: 'rec.jpg',
|
||||
ext: '.jpg',
|
||||
mime: 'image/jpeg',
|
||||
hash: expect.any(String),
|
||||
size: expect.any(Number),
|
||||
width: expect.any(Number),
|
||||
height: expect.any(Number),
|
||||
url: expect.any(String),
|
||||
provider: 'local',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('Generates a thumbnail on large enough files', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: '/upload',
|
||||
formData: {
|
||||
files: fs.createReadStream(path.join(__dirname, '../utils/thumbnail_target.png')),
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body)).toBe(true);
|
||||
expect(res.body.length).toBe(1);
|
||||
expect(res.body[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.anything(),
|
||||
name: 'thumbnail_target.png',
|
||||
ext: '.png',
|
||||
mime: 'image/png',
|
||||
hash: expect.any(String),
|
||||
size: expect.any(Number),
|
||||
width: expect.any(Number),
|
||||
height: expect.any(Number),
|
||||
url: expect.any(String),
|
||||
provider: 'local',
|
||||
formats: {
|
||||
thumbnail: {
|
||||
name: 'thumbnail_thumbnail_target.png',
|
||||
hash: expect.any(String),
|
||||
ext: '.png',
|
||||
mime: 'image/png',
|
||||
size: expect.any(Number),
|
||||
width: expect.any(Number),
|
||||
height: expect.any(Number),
|
||||
url: expect.any(String),
|
||||
path: null,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
63
packages/core/upload/tests/admin/file.test.e2e.js
Normal file
63
packages/core/upload/tests/admin/file.test.e2e.js
Normal file
@ -0,0 +1,63 @@
|
||||
'use strict';
|
||||
|
||||
const { createTestBuilder } = require('../../../../../test/helpers/builder');
|
||||
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
|
||||
const { createAuthRequest } = require('../../../../../test/helpers/request');
|
||||
|
||||
const builder = createTestBuilder();
|
||||
let strapi;
|
||||
let rq;
|
||||
|
||||
const dogModel = {
|
||||
displayName: 'Dog',
|
||||
singularName: 'dog',
|
||||
pluralName: 'dogs',
|
||||
kind: 'collectionType',
|
||||
attributes: {
|
||||
profilePicture: {
|
||||
type: 'media',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('Upload', () => {
|
||||
beforeAll(async () => {
|
||||
await builder.addContentType(dogModel).build();
|
||||
strapi = await createStrapiInstance();
|
||||
rq = await createAuthRequest({ strapi });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await strapi.destroy();
|
||||
await builder.cleanup();
|
||||
});
|
||||
|
||||
describe('Create', () => {
|
||||
test('Rejects when no files are provided', async () => {
|
||||
const res = await rq({ method: 'POST', url: '/upload', formData: {} });
|
||||
expect(res.statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Read', () => {
|
||||
test('GET /upload/files => Find files', async () => {
|
||||
const getRes = await rq({ method: 'GET', url: '/upload/files' });
|
||||
|
||||
expect(getRes.statusCode).toBe(200);
|
||||
expect(getRes.body).toEqual({
|
||||
results: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.anything(),
|
||||
url: expect.any(String),
|
||||
}),
|
||||
]),
|
||||
pagination: {
|
||||
page: expect.any(Number),
|
||||
pageSize: expect.any(Number),
|
||||
pageCount: expect.any(Number),
|
||||
total: expect.any(Number),
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
222
packages/core/upload/tests/admin/folder.test.e2e.js
Normal file
222
packages/core/upload/tests/admin/folder.test.e2e.js
Normal file
@ -0,0 +1,222 @@
|
||||
'use strict';
|
||||
|
||||
// Test a simple default API with no relations
|
||||
|
||||
const { omit, pick } = require('lodash/fp');
|
||||
|
||||
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: [],
|
||||
};
|
||||
|
||||
const uuidRegex = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;
|
||||
const rootPathRegex = /^\/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;
|
||||
const getFolderPathRegex = uid =>
|
||||
new RegExp(
|
||||
'^/' + uid + '/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$',
|
||||
'i'
|
||||
);
|
||||
|
||||
describe('Folder', () => {
|
||||
const builder = createTestBuilder();
|
||||
|
||||
beforeAll(async () => {
|
||||
strapi = await createStrapiInstance();
|
||||
rq = await createAuthRequest({ strapi });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await strapi.destroy();
|
||||
await builder.cleanup();
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
test('Can create a folder at root level', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: '/upload/folders?populate=parent',
|
||||
body: {
|
||||
name: 'folder 1',
|
||||
parent: null,
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.data).toMatchObject({
|
||||
id: expect.anything(),
|
||||
name: 'folder 1',
|
||||
uid: expect.stringMatching(uuidRegex),
|
||||
path: expect.stringMatching(rootPathRegex),
|
||||
createdAt: expect.anything(),
|
||||
updatedAt: expect.anything(),
|
||||
parent: null,
|
||||
});
|
||||
expect(res.body.data.uid).toBe(res.body.data.path.split('/').pop());
|
||||
|
||||
data.folders.push(omit('parent', res.body.data));
|
||||
});
|
||||
|
||||
test('Can create a folder inside another folder', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: '/upload/folders?populate=parent',
|
||||
body: {
|
||||
name: 'folder-2',
|
||||
parent: data.folders[0].id,
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.body.data).toMatchObject({
|
||||
id: expect.anything(),
|
||||
name: 'folder-2',
|
||||
uid: expect.stringMatching(uuidRegex),
|
||||
path: expect.stringMatching(getFolderPathRegex(data.folders[0].uid)),
|
||||
createdAt: expect.anything(),
|
||||
updatedAt: expect.anything(),
|
||||
parent: data.folders[0],
|
||||
});
|
||||
expect(res.body.data.uid).toBe(res.body.data.path.split('/').pop());
|
||||
|
||||
data.folders.push(omit('parent', res.body.data));
|
||||
});
|
||||
|
||||
test('Cannot create a folder with duplicated name at root level', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: '/upload/folders?populate=parent',
|
||||
body: {
|
||||
name: 'folder 1',
|
||||
parent: null,
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error.message).toBe('name already taken');
|
||||
});
|
||||
|
||||
test('Cannot create a folder with duplicated name inside a folder', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: '/upload/folders?populate=parent',
|
||||
body: {
|
||||
name: 'folder-2',
|
||||
parent: data.folders[0],
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error.message).toBe('name already taken');
|
||||
});
|
||||
|
||||
test('Cannot create a folder with name containing a slash', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: '/upload/folders?populate=parent',
|
||||
body: {
|
||||
name: 'folder 1/2',
|
||||
parent: null,
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error.message).toBe('name cannot contain slashes');
|
||||
});
|
||||
|
||||
test.each([[' abc'], [' abc '], ['abc '], [' abc '], [' abc ']])(
|
||||
'Cannot create a folder with name starting or ending with a whitespace (%p)',
|
||||
async name => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: '/upload/folders?populate=parent',
|
||||
body: {
|
||||
name,
|
||||
parent: null,
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error.message).toBe('name cannot start or end with a whitespace');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('read', () => {
|
||||
test('Can read folders', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: '/upload/folders',
|
||||
});
|
||||
|
||||
expect(res.body.pagination).toMatchObject({
|
||||
page: 1,
|
||||
pageCount: 1,
|
||||
pageSize: 10,
|
||||
total: 2,
|
||||
});
|
||||
expect(res.body.results).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
...data.folders[0],
|
||||
children: { count: 1 },
|
||||
createdBy: {
|
||||
firstname: expect.anything(),
|
||||
id: expect.anything(),
|
||||
lastname: expect.anything(),
|
||||
username: null,
|
||||
},
|
||||
files: { count: 0 },
|
||||
parent: null,
|
||||
updatedBy: {
|
||||
firstname: expect.anything(),
|
||||
id: expect.anything(),
|
||||
lastname: expect.anything(),
|
||||
username: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
...data.folders[1],
|
||||
children: { count: 0 },
|
||||
createdBy: {
|
||||
firstname: expect.anything(),
|
||||
id: expect.anything(),
|
||||
lastname: expect.anything(),
|
||||
username: null,
|
||||
},
|
||||
files: { count: 0 },
|
||||
parent: pick(['createdAt', 'id', 'name', 'path', 'uid', 'updatedAt'], data.folders[0]),
|
||||
updatedBy: {
|
||||
firstname: expect.anything(),
|
||||
id: expect.anything(),
|
||||
lastname: expect.anything(),
|
||||
username: null,
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
test('Can delete folders', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: '/upload/folders/batch-delete',
|
||||
body: {
|
||||
ids: data.folders.map(f => f.id),
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.body.data).toEqual(
|
||||
expect.arrayContaining([
|
||||
pick(['id', 'name', 'path', 'uid', 'updatedAt', 'createdAt'])(data.folders[0]),
|
||||
pick(['id', 'name', 'path', 'uid', 'updatedAt', 'createdAt'])(data.folders[1]),
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
81
packages/core/upload/tests/admin/settings.test.e2e.js
Normal file
81
packages/core/upload/tests/admin/settings.test.e2e.js
Normal file
@ -0,0 +1,81 @@
|
||||
'use strict';
|
||||
|
||||
// Helpers.
|
||||
const { createTestBuilder } = require('../../../../../test/helpers/builder');
|
||||
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
|
||||
const { createAuthRequest } = require('../../../../../test/helpers/request');
|
||||
|
||||
const builder = createTestBuilder();
|
||||
let strapi;
|
||||
let rq;
|
||||
|
||||
const dogModel = {
|
||||
displayName: 'Dog',
|
||||
singularName: 'dog',
|
||||
pluralName: 'dogs',
|
||||
kind: 'collectionType',
|
||||
attributes: {
|
||||
profilePicture: {
|
||||
type: 'media',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('Settings', () => {
|
||||
beforeAll(async () => {
|
||||
await builder.addContentType(dogModel).build();
|
||||
strapi = await createStrapiInstance();
|
||||
rq = await createAuthRequest({ strapi });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await strapi.destroy();
|
||||
await builder.cleanup();
|
||||
});
|
||||
|
||||
describe('GET /upload/settings => Get settings for an environment', () => {
|
||||
test('Returns the settings', async () => {
|
||||
const res = await rq({ method: 'GET', url: '/upload/settings' });
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toEqual({
|
||||
data: {
|
||||
autoOrientation: false,
|
||||
sizeOptimization: true,
|
||||
responsiveDimensions: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /upload/settings/:environment', () => {
|
||||
test('Updates an environment config correctly', async () => {
|
||||
const updateRes = await rq({
|
||||
method: 'PUT',
|
||||
url: '/upload/settings',
|
||||
body: {
|
||||
sizeOptimization: true,
|
||||
responsiveDimensions: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(updateRes.statusCode).toBe(200);
|
||||
expect(updateRes.body).toEqual({
|
||||
data: {
|
||||
sizeOptimization: true,
|
||||
responsiveDimensions: true,
|
||||
},
|
||||
});
|
||||
|
||||
const getRes = await rq({ method: 'GET', url: '/upload/settings' });
|
||||
|
||||
expect(getRes.statusCode).toBe(200);
|
||||
expect(getRes.body).toEqual({
|
||||
data: {
|
||||
sizeOptimization: true,
|
||||
responsiveDimensions: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -3,8 +3,8 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const { createStrapiInstance } = require('../../../../test/helpers/strapi');
|
||||
const { createAuthRequest } = require('../../../../test/helpers/request');
|
||||
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
|
||||
const { createAuthRequest } = require('../../../../../test/helpers/request');
|
||||
|
||||
let strapi;
|
||||
let rq;
|
||||
@ -45,7 +45,7 @@ describe('Upload plugin end to end tests', () => {
|
||||
map: JSON.stringify({
|
||||
nFile1: ['variables.file'],
|
||||
}),
|
||||
nFile1: fs.createReadStream(path.join(__dirname, '/rec.jpg')),
|
||||
nFile1: fs.createReadStream(path.join(__dirname, '../utils/rec.jpg')),
|
||||
};
|
||||
|
||||
const res = await rq({ method: 'POST', url: '/graphql', formData });
|
||||
@ -94,8 +94,8 @@ describe('Upload plugin end to end tests', () => {
|
||||
nFile0: ['variables.files.0'],
|
||||
nFile1: ['variables.files.1'],
|
||||
}),
|
||||
nFile0: fs.createReadStream(path.join(__dirname, '/rec.jpg')),
|
||||
nFile1: fs.createReadStream(path.join(__dirname, '/rec.jpg')),
|
||||
nFile0: fs.createReadStream(path.join(__dirname, '../utils/rec.jpg')),
|
||||
nFile1: fs.createReadStream(path.join(__dirname, '../utils/rec.jpg')),
|
||||
};
|
||||
|
||||
const res = await rq({ method: 'POST', url: '/graphql', formData });
|
||||
@ -144,7 +144,7 @@ describe('Upload plugin end to end tests', () => {
|
||||
map: JSON.stringify({
|
||||
nFile1: ['variables.file'],
|
||||
}),
|
||||
nFile1: fs.createReadStream(path.join(__dirname, '/rec.pdf')),
|
||||
nFile1: fs.createReadStream(path.join(__dirname, '../utils/rec.pdf')),
|
||||
};
|
||||
|
||||
const res = await rq({ method: 'POST', url: '/graphql', formData });
|
||||
@ -193,8 +193,8 @@ describe('Upload plugin end to end tests', () => {
|
||||
nFile0: ['variables.files.0'],
|
||||
nFile1: ['variables.files.1'],
|
||||
}),
|
||||
nFile0: fs.createReadStream(path.join(__dirname, '/rec.pdf')),
|
||||
nFile1: fs.createReadStream(path.join(__dirname, '/rec.pdf')),
|
||||
nFile0: fs.createReadStream(path.join(__dirname, '../utils/rec.pdf')),
|
||||
nFile1: fs.createReadStream(path.join(__dirname, '../utils/rec.pdf')),
|
||||
};
|
||||
|
||||
const res = await rq({ method: 'POST', url: '/graphql', formData });
|
||||
@ -354,7 +354,7 @@ describe('Upload plugin end to end tests', () => {
|
||||
map: JSON.stringify({
|
||||
nFile1: ['variables.file'],
|
||||
}),
|
||||
nFile1: fs.createReadStream(path.join(__dirname, '/rec.jpg')),
|
||||
nFile1: fs.createReadStream(path.join(__dirname, '../utils/rec.jpg')),
|
||||
};
|
||||
|
||||
const res = await rq({ method: 'POST', url: '/graphql', formData });
|
||||
@ -404,7 +404,7 @@ describe('Upload plugin end to end tests', () => {
|
||||
map: JSON.stringify({
|
||||
nFile1: ['variables.file'],
|
||||
}),
|
||||
nFile1: fs.createReadStream(path.join(__dirname, '/rec.pdf')),
|
||||
nFile1: fs.createReadStream(path.join(__dirname, '../utils/rec.pdf')),
|
||||
};
|
||||
|
||||
const res = await rq({ method: 'POST', url: '/graphql', formData });
|
||||
@ -4,9 +4,9 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Helpers.
|
||||
const { createTestBuilder } = require('../../../../test/helpers/builder');
|
||||
const { createStrapiInstance } = require('../../../../test/helpers/strapi');
|
||||
const { createAuthRequest } = require('../../../../test/helpers/request');
|
||||
const { createTestBuilder } = require('../../../../../test/helpers/builder');
|
||||
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
|
||||
const { createContentAPIRequest } = require('../../../../../test/helpers/request');
|
||||
|
||||
const builder = createTestBuilder();
|
||||
let strapi;
|
||||
@ -28,7 +28,7 @@ describe('Upload plugin end to end tests', () => {
|
||||
beforeAll(async () => {
|
||||
await builder.addContentType(dogModel).build();
|
||||
strapi = await createStrapiInstance();
|
||||
rq = await createAuthRequest({ strapi });
|
||||
rq = await createContentAPIRequest({ strapi });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@ -36,59 +36,13 @@ describe('Upload plugin end to end tests', () => {
|
||||
await builder.cleanup();
|
||||
});
|
||||
|
||||
describe('GET /upload/settings => Get settings for an environment', () => {
|
||||
test('Returns the settings', async () => {
|
||||
const res = await rq({ method: 'GET', url: '/upload/settings' });
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toEqual({
|
||||
data: {
|
||||
autoOrientation: false,
|
||||
sizeOptimization: true,
|
||||
responsiveDimensions: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /upload/settings/:environment', () => {
|
||||
test('Updates an environment config correctly', async () => {
|
||||
const updateRes = await rq({
|
||||
method: 'PUT',
|
||||
url: '/upload/settings',
|
||||
body: {
|
||||
sizeOptimization: true,
|
||||
responsiveDimensions: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(updateRes.statusCode).toBe(200);
|
||||
expect(updateRes.body).toEqual({
|
||||
data: {
|
||||
sizeOptimization: true,
|
||||
responsiveDimensions: true,
|
||||
},
|
||||
});
|
||||
|
||||
const getRes = await rq({ method: 'GET', url: '/upload/settings' });
|
||||
|
||||
expect(getRes.statusCode).toBe(200);
|
||||
expect(getRes.body).toEqual({
|
||||
data: {
|
||||
sizeOptimization: true,
|
||||
responsiveDimensions: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /upload => Upload a file', () => {
|
||||
describe('Create', () => {
|
||||
test('Simple image upload', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: '/upload',
|
||||
formData: {
|
||||
files: fs.createReadStream(path.join(__dirname, 'rec.jpg')),
|
||||
files: fs.createReadStream(path.join(__dirname, '../utils/rec.jpg')),
|
||||
},
|
||||
});
|
||||
|
||||
@ -121,7 +75,7 @@ describe('Upload plugin end to end tests', () => {
|
||||
method: 'POST',
|
||||
url: '/upload',
|
||||
formData: {
|
||||
files: fs.createReadStream(path.join(__dirname, 'thumbnail_target.png')),
|
||||
files: fs.createReadStream(path.join(__dirname, '../utils/thumbnail_target.png')),
|
||||
},
|
||||
});
|
||||
|
||||
@ -158,34 +112,30 @@ describe('Upload plugin end to end tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('GET /upload/files => Find files', async () => {
|
||||
const getRes = await rq({ method: 'GET', url: '/upload/files' });
|
||||
describe('Read', () => {
|
||||
test('Get files', async () => {
|
||||
const getRes = await rq({ method: 'GET', url: '/upload/files' });
|
||||
|
||||
expect(getRes.statusCode).toBe(200);
|
||||
expect(getRes.body).toEqual({
|
||||
results: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.anything(),
|
||||
url: expect.any(String),
|
||||
}),
|
||||
]),
|
||||
pagination: {
|
||||
page: expect.any(Number),
|
||||
pageSize: expect.any(Number),
|
||||
pageCount: expect.any(Number),
|
||||
total: expect.any(Number),
|
||||
},
|
||||
expect(getRes.statusCode).toBe(200);
|
||||
expect(getRes.body).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.anything(),
|
||||
url: expect.any(String),
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /api/:uid => Create an entity with a file', () => {
|
||||
describe('Create an entity with a file', () => {
|
||||
test('With an image', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: '/api/dogs?populate=*',
|
||||
url: '/dogs?populate=*',
|
||||
formData: {
|
||||
data: '{}',
|
||||
'files.profilePicture': fs.createReadStream(path.join(__dirname, 'rec.jpg')),
|
||||
'files.profilePicture': fs.createReadStream(path.join(__dirname, '../utils/rec.jpg')),
|
||||
},
|
||||
});
|
||||
|
||||
@ -210,10 +160,10 @@ describe('Upload plugin end to end tests', () => {
|
||||
test('With a pdf', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: '/api/dogs?populate=*',
|
||||
url: '/dogs?populate=*',
|
||||
formData: {
|
||||
data: '{}',
|
||||
'files.profilePicture': fs.createReadStream(path.join(__dirname, 'rec.pdf')),
|
||||
'files.profilePicture': fs.createReadStream(path.join(__dirname, '../utils/rec.pdf')),
|
||||
},
|
||||
});
|
||||
|
||||
@ -235,7 +185,4 @@ describe('Upload plugin end to end tests', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
test.todo('GET /upload/files/:id => Find one file');
|
||||
test.todo('GET /upload/search/:id => Search files');
|
||||
test.todo('DELETE /upload/files/:id => Delete a file');
|
||||
});
|
||||
|
Before Width: | Height: | Size: 787 B After Width: | Height: | Size: 787 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@ -3,7 +3,7 @@
|
||||
const { join, resolve, basename } = require('path');
|
||||
const os = require('os');
|
||||
const crypto = require('crypto');
|
||||
const uuid = require('uuid/v4');
|
||||
const uuid = require('uuid').v4;
|
||||
const sentry = require('@sentry/node');
|
||||
// FIXME
|
||||
/* eslint-disable import/extensions */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user