mirror of
https://github.com/strapi/strapi.git
synced 2025-08-18 05:37:10 +00:00
restructure upload controllers + add create and get folders
This commit is contained in:
parent
0f8956db72
commit
94308aa2bc
@ -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',
|
||||
},
|
||||
path: {
|
||||
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 };
|
||||
};
|
65
packages/core/upload/server/controllers/admin-file.js
Normal file
65
packages/core/upload/server/controllers/admin-file.js
Normal file
@ -0,0 +1,65 @@
|
||||
'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
|
||||
);
|
||||
|
||||
await getService('upload').remove(file);
|
||||
|
||||
ctx.body = await pm.sanitizeOutput(file, { action: ACTIONS.read });
|
||||
},
|
||||
};
|
74
packages/core/upload/server/controllers/admin-folder.js
Normal file
74
packages/core/upload/server/controllers/admin-folder.js
Normal file
@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
|
||||
const { setCreatorFields, pipeAsync } = require('@strapi/utils');
|
||||
const { ApplicationError } = require('@strapi/utils').errors;
|
||||
const { getService } = require('../utils');
|
||||
const { validateCreateFolder } = require('./validation/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 existingFolders = await strapi.entityService.findMany(folderModel, {
|
||||
filters: {
|
||||
parent: body.parent,
|
||||
name: body.name,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingFolders.length > 0) {
|
||||
throw new ApplicationError('name already taken');
|
||||
}
|
||||
|
||||
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),
|
||||
};
|
||||
},
|
||||
};
|
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/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/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 (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);
|
||||
},
|
||||
};
|
@ -3,7 +3,6 @@
|
||||
const _ = require('lodash');
|
||||
const utils = require('@strapi/utils');
|
||||
const { getService } = require('../utils');
|
||||
const validateSettings = require('./validation/settings');
|
||||
const validateUploadBody = require('./validation/upload');
|
||||
|
||||
const { sanitize } = utils;
|
||||
@ -57,24 +56,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 +116,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,33 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const { contentTypes: contentTypesUtils } = require('@strapi/utils');
|
||||
const { NotFoundError, ForbiddenError } = require('@strapi/utils').errors;
|
||||
const { getService } = require('../../utils');
|
||||
|
||||
const { CREATED_BY_ATTRIBUTE } = contentTypesUtils.constants;
|
||||
|
||||
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 };
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
findEntityAndCheckPermissions,
|
||||
};
|
22
packages/core/upload/server/controllers/validation/folder.js
Normal file
22
packages/core/upload/server/controllers/validation/folder.js
Normal file
@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
const { yup, validateYupSchema } = require('@strapi/utils');
|
||||
|
||||
const NO_SLASH_REGEX = /^[^/]+$/;
|
||||
|
||||
const validateCreateFolderSchema = yup
|
||||
.object()
|
||||
.shape({
|
||||
name: yup
|
||||
.string()
|
||||
.min(1)
|
||||
.matches(NO_SLASH_REGEX, 'name cannot contain slashes')
|
||||
.required(),
|
||||
parent: yup.strapiID().nullable(),
|
||||
})
|
||||
.noUnknown()
|
||||
.required();
|
||||
|
||||
module.exports = {
|
||||
validateCreateFolder: validateYupSchema(validateCreateFolderSchema),
|
||||
};
|
@ -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,37 @@ 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'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
42
packages/core/upload/server/services/folder.js
Normal file
42
packages/core/upload/server/services/folder.js
Normal file
@ -0,0 +1,42 @@
|
||||
'use strict';
|
||||
|
||||
const uuid = require('uuid/v4');
|
||||
const { trimChars, trimCharsEnd, trimCharsStart } = require('lodash/fp');
|
||||
|
||||
// TODO: to use once https://github.com/strapi/strapi/pull/12534 is merged
|
||||
// const { joinBy } = require('@strapi/utils');
|
||||
|
||||
const folderModel = 'plugin::upload.folder';
|
||||
|
||||
const joinBy = (joint, ...args) => {
|
||||
const trim = trimChars(joint);
|
||||
const trimEnd = trimCharsEnd(joint);
|
||||
const trimStart = trimCharsStart(joint);
|
||||
|
||||
return args.reduce((url, path, index) => {
|
||||
if (args.length === 1) return path;
|
||||
if (index === 0) return trimEnd(path);
|
||||
if (index === args.length - 1) return url + joint + trimStart(path);
|
||||
return url + joint + trim(path);
|
||||
}, '');
|
||||
};
|
||||
|
||||
const generateUID = () => uuid();
|
||||
|
||||
const setPathAndUID = async folder => {
|
||||
let parentPath = '/';
|
||||
if (folder.parent) {
|
||||
const parentFolder = await strapi.entityService.findOne(folderModel, folder.parent);
|
||||
parentPath = parentFolder.path;
|
||||
}
|
||||
|
||||
return Object.assign(folder, {
|
||||
uid: generateUID(),
|
||||
path: joinBy('/', parentPath, folder.name),
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
generateUID,
|
||||
setPathAndUID,
|
||||
};
|
@ -1,11 +1,13 @@
|
||||
'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');
|
||||
|
||||
module.exports = {
|
||||
provider: providerService,
|
||||
upload: uploadService,
|
||||
provider,
|
||||
upload,
|
||||
folder,
|
||||
'image-manipulation': imageManipulation,
|
||||
};
|
||||
|
181
packages/core/upload/tests/admin/folder.test.e2e.js
Normal file
181
packages/core/upload/tests/admin/folder.test.e2e.js
Normal file
@ -0,0 +1,181 @@
|
||||
'use strict';
|
||||
|
||||
// Test a simple default API with no relations
|
||||
|
||||
const { omit } = 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: [],
|
||||
};
|
||||
|
||||
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.body.data).toMatchObject({
|
||||
id: expect.anything(),
|
||||
name: 'folder-1',
|
||||
uid: expect.anything(),
|
||||
path: '/folder-1',
|
||||
createdAt: expect.anything(),
|
||||
updatedAt: expect.anything(),
|
||||
parent: null,
|
||||
});
|
||||
|
||||
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 path 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('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.anything(),
|
||||
path: '/folder-1/folder-2',
|
||||
createdAt: expect.anything(),
|
||||
updatedAt: expect.anything(),
|
||||
parent: data.folders[0],
|
||||
});
|
||||
|
||||
data.folders.push(omit('parent', res.body.data));
|
||||
});
|
||||
});
|
||||
|
||||
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).toHaveLength(2);
|
||||
expect(res.body.results).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
children: { count: 1 },
|
||||
createdAt: expect.anything(),
|
||||
createdBy: {
|
||||
firstname: 'admin',
|
||||
id: expect.anything(),
|
||||
lastname: 'admin',
|
||||
username: null,
|
||||
},
|
||||
files: { count: 0 },
|
||||
id: expect.anything(),
|
||||
name: 'folder-1',
|
||||
parent: null,
|
||||
path: '/folder-1',
|
||||
uid: expect.anything(),
|
||||
updatedAt: expect.anything(),
|
||||
updatedBy: {
|
||||
firstname: 'admin',
|
||||
id: expect.anything(),
|
||||
lastname: 'admin',
|
||||
username: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
children: { count: 0 },
|
||||
createdAt: expect.anything(),
|
||||
createdBy: {
|
||||
firstname: 'admin',
|
||||
id: expect.anything(),
|
||||
lastname: 'admin',
|
||||
username: null,
|
||||
},
|
||||
files: {
|
||||
count: 0,
|
||||
},
|
||||
id: expect.anything(),
|
||||
name: 'folder-2',
|
||||
parent: {
|
||||
createdAt: expect.anything(),
|
||||
id: expect.anything(),
|
||||
name: 'folder-1',
|
||||
path: '/folder-1',
|
||||
uid: expect.anything(),
|
||||
updatedAt: expect.anything(),
|
||||
},
|
||||
path: '/folder-1/folder-2',
|
||||
uid: expect.anything(),
|
||||
updatedAt: expect.anything(),
|
||||
updatedBy: {
|
||||
firstname: 'admin',
|
||||
id: expect.anything(),
|
||||
lastname: 'admin',
|
||||
username: null,
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -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;
|
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 |
@ -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 { createAuthRequest } = require('../../../../../test/helpers/request');
|
||||
|
||||
const builder = createTestBuilder();
|
||||
let strapi;
|
Loading…
x
Reference in New Issue
Block a user