mirror of
https://github.com/strapi/strapi.git
synced 2025-08-24 00:27:13 +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-redux": "7.2.3",
|
||||||
"react-router": "^5.2.0",
|
"react-router": "^5.2.0",
|
||||||
"react-router-dom": "5.2.0",
|
"react-router-dom": "5.2.0",
|
||||||
"sharp": "0.30.1"
|
"sharp": "0.30.1",
|
||||||
|
"uuid": "8.3.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.22.0 <=16.x.x",
|
"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',
|
relation: 'morphToMany',
|
||||||
configurable: false,
|
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';
|
'use strict';
|
||||||
|
|
||||||
const file = require('./file');
|
const file = require('./file');
|
||||||
|
const folder = require('./folder');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
file,
|
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 _ = require('lodash');
|
||||||
const utils = require('@strapi/utils');
|
const utils = require('@strapi/utils');
|
||||||
const { getService } = require('../utils');
|
const { getService } = require('../utils');
|
||||||
const validateSettings = require('./validation/settings');
|
|
||||||
const validateUploadBody = require('./validation/upload');
|
const validateUploadBody = require('./validation/upload');
|
||||||
|
|
||||||
const { sanitize } = utils;
|
const { sanitize } = utils;
|
||||||
@ -57,24 +56,6 @@ module.exports = {
|
|||||||
ctx.body = await sanitizeOutput(file, ctx);
|
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) {
|
async updateFileInfo(ctx) {
|
||||||
const {
|
const {
|
||||||
query: { id },
|
query: { id },
|
||||||
@ -135,15 +116,4 @@ module.exports = {
|
|||||||
|
|
||||||
await (id ? this.replaceFile : this.uploadFiles)(ctx);
|
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';
|
'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');
|
const contentApi = require('./content-api');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
'admin-api': adminApi,
|
'admin-file': adminFile,
|
||||||
|
'admin-folder': adminFolder,
|
||||||
|
'admin-settings': adminSettings,
|
||||||
|
'admin-upload': adminUpload,
|
||||||
'content-api': contentApi,
|
'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',
|
method: 'GET',
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
handler: 'admin-api.getSettings',
|
handler: 'admin-settings.getSettings',
|
||||||
config: {
|
config: {
|
||||||
policies: [
|
policies: [
|
||||||
'admin::isAuthenticatedAdmin',
|
'admin::isAuthenticatedAdmin',
|
||||||
@ -22,7 +22,7 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
handler: 'admin-api.updateSettings',
|
handler: 'admin-settings.updateSettings',
|
||||||
config: {
|
config: {
|
||||||
policies: [
|
policies: [
|
||||||
'admin::isAuthenticatedAdmin',
|
'admin::isAuthenticatedAdmin',
|
||||||
@ -38,7 +38,7 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/',
|
path: '/',
|
||||||
handler: 'admin-api.upload',
|
handler: 'admin-upload.upload',
|
||||||
config: {
|
config: {
|
||||||
policies: ['admin::isAuthenticatedAdmin'],
|
policies: ['admin::isAuthenticatedAdmin'],
|
||||||
},
|
},
|
||||||
@ -46,7 +46,7 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: '/files',
|
path: '/files',
|
||||||
handler: 'admin-api.find',
|
handler: 'admin-file.find',
|
||||||
config: {
|
config: {
|
||||||
policies: [
|
policies: [
|
||||||
'admin::isAuthenticatedAdmin',
|
'admin::isAuthenticatedAdmin',
|
||||||
@ -62,7 +62,7 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: '/files/:id',
|
path: '/files/:id',
|
||||||
handler: 'admin-api.findOne',
|
handler: 'admin-file.findOne',
|
||||||
config: {
|
config: {
|
||||||
policies: [
|
policies: [
|
||||||
'admin::isAuthenticatedAdmin',
|
'admin::isAuthenticatedAdmin',
|
||||||
@ -78,7 +78,7 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
path: '/files/:id',
|
path: '/files/:id',
|
||||||
handler: 'admin-api.destroy',
|
handler: 'admin-file.destroy',
|
||||||
config: {
|
config: {
|
||||||
policies: [
|
policies: [
|
||||||
'admin::isAuthenticatedAdmin',
|
'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';
|
'use strict';
|
||||||
|
|
||||||
const providerService = require('./provider');
|
const provider = require('./provider');
|
||||||
const uploadService = require('./upload');
|
const upload = require('./upload');
|
||||||
const imageManipulation = require('./image-manipulation');
|
const imageManipulation = require('./image-manipulation');
|
||||||
|
const folder = require('./folder');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
provider: providerService,
|
provider,
|
||||||
upload: uploadService,
|
upload,
|
||||||
|
folder,
|
||||||
'image-manipulation': imageManipulation,
|
'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 fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const { createStrapiInstance } = require('../../../../test/helpers/strapi');
|
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
|
||||||
const { createAuthRequest } = require('../../../../test/helpers/request');
|
const { createAuthRequest } = require('../../../../../test/helpers/request');
|
||||||
|
|
||||||
let strapi;
|
let strapi;
|
||||||
let rq;
|
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');
|
const path = require('path');
|
||||||
|
|
||||||
// Helpers.
|
// Helpers.
|
||||||
const { createTestBuilder } = require('../../../../test/helpers/builder');
|
const { createTestBuilder } = require('../../../../../test/helpers/builder');
|
||||||
const { createStrapiInstance } = require('../../../../test/helpers/strapi');
|
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
|
||||||
const { createAuthRequest } = require('../../../../test/helpers/request');
|
const { createAuthRequest } = require('../../../../../test/helpers/request');
|
||||||
|
|
||||||
const builder = createTestBuilder();
|
const builder = createTestBuilder();
|
||||||
let strapi;
|
let strapi;
|
Loading…
x
Reference in New Issue
Block a user