import Controller from "wms-core/Controller"; import {REQUIRE_AUTH_MIDDLEWARE} from "wms-core/auth/AuthComponent"; import {Request, Response, Router} from "express"; import {BadRequestError, NotFoundHttpError, ServerError} from "wms-core/HttpError"; import FileModel from "../models/FileModel"; import {cryptoRandomDictionary} from "wms-core/Utils"; import config from "config"; import * as fs from "fs"; import multer from "multer"; const SLUG_DICTIONARY = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; export default class FileController extends Controller { setupRequestParsingMiddlewares(router: Router) { router.use('/', FILE_UPLOAD_MIDDLEWARE); router.use('/:slug', FILE_UPLOAD_MIDDLEWARE); } routes(): void { this.get('/files/:page?', this.getFileManager, 'file-manager', REQUIRE_AUTH_MIDDLEWARE); this.get('/files/delete/:slug', this.deleteFile, 'delete-file-frontend', REQUIRE_AUTH_MIDDLEWARE); this.get('/:slug', this.downloadFile, 'get-file'); this.post('/', this.postFile, 'post-file', REQUIRE_AUTH_MIDDLEWARE); this.put('/:slug', this.putFile, 'put-file', REQUIRE_AUTH_MIDDLEWARE); this.delete('/:slug', this.deleteFile, 'delete-file', REQUIRE_AUTH_MIDDLEWARE); } protected async getFileManager(req: Request, res: Response): Promise { res.render('file-manager', { files: await FileModel.paginate(req, 100), max_upload_size: config.get('max_upload_size'), }); } protected async downloadFile(req: Request, res: Response): Promise { const file = await FileModel.getBySlug(req.params.slug); if (!file || file.shouldBeDeleted()) throw new NotFoundHttpError('File', req.url); switch (file.storage_type) { case 'local': res.download(file.storage_path, file.real_name); break; default: throw new ServerError(`This file cannot be served. Download protocol for ${file.storage_type} storage type not implemented.`); } } protected async postFile(req: Request, res: Response): Promise { await this.handleFileUpload(req.body.slug || await this.generateSlug(10), req, res); } protected async putFile(req: Request, res: Response): Promise { const slug = req.params.slug; if (!slug) throw new BadRequestError('Cannot put without a slug.', 'Either provide a slug or use POST method instead.', req.url); await this.handleFileUpload(slug, req, res); } protected async handleFileUpload(slug: string, req: Request, res: Response): Promise { // Check for file upload if (!req.file) { throw new BadRequestError('No file received.', 'You must upload exactly one (1) file.', req.url); } let upload = req.file; // TTL let ttl = config.get('default_file_ttl'); if (req.body.never_expire !== undefined) ttl = 0; if (req.body.ttl !== undefined) ttl = parseInt(req.body.ttl); else if (req.body.expire_after_days !== undefined) ttl = parseInt(req.body.expire_after_days) * 24 * 3600; const file = new FileModel({ slug: slug, real_name: upload.originalname, storage_type: 'local', storage_path: 'storage/uploads/' + slug, ttl: ttl, }); await file.save(); fs.renameSync(upload.path, file.storage_path); res.format({ json: () => res.json({ url: file.getURL(), }), text: () => res.send(file.getURL()), html: () => { req.flash('success', 'Upload success!'); res.redirectBack('/'); }, }); } protected async deleteFile(req: Request, res: Response): Promise { const file = await FileModel.getBySlug(req.params.slug); if (!file) throw new NotFoundHttpError('File', req.url); switch (file.storage_type) { case 'local': await file.delete(); fs.unlinkSync(file.storage_path); break; default: throw new ServerError(`This file cannot be deleted. Deletion protocol for ${file.storage_type} storage type not implemented.`); } res.format({ json: () => res.json({ status: 'success', }), text: () => res.send('success'), html: () => { req.flash('success', 'Successfully deleted file.'); res.redirectBack('/'); }, }); } private async generateSlug(tries: number): Promise { let i = 0; do { const slug = cryptoRandomDictionary(config.get('newlyGeneratedSlugSize'), SLUG_DICTIONARY); if (!await FileModel.getBySlug(slug)) { return slug; } i++; } while (i < tries); throw new ServerError('Failed to generate slug; newly generated slug size should be increased by 1.'); } } const FILE_UPLOAD_MIDDLEWARE = multer({ dest: 'storage/tmp', limits: { fileSize: config.get('max_upload_size') * 1024 * 1024, }, }).single('upload');