From 196160dc1ca96d30cf9591347be5931421f970c0 Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Tue, 3 Mar 2020 18:35:57 +0100 Subject: [PATCH 1/4] Add allowedType in ctb and init getMediaTypes api Signed-off-by: Alexandre Bodin --- .../validation/__tests__/types.test.js | 70 +++++++++++++++++++ .../controllers/validation/types.js | 14 ++++ .../services/schema-builder/index.js | 11 +-- .../strapi-plugin-upload/config/config.json | 5 +- .../config/functions/bootstrap.js | 15 +++- .../strapi-plugin-upload/config/routes.json | 8 +++ .../controllers/Upload.js | 6 ++ packages/strapi-plugin-upload/package.json | 2 + yarn.lock | 38 +++++++++- 9 files changed, 153 insertions(+), 16 deletions(-) diff --git a/packages/strapi-plugin-content-type-builder/controllers/validation/__tests__/types.test.js b/packages/strapi-plugin-content-type-builder/controllers/validation/__tests__/types.test.js index e9470a9d7e..5a48d4171c 100644 --- a/packages/strapi-plugin-content-type-builder/controllers/validation/__tests__/types.test.js +++ b/packages/strapi-plugin-content-type-builder/controllers/validation/__tests__/types.test.js @@ -238,4 +238,74 @@ describe('Type validators', () => { expect(validator.isValidSync(attributes.slug)).toBe(false); }); }); + + describe('media type', () => { + test('Validates allowedTypes', () => { + const attributes = { + img: { + type: 'media', + allowedTypes: ['nonexistent'], + }, + }; + + const validator = getTypeValidator(attributes.img, { + types: ['media'], + modelType: 'collectionType', + attributes, + }); + + expect(validator.isValidSync(attributes.img)).toBe(false); + }); + + test('Cannot set all with other allowedTypes', () => { + const attributes = { + img: { + type: 'media', + allowedTypes: ['all', 'videos'], + }, + }; + + const validator = getTypeValidator(attributes.img, { + types: ['media'], + modelType: 'collectionType', + attributes, + }); + + expect(validator.isValidSync(attributes.img)).toBe(false); + }); + + test('Can set multiple allowedTypes', () => { + const attributes = { + img: { + type: 'media', + allowedTypes: ['files', 'videos'], + }, + }; + + const validator = getTypeValidator(attributes.img, { + types: ['media'], + modelType: 'collectionType', + attributes, + }); + + expect(validator.isValidSync(attributes.img)).toBe(true); + }); + + test.each(['all', 'images', 'files', 'videos'])('%s is an allowed types', type => { + const attributes = { + img: { + type: 'media', + allowedTypes: [type], + }, + }; + + const validator = getTypeValidator(attributes.img, { + types: ['media'], + modelType: 'collectionType', + attributes, + }); + + expect(validator.isValidSync(attributes.img)).toBe(true); + }); + }); }); diff --git a/packages/strapi-plugin-content-type-builder/controllers/validation/types.js b/packages/strapi-plugin-content-type-builder/controllers/validation/types.js index b895796305..e34c8f5572 100644 --- a/packages/strapi-plugin-content-type-builder/controllers/validation/types.js +++ b/packages/strapi-plugin-content-type-builder/controllers/validation/types.js @@ -49,6 +49,20 @@ const getTypeShape = (attribute, { modelType, attributes } = {}) => { multiple: yup.boolean(), required: validators.required, unique: validators.unique, + allowedTypes: yup.lazy(value => { + if (Array.isArray(value) && value.includes('all')) { + return yup + .array() + .of(yup.string().oneOf(['all'])) + .min(1) + .max(1); + } + + return yup + .array() + .of(yup.string().oneOf(['images', 'videos', 'files'])) + .min(1); + }), }; } diff --git a/packages/strapi-plugin-content-type-builder/services/schema-builder/index.js b/packages/strapi-plugin-content-type-builder/services/schema-builder/index.js index d04817708d..1b8eedb306 100644 --- a/packages/strapi-plugin-content-type-builder/services/schema-builder/index.js +++ b/packages/strapi-plugin-content-type-builder/services/schema-builder/index.js @@ -60,18 +60,12 @@ function createSchemaBuilder({ components, contentTypes }) { // init temporary ContentTypes Object.keys(contentTypes).forEach(key => { - tmpContentTypes.set( - contentTypes[key].uid, - createSchemaHandler(contentTypes[key]) - ); + tmpContentTypes.set(contentTypes[key].uid, createSchemaHandler(contentTypes[key])); }); // init temporary components Object.keys(components).forEach(key => { - tmpComponents.set( - components[key].uid, - createSchemaHandler(components[key]) - ); + tmpComponents.set(components[key].uid, createSchemaHandler(components[key])); }); return { @@ -97,6 +91,7 @@ function createSchemaBuilder({ components, contentTypes }) { acc[key] = { [attribute.multiple ? 'collection' : 'model']: 'file', via, + allowedTypes: attribute.allowedTypes, plugin: 'upload', required: attribute.required ? true : false, configurable: configurable === false ? false : undefined, diff --git a/packages/strapi-plugin-upload/config/config.json b/packages/strapi-plugin-upload/config/config.json index b41440d212..79e49e372a 100644 --- a/packages/strapi-plugin-upload/config/config.json +++ b/packages/strapi-plugin-upload/config/config.json @@ -2,7 +2,6 @@ "enabled": true, "provider": "local", "providerOptions": { - "sizeLimit": 1000 - }, - "providers": [] + "sizeLimit": 1000000 + } } diff --git a/packages/strapi-plugin-upload/config/functions/bootstrap.js b/packages/strapi-plugin-upload/config/functions/bootstrap.js index 5990c641f8..e9231b02a9 100644 --- a/packages/strapi-plugin-upload/config/functions/bootstrap.js +++ b/packages/strapi-plugin-upload/config/functions/bootstrap.js @@ -1,5 +1,9 @@ 'use strict'; +const db = require('mime-db'); +const mime = require('mime-type')(db); +const _ = require('lodash'); + /** * An asynchronous bootstrap function that runs before * your application gets started. @@ -16,9 +20,14 @@ module.exports = async () => { key: 'settings', }); - strapi.plugins.upload.provider = createProvider( - strapi.plugins.upload.config || {} - ); + _.defaults(strapi.plugins.upload.config, { + mediaTypes: { + images: mime.glob('image/*'), + videos: mime.glob('video/*'), + }, + }); + + strapi.plugins.upload.provider = createProvider(strapi.plugins.upload.config || {}); // if provider config does not exist set one by default const config = await configurator.get(); diff --git a/packages/strapi-plugin-upload/config/routes.json b/packages/strapi-plugin-upload/config/routes.json index 07af8d59e1..023964ebc2 100644 --- a/packages/strapi-plugin-upload/config/routes.json +++ b/packages/strapi-plugin-upload/config/routes.json @@ -29,6 +29,14 @@ "policies": [] } }, + { + "method": "GET", + "path": "/media-types", + "handler": "Upload.getMediaTypes", + "config": { + "policies": [] + } + }, { "method": "GET", "path": "/files/count", diff --git a/packages/strapi-plugin-upload/controllers/Upload.js b/packages/strapi-plugin-upload/controllers/Upload.js index d0f5eff68c..ed17864027 100644 --- a/packages/strapi-plugin-upload/controllers/Upload.js +++ b/packages/strapi-plugin-upload/controllers/Upload.js @@ -106,6 +106,12 @@ module.exports = { ctx.body = { data }; }, + async getMediaTypes(ctx) { + const data = _.get(strapi.plugins.upload, 'config.mediaTypes'); + + ctx.body = { data }; + }, + async find(ctx) { const data = await strapi.plugins['upload'].services.upload.fetchAll(ctx.query); diff --git a/packages/strapi-plugin-upload/package.json b/packages/strapi-plugin-upload/package.json index 995e78e614..b1cdc8d886 100644 --- a/packages/strapi-plugin-upload/package.json +++ b/packages/strapi-plugin-upload/package.json @@ -16,6 +16,8 @@ "immutable": "^3.8.2", "invariant": "^2.2.1", "lodash": "^4.17.11", + "mime-db": "1.43.0", + "mime-type": "3.0.7", "react": "^16.9.0", "react-copy-to-clipboard": "^5.0.1", "react-dom": "^16.9.0", diff --git a/yarn.lock b/yarn.lock index cea61e6800..1806832afe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6945,7 +6945,7 @@ escape-string-regexp@1.0.2: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1" integrity sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE= -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -9218,6 +9218,13 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" +inherits-ex@^1.1.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/inherits-ex/-/inherits-ex-1.2.3.tgz#ba0da6258e9b855f98429e82ea97c4cc0e4e459d" + integrity sha512-DCZqD7BpjXqaha8IKcoAE3ZZr6Hi12ropV1h+3pBnirE14mNRwLuYySvYxUSBemTQ40SjAxPL8BTk2Xw/3IF9w== + dependencies: + xtend "^4.0.0" + inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" @@ -11618,7 +11625,7 @@ mdn-data@2.0.4: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== -media-typer@0.3.0: +media-typer@0.3.0, media-typer@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= @@ -11774,6 +11781,16 @@ mime-db@1.43.0, "mime-db@>= 1.43.0 < 2": resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== +mime-type@3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/mime-type/-/mime-type-3.0.7.tgz#15c21e4833edd1ac5ddf04fffb49164d17bef57b" + integrity sha512-NyWtbAKERuLQIv+1jjEdWGrWepVlubZEW0fTs4K9T6UWW45iMBpgrwpP5GIl8/5trHLviOcQfA6zEth3T8WhNA== + dependencies: + media-typer "^0.3.0" + minimatch "^3.0.4" + path.js "^1.0.7" + util-ex "^0.3.15" + mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.26" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" @@ -13293,6 +13310,15 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +path.js@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path.js/-/path.js-1.0.7.tgz#7d136b607de19bfd98ba068874926287e6534939" + integrity sha1-fRNrYH3hm/2YugaIdJJih+ZTSTk= + dependencies: + escape-string-regexp "^1.0.3" + inherits-ex "^1.1.2" + util-ex "^0.3.10" + pbkdf2@^3.0.3: version "3.0.17" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" @@ -17729,6 +17755,14 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= +util-ex@^0.3.10, util-ex@^0.3.15: + version "0.3.15" + resolved "https://registry.yarnpkg.com/util-ex/-/util-ex-0.3.15.tgz#f9261cda13c4327d0740cbe67be1227ded8b0058" + integrity sha1-+SYc2hPEMn0HQMvme+Eife2LAFg= + dependencies: + inherits-ex "^1.1.2" + xtend "^4.0.0" + util-promisify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/util-promisify/-/util-promisify-2.1.0.tgz#3c2236476c4d32c5ff3c47002add7c13b9a82a53" From b084cb1c65ad450c10bb6faba50ebb500be7db16 Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Tue, 3 Mar 2020 18:41:09 +0100 Subject: [PATCH 2/4] Return type list and mimeTypes for filters Signed-off-by: Alexandre Bodin --- .../config/functions/bootstrap.js | 11 ----------- .../strapi-plugin-upload/config/routes.json | 16 ++++++++-------- .../strapi-plugin-upload/controllers/Upload.js | 6 ------ .../controllers/media-types.js | 18 ++++++++++++++++++ 4 files changed, 26 insertions(+), 25 deletions(-) create mode 100644 packages/strapi-plugin-upload/controllers/media-types.js diff --git a/packages/strapi-plugin-upload/config/functions/bootstrap.js b/packages/strapi-plugin-upload/config/functions/bootstrap.js index e9231b02a9..b41630a1a6 100644 --- a/packages/strapi-plugin-upload/config/functions/bootstrap.js +++ b/packages/strapi-plugin-upload/config/functions/bootstrap.js @@ -1,9 +1,5 @@ 'use strict'; -const db = require('mime-db'); -const mime = require('mime-type')(db); -const _ = require('lodash'); - /** * An asynchronous bootstrap function that runs before * your application gets started. @@ -20,13 +16,6 @@ module.exports = async () => { key: 'settings', }); - _.defaults(strapi.plugins.upload.config, { - mediaTypes: { - images: mime.glob('image/*'), - videos: mime.glob('video/*'), - }, - }); - strapi.plugins.upload.provider = createProvider(strapi.plugins.upload.config || {}); // if provider config does not exist set one by default diff --git a/packages/strapi-plugin-upload/config/routes.json b/packages/strapi-plugin-upload/config/routes.json index 023964ebc2..a23e3578d4 100644 --- a/packages/strapi-plugin-upload/config/routes.json +++ b/packages/strapi-plugin-upload/config/routes.json @@ -29,14 +29,6 @@ "policies": [] } }, - { - "method": "GET", - "path": "/media-types", - "handler": "Upload.getMediaTypes", - "config": { - "policies": [] - } - }, { "method": "GET", "path": "/files/count", @@ -101,6 +93,14 @@ "name": "File" } } + }, + { + "method": "GET", + "path": "/media-types", + "handler": "media-types.getMediaTypes", + "config": { + "policies": [] + } } ] } diff --git a/packages/strapi-plugin-upload/controllers/Upload.js b/packages/strapi-plugin-upload/controllers/Upload.js index ed17864027..d0f5eff68c 100644 --- a/packages/strapi-plugin-upload/controllers/Upload.js +++ b/packages/strapi-plugin-upload/controllers/Upload.js @@ -106,12 +106,6 @@ module.exports = { ctx.body = { data }; }, - async getMediaTypes(ctx) { - const data = _.get(strapi.plugins.upload, 'config.mediaTypes'); - - ctx.body = { data }; - }, - async find(ctx) { const data = await strapi.plugins['upload'].services.upload.fetchAll(ctx.query); diff --git a/packages/strapi-plugin-upload/controllers/media-types.js b/packages/strapi-plugin-upload/controllers/media-types.js new file mode 100644 index 0000000000..619c818d37 --- /dev/null +++ b/packages/strapi-plugin-upload/controllers/media-types.js @@ -0,0 +1,18 @@ +'use strict'; + +const db = require('mime-db'); +const mime = require('mime-type')(db); + +module.exports = { + async getMediaTypes(ctx) { + const data = { + types: ['images', 'videos', 'files'], + mimeTypes: { + images: mime.glob('image/*'), + videos: mime.glob('video/*'), + }, + }; + + ctx.body = { data }; + }, +}; From 7cd34b725abd708cd55d59aaa8ed644347260ee9 Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Wed, 4 Mar 2020 10:31:32 +0100 Subject: [PATCH 3/4] wip Signed-off-by: Alexandre Bodin --- .../strapi-plugin-upload/config/routes.json | 35 ++++++++++------- .../controllers/Upload.js | 38 +++++++++++++++++++ .../strapi-plugin-upload/services/Upload.js | 29 ++++++++++++++ 3 files changed, 89 insertions(+), 13 deletions(-) diff --git a/packages/strapi-plugin-upload/config/routes.json b/packages/strapi-plugin-upload/config/routes.json index a23e3578d4..d47777ce26 100644 --- a/packages/strapi-plugin-upload/config/routes.json +++ b/packages/strapi-plugin-upload/config/routes.json @@ -1,18 +1,5 @@ { "routes": [ - { - "method": "POST", - "path": "/", - "handler": "Upload.upload", - "config": { - "policies": [], - "description": "Upload a file", - "tag": { - "plugin": "upload", - "name": "File" - } - } - }, { "method": "GET", "path": "/settings", @@ -29,6 +16,28 @@ "policies": [] } }, + { + "method": "POST", + "path": "/", + "handler": "Upload.upload", + "config": { + "policies": [], + "description": "Upload a file", + "tag": { + "plugin": "upload", + "name": "File" + } + } + }, + { + "method": "POST", + "path": "/files/replace/:id", + "handler": "Upload.replaceFile", + "config": { + "policies": [], + "description": "Replace a file" + } + }, { "method": "GET", "path": "/files/count", diff --git a/packages/strapi-plugin-upload/controllers/Upload.js b/packages/strapi-plugin-upload/controllers/Upload.js index d0f5eff68c..0c004d5115 100644 --- a/packages/strapi-plugin-upload/controllers/Upload.js +++ b/packages/strapi-plugin-upload/controllers/Upload.js @@ -106,6 +106,44 @@ module.exports = { ctx.body = { data }; }, + async replaceFile(ctx) { + const { id } = ctx.params; + + const uploadService = strapi.plugins.upload.services.upload; + + // Retrieve provider configuration. + const { enabled } = strapi.plugins.upload.config; + + // Verify if the file upload is enable. + if (enabled === false) { + throw strapi.errors.badRequest(null, { + errors: [{ id: 'Upload.status.disabled', message: 'File upload is disabled' }], + }); + } + + const data = await strapi.plugins['upload'].services.upload.fetch({ id }); + + if (!data) { + return ctx.notFound('file.notFound'); + } + + const { fileInfo } = await validateUploadBody(uploadSchema, ctx.request.body); + + const { file = {} } = ctx.request.files || {}; + + if (_.isUndefined(file)) { + throw strapi.errors.badRequest(null, { + errors: [{ id: 'Upload.status.empty', message: 'File is missing' }], + }); + } + + const enhancedFile = uploadService.enhanceFile(file, fileInfo); + + const updatedFile = await uploadService.update(id, enhancedFile); + + ctx.send(updatedFile); + }, + async find(ctx) { const data = await strapi.plugins['upload'].services.upload.fetchAll(ctx.query); diff --git a/packages/strapi-plugin-upload/services/Upload.js b/packages/strapi-plugin-upload/services/Upload.js index 67e34c3769..0a65b41438 100644 --- a/packages/strapi-plugin-upload/services/Upload.js +++ b/packages/strapi-plugin-upload/services/Upload.js @@ -99,6 +99,35 @@ module.exports = { return Promise.all(files.map(file => uploadFile(file))); }, + async replace(dbFile, file) { + const config = strapi.plugins.upload.config; + + // keep a constant hash + _.assign(file, { + hash: dbFile.hash, + ext: dbFile.ext, + }); + + // execute delete function of the provider + if (dbFile.provider === config.provider) { + await strapi.plugins.upload.provider.delete(dbFile); + } + + await strapi.plugins.upload.provider.upload(file); + + delete file.buffer; + file.provider = config.provider; + + const res = await this.update({ id: dbFile.id }, {}); + strapi.eventHub.emit('media.update', { media: res }); + + return res; + }, + + update(id, values) { + return strapi.query('file', 'upload').update({ id }, values); + }, + add(values) { return strapi.query('file', 'upload').create(values); }, From c722b0db00b7f87404edc6ef06c1e68f79c5cf06 Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Thu, 5 Mar 2020 13:51:15 +0100 Subject: [PATCH 4/4] Implement file replace Signed-off-by: Alexandre Bodin --- .../strapi-plugin-upload/config/routes.json | 9 --- .../config/schema.graphql.js | 13 ++-- .../controllers/Upload.js | 71 +++++++++---------- .../strapi-plugin-upload/services/Upload.js | 71 ++++++++++++------- 4 files changed, 86 insertions(+), 78 deletions(-) diff --git a/packages/strapi-plugin-upload/config/routes.json b/packages/strapi-plugin-upload/config/routes.json index d47777ce26..4cd62678bd 100644 --- a/packages/strapi-plugin-upload/config/routes.json +++ b/packages/strapi-plugin-upload/config/routes.json @@ -29,15 +29,6 @@ } } }, - { - "method": "POST", - "path": "/files/replace/:id", - "handler": "Upload.replaceFile", - "config": { - "policies": [], - "description": "Replace a file" - } - }, { "method": "GET", "path": "/files/count", diff --git a/packages/strapi-plugin-upload/config/schema.graphql.js b/packages/strapi-plugin-upload/config/schema.graphql.js index 0f3216d484..279d1c033f 100644 --- a/packages/strapi-plugin-upload/config/schema.graphql.js +++ b/packages/strapi-plugin-upload/config/schema.graphql.js @@ -23,7 +23,9 @@ module.exports = { resolver: async (obj, { file: upload, ...fields }) => { const file = await formatFile(upload, fields); - const uploadedFiles = await strapi.plugins.upload.services.upload.upload([file]); + const uploadedFiles = await strapi.plugins.upload.services.upload.uploadFileAndPersist( + file + ); // Return response. return uploadedFiles.length === 1 ? uploadedFiles[0] : uploadedFiles; @@ -35,10 +37,9 @@ module.exports = { resolver: async (obj, { files: uploads, ...fields }) => { const files = await Promise.all(uploads.map(upload => formatFile(upload, fields))); - const uploadedFiles = await strapi.plugins.upload.services.upload.upload(files); + const uploadService = strapi.plugins.upload.services.upload; - // Return response. - return uploadedFiles; + return Promise.all(files.map(file => uploadService.uploadFileAndPersist(file))); }, }, }, @@ -55,8 +56,8 @@ const formatFile = async (upload, metas) => { const buffer = Buffer.concat(buffers); - const { formatFileInfo } = strapi.plugins.upload.services.upload; - const fileInfo = formatFileInfo( + const uploadService = strapi.plugins.upload.services.upload; + const fileInfo = uploadService.formatFileInfo( { filename, type: mimetype, diff --git a/packages/strapi-plugin-upload/controllers/Upload.js b/packages/strapi-plugin-upload/controllers/Upload.js index 0c004d5115..dda40d633f 100644 --- a/packages/strapi-plugin-upload/controllers/Upload.js +++ b/packages/strapi-plugin-upload/controllers/Upload.js @@ -29,53 +29,50 @@ const validateUploadBody = (schema, data = {}) => { }); }; +const isUploadDisabled = () => _.get(strapi.plugins, 'upload.config.enabled', true) === false; + +const disabledPluginError = () => + strapi.errors.badRequest(null, { + errors: [{ id: 'Upload.status.disabled', message: 'File upload is disabled' }], + }); + +const emptyFileError = () => + strapi.errors.badRequest(null, { + errors: [{ id: 'Upload.status.empty', message: 'Files are empty' }], + }); + module.exports = { async upload(ctx) { - const uploadService = strapi.plugins.upload.services.upload; - - // Retrieve provider configuration. - const { enabled } = strapi.plugins.upload.config; - - // Verify if the file upload is enable. - if (enabled === false) { - throw strapi.errors.badRequest(null, { - errors: [{ id: 'Upload.status.disabled', message: 'File upload is disabled' }], - }); + if (isUploadDisabled()) { + throw disabledPluginError(); } const files = _.get(ctx.request.files, 'files'); - if (_.isEmpty(files)) { - throw strapi.errors.badRequest(null, { - errors: [{ id: 'Upload.status.empty', message: 'Files are empty' }], - }); + throw emptyFileError(); } - let data; - if (Array.isArray(files)) { - data = await validateUploadBody(multiUploadSchema, ctx.request.body); + const { id } = ctx.query; + + const uploadService = strapi.plugins.upload.services.upload; + + const validationSchema = Array.isArray(files) ? multiUploadSchema : uploadSchema; + const data = await validateUploadBody(validationSchema, ctx.request.body); + + if (id) { + // cannot replace with more than one file + if (Array.isArray(files)) { + throw strapi.errors.badRequest(null, { + errors: [ + { id: 'Upload.replace.single', message: 'Cannot replace a file with multiple ones' }, + ], + }); + } + + ctx.body = await uploadService.replace(id, { data, file: files }); } else { - data = await validateUploadBody(uploadSchema, ctx.request.body); + ctx.body = await uploadService.upload({ data, files }); } - - const { refId, ref, source, field, path, fileInfo } = data; - - const fileArray = Array.isArray(files) ? files : [files]; - const fileInfoArray = Array.isArray(fileInfo) ? fileInfo : [fileInfo]; - - // Transform stream files to buffer - const enhancedFiles = await Promise.all( - fileArray.map((file, idx) => { - const fileInfo = fileInfoArray[idx] || {}; - - return uploadService.enhanceFile(file, fileInfo, { refId, ref, source, field, path }); - }) - ); - - const uploadedFiles = await uploadService.upload(enhancedFiles); - - // Send 200 `ok` - ctx.send(uploadedFiles); }, async getSettings(ctx) { diff --git a/packages/strapi-plugin-upload/services/Upload.js b/packages/strapi-plugin-upload/services/Upload.js index 0a65b41438..30563e2feb 100644 --- a/packages/strapi-plugin-upload/services/Upload.js +++ b/packages/strapi-plugin-upload/services/Upload.js @@ -58,7 +58,7 @@ module.exports = { return entity; }, - async enhanceFile(file, fileInfo, metas) { + async enhanceFile(file, fileInfo = {}, metas = {}) { const parts = await toArray(fs.createReadStream(file.path)); const buffers = parts.map(part => (_.isBuffer(part) ? part : Buffer.from(part))); @@ -79,31 +79,52 @@ module.exports = { }); }, - async upload(files) { - const config = strapi.plugins.upload.config; + async upload({ data, files }) { + const { fileInfo, ...metas } = data; - // upload a single file - const uploadFile = async file => { - await strapi.plugins.upload.provider.upload(file); + const fileArray = Array.isArray(files) ? files : [files]; + const fileInfoArray = Array.isArray(fileInfo) ? fileInfo : [fileInfo]; - delete file.buffer; - file.provider = config.provider; + const doUpload = async (file, fileInfo) => { + const fileData = await this.enhanceFile(file, fileInfo, metas); - const res = await this.add(file); - - strapi.eventHub.emit('media.create', { media: res }); - return res; + return this.uploadFileAndPersist(fileData); }; - // Execute upload function of the provider for all files. - return Promise.all(files.map(file => uploadFile(file))); + return await Promise.all( + fileArray.map((file, idx) => doUpload(file, fileInfoArray[idx] || {})) + ); }, - async replace(dbFile, file) { + async uploadFileAndPersist(fileData) { + const config = strapi.plugins.upload.config; + await strapi.plugins.upload.provider.upload(fileData); + + delete fileData.buffer; + fileData.provider = config.provider; + + const res = await this.add(fileData); + + strapi.eventHub.emit('media.create', { media: res }); + return res; + }, + + async replace(id, { data, file }) { const config = strapi.plugins.upload.config; + const dbFile = await this.fetch({ id }); + + if (!dbFile) { + throw new Error('file not found'); + } + + const { fileInfo } = data; + const fileData = await this.enhanceFile(file, fileInfo); + + // TODO: maybe check if same extension ?? + // keep a constant hash - _.assign(file, { + _.assign(fileData, { hash: dbFile.hash, ext: dbFile.ext, }); @@ -113,19 +134,19 @@ module.exports = { await strapi.plugins.upload.provider.delete(dbFile); } - await strapi.plugins.upload.provider.upload(file); + await strapi.plugins.upload.provider.upload(fileData); - delete file.buffer; - file.provider = config.provider; + delete fileData.buffer; + fileData.provider = config.provider; - const res = await this.update({ id: dbFile.id }, {}); + const res = await this.update({ id }, fileData); strapi.eventHub.emit('media.update', { media: res }); return res; }, - update(id, values) { - return strapi.query('file', 'upload').update({ id }, values); + update(params, values) { + return strapi.query('file', 'upload').update(params, values); }, add(values) { @@ -133,9 +154,7 @@ module.exports = { }, fetch(params) { - return strapi.query('file', 'upload').findOne({ - id: params.id, - }); + return strapi.query('file', 'upload').findOne(params); }, fetchAll(params) { @@ -180,7 +199,7 @@ module.exports = { } ); }) - ).then(files => this.upload(files)); + ).then(files => this.uploadFileAndPersist(files)); }, async getConfig() {