2020-04-22 15:52:17 +02:00
|
|
|
import {RequestHandler, Router} from "express";
|
|
|
|
import {PathParams} from "express-serve-static-core";
|
|
|
|
import config from "config";
|
|
|
|
import Logger from "./Logger";
|
2020-04-25 16:09:47 +02:00
|
|
|
import Validator, {ValidationBag} from "./db/Validator";
|
2020-04-22 15:52:17 +02:00
|
|
|
|
|
|
|
export default abstract class Controller {
|
|
|
|
private static readonly routes: { [p: string]: string } = {};
|
|
|
|
|
|
|
|
public static route(route: string, params: RouteParams = [], absolute: boolean = false): string {
|
|
|
|
let path = this.routes[route];
|
|
|
|
if (path === undefined) throw new Error(`Unknown route for name ${route}.`);
|
|
|
|
|
|
|
|
if (typeof params === 'string' || typeof params === 'number') {
|
|
|
|
path = path.replace(/:[a-zA-Z_-]+\??/, '' + params);
|
|
|
|
} else if (Array.isArray(params)) {
|
|
|
|
let i = 0;
|
|
|
|
for (const match of path.matchAll(/:[a-zA-Z_-]+\??/)) {
|
|
|
|
if (match.length > 0) {
|
|
|
|
path = path.replace(match[0], typeof params[i] !== 'undefined' ? params[i] : '');
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
path = path.replace(/\/+/, '/');
|
|
|
|
} else {
|
|
|
|
for (const key in params) {
|
|
|
|
if (params.hasOwnProperty(key)) {
|
|
|
|
path = path.replace(new RegExp(`:${key}\\??`), params[key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return `${absolute ? config.get<string>('public_url') : ''}${path}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
private router?: Router;
|
|
|
|
|
|
|
|
public getGlobalHandlers(): RequestHandler[] {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
public hasGlobalHandlers(): boolean {
|
|
|
|
return this.getGlobalHandlers().length > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
public setupGlobalHandlers(router: Router): void {
|
|
|
|
for (const globalHandler of this.getGlobalHandlers()) {
|
|
|
|
router.use(this.wrap(globalHandler));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public getRoutesPrefix(): string {
|
|
|
|
return '/';
|
|
|
|
}
|
|
|
|
|
|
|
|
public abstract routes(): void;
|
|
|
|
|
|
|
|
public setupRoutes(router: Router): void {
|
|
|
|
this.router = router;
|
|
|
|
this.routes();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected use(handler: RequestHandler) {
|
|
|
|
this.router?.use(this.wrap(handler));
|
|
|
|
}
|
|
|
|
|
|
|
|
protected get(path: PathParams, handler: RequestHandler, routeName?: string, ...middlewares: RequestHandler[]) {
|
|
|
|
this.registerRoutes(path, handler, routeName);
|
|
|
|
for (const middleware of middlewares) {
|
|
|
|
this.router?.get(path, this.wrap(middleware));
|
|
|
|
}
|
|
|
|
this.router?.get(path, this.wrap(handler));
|
|
|
|
}
|
|
|
|
|
|
|
|
protected post(path: PathParams, handler: RequestHandler, routeName?: string, ...middlewares: RequestHandler[]) {
|
|
|
|
this.registerRoutes(path, handler, routeName);
|
|
|
|
for (const middleware of middlewares) {
|
|
|
|
this.router?.post(path, this.wrap(middleware));
|
|
|
|
}
|
|
|
|
this.router?.post(path, this.wrap(handler));
|
|
|
|
}
|
|
|
|
|
|
|
|
private wrap(handler: RequestHandler): RequestHandler {
|
|
|
|
return (req, res, next) => {
|
2020-04-25 16:09:47 +02:00
|
|
|
function handleErr(e: any) {
|
|
|
|
if (e instanceof ValidationBag) {
|
|
|
|
req.flash('validation', e.getMessages());
|
|
|
|
res.redirectBack();
|
|
|
|
} else {
|
|
|
|
next(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const promise = handler.call(this, req, res, next);
|
|
|
|
if (promise instanceof Promise) {
|
|
|
|
promise.catch(e => {
|
|
|
|
handleErr(e);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
handleErr(e);
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
private registerRoutes(path: PathParams, handler: RequestHandler, routeName?: string) {
|
|
|
|
if (typeof routeName !== 'string') {
|
|
|
|
routeName = handler.name
|
|
|
|
.replace(/(?:^|\.?)([A-Z])/g, (x, y) => '_' + y.toLowerCase())
|
|
|
|
.replace(/(^_|get_|post_)/g, '');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (routeName.length === 0) return;
|
|
|
|
|
|
|
|
let routePath = null;
|
|
|
|
if (path instanceof Array && path.length > 0) {
|
|
|
|
path = path[0];
|
|
|
|
}
|
|
|
|
if (typeof path === 'string') {
|
|
|
|
const prefix = this.getRoutesPrefix();
|
|
|
|
routePath = (prefix !== '/' ? prefix : '') + path;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Controller.routes[routeName]) {
|
|
|
|
if (typeof routePath === 'string') {
|
|
|
|
Logger.info(`Route ${routeName} has path ${routePath}`);
|
|
|
|
Controller.routes[routeName] = routePath;
|
|
|
|
} else {
|
|
|
|
Logger.warn(`Cannot assign path to route ${routeName}.`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-04-25 09:33:33 +02:00
|
|
|
|
|
|
|
protected async validate(validationMap: { [p: string]: Validator<any> }, body: any): Promise<void> {
|
2020-04-25 16:09:47 +02:00
|
|
|
const bag = new ValidationBag();
|
|
|
|
|
2020-04-25 09:33:33 +02:00
|
|
|
for (const p in validationMap) {
|
|
|
|
if (validationMap.hasOwnProperty(p)) {
|
2020-04-25 16:09:47 +02:00
|
|
|
try {
|
|
|
|
await validationMap[p].execute(p, body[p], false);
|
|
|
|
} catch (e) {
|
|
|
|
if (e instanceof ValidationBag) {
|
|
|
|
bag.addBag(e);
|
|
|
|
} else throw e;
|
|
|
|
}
|
2020-04-25 09:33:33 +02:00
|
|
|
}
|
|
|
|
}
|
2020-04-25 16:09:47 +02:00
|
|
|
|
|
|
|
if (bag.hasMessages()) throw bag;
|
2020-04-25 09:33:33 +02:00
|
|
|
}
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export type RouteParams = { [p: string]: string } | string[] | string | number;
|