172 lines
6.5 KiB
TypeScript
172 lines
6.5 KiB
TypeScript
import config from "config";
|
|
import {NextFunction, Request, Response} from "express";
|
|
import formidable from "formidable";
|
|
import * as fs from "fs";
|
|
import path from "path";
|
|
import {RequireAuthMiddleware, RequireRequestAuthMiddleware} from "swaf/auth/AuthComponent";
|
|
import {route} from "swaf/common/Routing";
|
|
import Controller from "swaf/Controller";
|
|
import FileUploadMiddleware from "swaf/FileUploadMiddleware";
|
|
import {BadRequestError, ForbiddenHttpError, ServerError} from "swaf/HttpError";
|
|
import {logger} from "swaf/Logger";
|
|
|
|
import FileModel from "../models/FileModel.js";
|
|
import generateSlug from "../SlugGenerator.js";
|
|
|
|
|
|
export default class FileController extends Controller {
|
|
public routes(): void {
|
|
this.get('/files/:page([0-9]+)?', this.getFileUploader, 'file-uploader',
|
|
RequireAuthMiddleware);
|
|
this.get('/files/upload-script', this.downloadLinuxScript, 'file-linux-script');
|
|
this.post('/files/upload', this.postFileFrontend, 'post-file-frontend',
|
|
RequireAuthMiddleware, FileUploadFormMiddleware);
|
|
this.post('/files/delete/:slug', FileController.deleteFileRoute, 'delete-file-frontend',
|
|
RequireAuthMiddleware);
|
|
}
|
|
|
|
protected async getFileUploader(req: Request, res: Response): Promise<void> {
|
|
const allowedDomains = config.get<string[]>('allowed_url_domains');
|
|
const user = req.as(RequireAuthMiddleware).getUser();
|
|
const files = await FileModel.paginateForUser(req, 25, user.getOrFail('id'));
|
|
res.render('file-uploader', {
|
|
max_upload_size: config.get<string>('max_upload_size'),
|
|
allowed_domains: allowedDomains,
|
|
default_domain: allowedDomains[config.get<number>('default_url_domain_for_files')],
|
|
files: files.map(file => ({
|
|
...file,
|
|
shouldBeDeleted: file.shouldBeDeleted(),
|
|
url: file.getURL(),
|
|
expires_at: file.expires_at?.toISOString(),
|
|
})),
|
|
pagination: files.pagination?.serialize(),
|
|
});
|
|
}
|
|
|
|
protected async downloadLinuxScript(req: Request, res: Response): Promise<void> {
|
|
res.download(path.resolve(__dirname, '..', 'assets/files/upload_file.sh'), 'upload_file.sh');
|
|
}
|
|
|
|
protected async postFileFrontend(req: Request, res: Response): Promise<void> {
|
|
req.body.type = 'file';
|
|
await FileController.handleFileUpload(
|
|
req.body.autogen_url === undefined && req.body.slug ?
|
|
req.body.slug :
|
|
await generateSlug(10),
|
|
req,
|
|
res,
|
|
);
|
|
}
|
|
|
|
public static async handleFileUpload(
|
|
slug: string,
|
|
req: Request,
|
|
res: Response,
|
|
requestAuth: boolean = false,
|
|
): Promise<void> {
|
|
// Check for file upload
|
|
if (Object.keys(req.files).indexOf('upload') < 0) {
|
|
throw new BadRequestError('No file received.', 'You must upload exactly one (1) file.', req.url);
|
|
}
|
|
|
|
const upload = req.files['upload'];
|
|
if (Array.isArray(upload)) {
|
|
throw new BadRequestError(
|
|
'Uploading multiple files at once is unsupported.',
|
|
'Please only upload one file at a time.',
|
|
req.url,
|
|
);
|
|
}
|
|
|
|
// 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 user = (requestAuth ? req.as(RequireRequestAuthMiddleware) : req.as(RequireAuthMiddleware)).getUser();
|
|
|
|
const file = FileModel.create({
|
|
user_id: user.id,
|
|
slug: slug,
|
|
real_name: upload.originalFilename,
|
|
storage_type: 'local',
|
|
storage_path: 'storage/uploads/' + slug,
|
|
size: upload.size,
|
|
expires_at: ttl <= 0 ? null : new Date(Date.now() + ttl * 1000),
|
|
});
|
|
|
|
await file.save();
|
|
fs.renameSync(upload.filepath, file.getOrFail('storage_path'));
|
|
|
|
const domain = req.body.url_domain ||
|
|
config.get<string[]>('allowed_url_domains')[config.get<number>('default_url_domain_for_files')];
|
|
res.format({
|
|
json: () => res.json({
|
|
url: file.getURL(domain),
|
|
}),
|
|
text: () => res.send(file.getURL(domain)),
|
|
html: () => {
|
|
req.flash('success', 'Upload success!');
|
|
req.flash('url', file.getURL(domain));
|
|
res.redirect(route('file-uploader'));
|
|
},
|
|
});
|
|
}
|
|
|
|
public static async deleteFileRoute(
|
|
req: Request,
|
|
res: Response,
|
|
next: NextFunction,
|
|
requestAuth: boolean = false,
|
|
): Promise<void> {
|
|
const slug = req.params.slug;
|
|
if (!slug) throw new BadRequestError('Cannot delete nothing.', 'Please provide a slug.', req.url);
|
|
|
|
const file = await FileModel.getBySlug(req.params.slug);
|
|
if (!file) return next();
|
|
|
|
const user = (requestAuth ? req.as(RequireRequestAuthMiddleware) : req.as(RequireAuthMiddleware)).getUser();
|
|
if (!file.canDelete(user.getOrFail('id'))) throw new ForbiddenHttpError('file', req.url);
|
|
|
|
switch (file.storage_type) {
|
|
case 'local':
|
|
await FileController.deleteFile(file);
|
|
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.redirect(route('file-uploader'));
|
|
},
|
|
});
|
|
}
|
|
|
|
public static async deleteFile(file: FileModel): Promise<void> {
|
|
fs.unlinkSync(file.getOrFail('storage_path'));
|
|
await file.delete();
|
|
logger.info('Deleted', file.storage_path, `(${file.real_name})`);
|
|
}
|
|
}
|
|
|
|
export class FileUploadFormMiddleware extends FileUploadMiddleware {
|
|
protected getDefaultField(): string {
|
|
return 'upload';
|
|
}
|
|
|
|
protected getFormidableOptions(): formidable.Options {
|
|
return {
|
|
uploadDir: 'storage/tmp',
|
|
maxFileSize: config.get<number>('max_upload_size') * 1024 * 1024,
|
|
};
|
|
}
|
|
|
|
}
|