96 lines
3.8 KiB
TypeScript
96 lines
3.8 KiB
TypeScript
import Controller from "wms-core/Controller";
|
|
import {REQUIRE_AUTH_MIDDLEWARE} from "wms-core/auth/AuthComponent";
|
|
import {Request, Response} from "express";
|
|
import {BadRequestError, NotFoundHttpError, ServerError} from "wms-core/HttpError";
|
|
import FileModel from "../models/FileModel";
|
|
import fileUpload from "express-fileupload";
|
|
import {cryptoRandomDictionary} from "wms-core/Utils";
|
|
import config from "config";
|
|
|
|
const FILE_UPLOAD_MIDDLEWARE = fileUpload({
|
|
tempFileDir: 'storage/tmp',
|
|
useTempFiles: true,
|
|
abortOnLimit: true,
|
|
createParentPath: true,
|
|
});
|
|
|
|
const SLUG_DICTIONARY = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
export default class FileController extends Controller {
|
|
routes(): void {
|
|
this.get('/f/:slug', this.downloadFile, 'get-file');
|
|
this.post('/f', this.postFile, 'post-file', REQUIRE_AUTH_MIDDLEWARE, FILE_UPLOAD_MIDDLEWARE);
|
|
this.put('/f/:slug', this.putFile, 'put-file', REQUIRE_AUTH_MIDDLEWARE, FILE_UPLOAD_MIDDLEWARE);
|
|
this.delete('/f/:slug', this.deleteFile, 'delete-file', REQUIRE_AUTH_MIDDLEWARE);
|
|
}
|
|
|
|
protected async downloadFile(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':
|
|
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(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> {
|
|
if (!req.files || !req.files.upload) {
|
|
throw new BadRequestError('No file received.', 'You must upload exactly one (1) file.', req.url);
|
|
}
|
|
|
|
let upload = req.files.upload;
|
|
if (Array.isArray(upload) && upload.length !== 1) {
|
|
throw new BadRequestError((req.files ? req.files.length : 0) + ' files received.', 'You must upload exactly one (1) file.', req.url);
|
|
}
|
|
if (Array.isArray(upload)) upload = upload[0];
|
|
|
|
const file = new FileModel({
|
|
slug: slug,
|
|
real_name: upload.name,
|
|
storage_type: 'local',
|
|
storage_path: 'storage/uploads/' + slug,
|
|
});
|
|
|
|
await file.save();
|
|
await upload.mv(file.storage_path);
|
|
|
|
res.format({
|
|
json: () => res.json({
|
|
url: file.getURL(),
|
|
}),
|
|
text: () => res.send(file.getURL()),
|
|
html: () => res.render('upload_success', {url: file.getURL()}),
|
|
});
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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.');
|
|
}
|
|
} |