import {RequestHandler, Router} from "express"; import {PathParams} from "express-serve-static-core"; import config from "config"; import Logger from "./Logger"; import Validator, {ValidationBag} from "./db/Validator"; 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_-]+\??/g, '' + params); } else if (Array.isArray(params)) { let i = 0; for (const match of path.matchAll(/:[a-zA-Z_-]+(\(.*\))?\??/g)) { if (match.length > 0) { path = path.replace(match[0], typeof params[i] !== 'undefined' ? params[i] : ''); } i++; } path = path.replace(/\/+/g, '/'); } else { for (const key in params) { if (params.hasOwnProperty(key)) { path = path.replace(new RegExp(`:${key}\\??`), params[key]); } } } return `${absolute ? config.get('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)); } protected put(path: PathParams, handler: RequestHandler, routeName?: string, ...middlewares: RequestHandler[]) { this.registerRoutes(path, handler, routeName); for (const middleware of middlewares) { this.router?.put(path, this.wrap(middleware)); } this.router?.put(path, this.wrap(handler)); } protected delete(path: PathParams, handler: RequestHandler, routeName?: string, ...middlewares: RequestHandler[]) { this.registerRoutes(path, handler, routeName); for (const middleware of middlewares) { this.router?.delete(path, this.wrap(middleware)); } this.router?.delete(path, this.wrap(handler)); } private wrap(handler: RequestHandler): RequestHandler { return (req, res, next) => { 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); } }; } 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}.`); } } } protected async validate(validationMap: { [p: string]: Validator }, body: any): Promise { const bag = new ValidationBag(); for (const p in validationMap) { if (validationMap.hasOwnProperty(p)) { try { await validationMap[p].execute(p, body[p], false); } catch (e) { if (e instanceof ValidationBag) { bag.addBag(e); } else throw e; } } } if (bag.hasMessages()) throw bag; } } export type RouteParams = { [p: string]: string } | string[] | string | number;