diff --git a/.gitignore b/.gitignore index b654c77..d9bc62b 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,4 @@ typings/ # Uploads /public/uploads +/public/uploads_test diff --git a/config/development.json b/config/development.json index 62c4ac6..47d561b 100644 --- a/config/development.json +++ b/config/development.json @@ -1,4 +1,5 @@ { "port": 3000, - "database": ".db" + "database": ".db", + "uploads": "public/uploads" } diff --git a/config/index.js b/config/index.js index 42e364b..4cb262f 100644 --- a/config/index.js +++ b/config/index.js @@ -15,7 +15,8 @@ if (fs.existsSync(path.resolve(__dirname, configPath))) { } else { config = { database: '.db', - port: 3000 + port: 3000, + uploads: 'public/uploads' }; } diff --git a/config/production.json b/config/production.json index 62c4ac6..47d561b 100644 --- a/config/production.json +++ b/config/production.json @@ -1,4 +1,5 @@ { "port": 3000, - "database": ".db" + "database": ".db", + "uploads": "public/uploads" } diff --git a/config/testing.json b/config/testing.json index 25957e0..8fe25d0 100644 --- a/config/testing.json +++ b/config/testing.json @@ -1,5 +1,6 @@ { "port": 3001, "database": ".testdb", - "rcFile": "./test/.codexdocsrc" + "rcFile": "./test/.codexdocsrc", + "uploads": "public/uploads_test" } diff --git a/package.json b/package.json index 274bf59..f9c8def 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "express": "~4.16.0", "file-type": "^10.7.1", "http-errors": "~1.7.1", + "mime": "^2.4.0", "module-dispatcher": "^2.0.0", "morgan": "~1.9.0", "multer": "^1.3.1", @@ -46,8 +47,8 @@ "babel-loader": "^8.0.2", "chai": "^4.1.2", "chai-http": "^4.0.0", - "codex.editor.image": "^2.0.3", "codex.editor.code": "^2.3.1", + "codex.editor.image": "^2.0.3", "codex.editor.inline-code": "^1.2.0", "codex.editor.list": "^1.2.3", "codex.editor.marker": "^1.0.1", @@ -79,6 +80,7 @@ "postcss-nested-ancestors": "^2.0.0", "postcss-nesting": "^7.0.0", "postcss-smart-import": "^0.7.6", + "rimraf": "^2.6.3", "sinon": "^7.0.0", "webpack": "^4.17.1", "webpack-cli": "^3.1.0" diff --git a/src/app.js b/src/app.js index a444690..8ef3210 100644 --- a/src/app.js +++ b/src/app.js @@ -4,8 +4,6 @@ const path = require('path'); const cookieParser = require('cookie-parser'); const logger = require('morgan'); const rcParser = require('./utils/rcparser'); -const FileModel = require('./models/file'); - const routes = require('./routes'); const app = express(); @@ -22,19 +20,7 @@ app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); -app.use(express.static( - path.join(__dirname, '../public'), - { - setHeaders: async (res, pathToFile) => { - const filename = path.basename(pathToFile); - const file = await FileModel.getByFilename(filename); - - if (file._id && file.mimetype) { - res.setHeader('content-type', file.mimetype); - } - } - } -)); +app.use(express.static(path.join(__dirname, '../public'))); app.use('/', routes); // catch 404 and forward to error handler diff --git a/src/controllers/transport.js b/src/controllers/transport.js index 7d6a359..42c204c 100644 --- a/src/controllers/transport.js +++ b/src/controllers/transport.js @@ -1,10 +1,12 @@ const fileType = require('file-type'); const fetch = require('node-fetch'); const fs = require('fs'); +const nodePath = require('path'); const Model = require('../models/file'); const { random16 } = require('../utils/crypto'); const { deepMerge } = require('../utils/objects'); +const config = require('../../config'); /** * @class Transport @@ -26,7 +28,7 @@ class Transport { * @return {Promise} */ static async save(multerData, map) { - const { originalname: name, filename, path, size, mimetype } = multerData; + const { originalname: name, path, filename, size, mimetype } = multerData; const file = new Model({ name, filename, path, size, mimetype }); @@ -52,14 +54,15 @@ class Transport { const buffer = await fetchedFile.buffer(); const filename = await random16(); - fs.writeFileSync(`public/uploads/${filename}`, buffer); - const type = fileType(buffer); + const ext = type ? type.ext : nodePath.extname(url).slice(1); + + fs.writeFileSync(`${config.uploads}/${filename}.${ext}`, buffer); const file = new Model({ name: url, - filename, - path: `/uploads/${filename}`, + filename: `${filename}.${ext}`, + path: `${config.uploads}/${filename}.${ext}`, size: buffer.length, mimetype: type ? type.mime : fetchedFile.headers.get('content-type') }); diff --git a/src/routes/api/transport.js b/src/routes/api/transport.js index 156a072..ec17f4b 100644 --- a/src/routes/api/transport.js +++ b/src/routes/api/transport.js @@ -1,13 +1,31 @@ const express = require('express'); const router = express.Router(); const multer = require('multer'); +const mime = require('mime') const Transport = require('../../controllers/transport'); +const { random16 } = require('../../utils/crypto'); +const config = require('../../../config'); + +/** + * Multer storage for uploaded files and images + * @type {DiskStorage|DiskStorage} + */ +const storage = multer.diskStorage({ + destination: (req, file, cb) => { + cb(null, config.uploads || 'public/uploads'); + }, + filename: async (req, file, cb) => { + const filename = await random16(); + + cb(null, `${filename}.${mime.getExtension(file.mimetype)}`); + } +}) /** * Multer middleware for image uploading */ const imageUploader = multer({ - dest: 'public/uploads/', + storage, fileFilter: (req, file, cb) => { if (!/image/.test(file.mimetype)) { cb(null, false); @@ -22,7 +40,7 @@ const imageUploader = multer({ * Multer middleware for file uploading */ const fileUploader = multer({ - dest: 'public/uploads/' + storage }).fields([ { name: 'file', maxCount: 1 } ]); /** diff --git a/test/rest/transport.js b/test/rest/transport.js index 9dac60f..da375f0 100644 --- a/test/rest/transport.js +++ b/test/rest/transport.js @@ -3,6 +3,7 @@ const path = require('path'); const fileType = require('file-type'); const chai = require('chai'); const chaiHTTP = require('chai-http'); +const rimraf = require('rimraf'); const {expect} = chai; const {app} = require('../../bin/www'); @@ -17,6 +18,10 @@ describe('Transport routes: ', () => { before(async () => { agent = chai.request.agent(app); + + if (!fs.existsSync('./' + config.uploads)) { + fs.mkdirSync('./' + config.uploads); + } }); after(async () => { @@ -25,6 +30,10 @@ describe('Transport routes: ', () => { if (fs.existsSync(pathToDB)) { fs.unlinkSync(pathToDB); } + + if (fs.existsSync('./' + config.uploads)) { + rimraf.sync('./' + config.uploads); + } }); it('Uploading an image', async () => { @@ -102,7 +111,7 @@ describe('Transport routes: ', () => { .get(file.path); expect(getRes).to.have.status(200); - expect(getRes).to.have.header('content-type', file.mimetype); + expect(getRes).to.have.header('content-type', new RegExp(`^${file.mimetype}`)); }); it('Uploading a file with map option', async () => { diff --git a/yarn.lock b/yarn.lock index 75fbd3b..9faded4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3730,6 +3730,11 @@ mime@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" +mime@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.0.tgz#e051fd881358585f3279df333fe694da0bcffdd6" + integrity sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w== + mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" @@ -5293,7 +5298,7 @@ rgba-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" -rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@~2.6.2: +rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@~2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" dependencies: