diff --git a/public/language/en-GB/admin/manage/uploads.json b/public/language/en-GB/admin/manage/uploads.json index 21bc8201fc..72a695ccdc 100644 --- a/public/language/en-GB/admin/manage/uploads.json +++ b/public/language/en-GB/admin/manage/uploads.json @@ -5,5 +5,7 @@ "orphaned": "Orphaned", "size/filecount": "Size / Filecount", "confirm-delete": "Do you really want to delete this file?", - "filecount": "%1 files" + "filecount": "%1 files", + "new-folder": "New Folder", + "name-new-folder": "Enter a name for new the folder" } \ No newline at end of file diff --git a/public/language/en-GB/error.json b/public/language/en-GB/error.json index a6c5bfb8aa..1891190106 100644 --- a/public/language/en-GB/error.json +++ b/public/language/en-GB/error.json @@ -28,6 +28,8 @@ "invalid-event": "Invalid event: %1", "local-login-disabled": "Local login system has been disabled for non-privileged accounts.", "csrf-invalid": "We were unable to log you in, likely due to an expired session. Please try again", + "invalid-path": "Invalid path", + "folder-exists": "Folder exists", "invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2", diff --git a/public/openapi/write.yaml b/public/openapi/write.yaml index abad800516..8ac39fe982 100644 --- a/public/openapi/write.yaml +++ b/public/openapi/write.yaml @@ -143,4 +143,6 @@ paths: /admin/analytics/{set}: $ref: 'write/admin/analytics/set.yaml' /files/: - $ref: 'write/files.yaml' \ No newline at end of file + $ref: 'write/files.yaml' + /files/folder: + $ref: 'write/files/folder.yaml' \ No newline at end of file diff --git a/public/openapi/write/files/folder.yaml b/public/openapi/write/files/folder.yaml new file mode 100644 index 0000000000..84295a2917 --- /dev/null +++ b/public/openapi/write/files/folder.yaml @@ -0,0 +1,36 @@ +put: + tags: + - files + summary: create a new folder + description: This operation creates a new folder inside upload path + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + path: + type: string + description: Path to the file (relative to the configured `upload_path`) + example: /files + folderName: + type: string + description: New folder name + example: myfiles + required: + - path + - folderName + responses: + '200': + description: Folder created + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../components/schemas/Status.yaml#/Status + response: + type: object + properties: {} \ No newline at end of file diff --git a/public/src/admin/manage/uploads.js b/public/src/admin/manage/uploads.js index 253e5ac1af..6ed59d2f77 100644 --- a/public/src/admin/manage/uploads.js +++ b/public/src/admin/manage/uploads.js @@ -1,7 +1,6 @@ 'use strict'; - -define('admin/manage/uploads', ['uploader', 'api'], function (uploader, api) { +define('admin/manage/uploads', ['api', 'bootbox', 'uploader'], function (api, bootbox, uploader) { var Uploads = {}; Uploads.init = function () { @@ -29,6 +28,21 @@ define('admin/manage/uploads', ['uploader', 'api'], function (uploader, api) { }).catch(app.alertError); }); }); + + $('#new-folder').on('click', async function () { + bootbox.prompt('[[admin/manage/uploads:name-new-folder]]', (newFolderName) => { + if (!newFolderName || !newFolderName.trim()) { + return; + } + + api.put('/files/folder', { + path: ajaxify.data.currentFolder, + folderName: newFolderName, + }).then(() => { + ajaxify.refresh(); + }).catch(app.alertError); + }); + }); }; return Uploads; diff --git a/src/controllers/write/files.js b/src/controllers/write/files.js index 564424f5cd..61a6320094 100644 --- a/src/controllers/write/files.js +++ b/src/controllers/write/files.js @@ -9,3 +9,8 @@ Files.delete = async (req, res) => { await fs.unlink(res.locals.cleanedPath); helpers.formatApiResponse(200, res); }; + +Files.createFolder = async (req, res) => { + await fs.mkdir(res.locals.folderPath); + helpers.formatApiResponse(200, res); +}; diff --git a/src/middleware/assert.js b/src/middleware/assert.js index c92d8d2d13..49718cba14 100644 --- a/src/middleware/assert.js +++ b/src/middleware/assert.js @@ -14,6 +14,7 @@ const user = require('../user'); const groups = require('../groups'); const topics = require('../topics'); const posts = require('../posts'); +const slugify = require('../slugify'); const helpers = require('./helpers'); const controllerHelpers = require('../controllers/helpers'); @@ -86,3 +87,20 @@ Assert.path = helpers.try(async (req, res, next) => { next(); }); + +Assert.folderName = helpers.try(async (req, res, next) => { + const folderName = slugify(path.basename(req.body.folderName.trim())); + const folderPath = path.join(res.locals.cleanedPath, folderName); + + // slugify removes invalid characters, folderName may become empty + if (!folderName) { + return controllerHelpers.formatApiResponse(403, res, new Error('[[error:invalid-path]]')); + } + if (await file.exists(folderPath)) { + return controllerHelpers.formatApiResponse(403, res, new Error('[[error:folder-exists]]')); + } + + res.locals.folderPath = folderPath; + + next(); +}); diff --git a/src/routes/write/files.js b/src/routes/write/files.js index f0d2aab037..97ddde6d01 100644 --- a/src/routes/write/files.js +++ b/src/routes/write/files.js @@ -8,7 +8,7 @@ const routeHelpers = require('../helpers'); const { setupApiRoute } = routeHelpers; module.exports = function () { - const middlewares = [middleware.ensureLoggedIn]; + const middlewares = [middleware.ensureLoggedIn, middleware.admin.checkPrivileges]; // setupApiRoute(router, 'put', '/', [ // ...middlewares, @@ -21,5 +21,13 @@ module.exports = function () { middleware.assert.path, ], controllers.write.files.delete); + setupApiRoute(router, 'put', '/folder', [ + ...middlewares, + middleware.checkRequired.bind(null, ['path', 'folderName']), + middleware.assert.path, + // Should come after assert.path + middleware.assert.folderName, + ], controllers.write.files.createFolder); + return router; }; diff --git a/src/views/admin/manage/uploads.tpl b/src/views/admin/manage/uploads.tpl index bec935d1af..e478d2b023 100644 --- a/src/views/admin/manage/uploads.tpl +++ b/src/views/admin/manage/uploads.tpl @@ -1,6 +1,13 @@