ily.li/src/controllers/FileController.ts

140 lines
5.3 KiB
TypeScript

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<void> {
res.render('file-manager', {
files: await FileModel.paginate<FileModel>(req, 100),
max_upload_size: config.get<string>('max_upload_size'),
});
}
protected async downloadFile(req: Request, res: Response): Promise<void> {
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<void> {
await this.handleFileUpload(req.body.slug || await this.generateSlug(10), req, res);
}
protected async putFile(req: Request, res: Response): Promise<void> {
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<void> {
// 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<number>('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<void> {
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<string> {
let i = 0;
do {
const slug = cryptoRandomDictionary(config.get<number>('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<number>('max_upload_size') * 1024 * 1024,
},
}).single('upload');