2020-07-11 11:08:57 +02:00
|
|
|
import express, {IRouter, RequestHandler, Router} from "express";
|
2020-04-22 15:52:17 +02:00
|
|
|
import {PathParams} from "express-serve-static-core";
|
|
|
|
import config from "config";
|
2021-01-22 15:54:26 +01:00
|
|
|
import {logger} from "./Logger";
|
2020-07-11 11:08:57 +02:00
|
|
|
import FileUploadMiddleware from "./FileUploadMiddleware";
|
2020-07-15 11:42:49 +02:00
|
|
|
import * as querystring from "querystring";
|
|
|
|
import {ParsedUrlQueryInput} from "querystring";
|
2020-09-25 23:42:15 +02:00
|
|
|
import Middleware, {MiddlewareType} from "./Middleware";
|
2020-09-25 22:03:22 +02:00
|
|
|
import Application from "./Application";
|
2020-04-22 15:52:17 +02:00
|
|
|
|
|
|
|
export default abstract class Controller {
|
2021-03-30 10:45:14 +02:00
|
|
|
/**
|
|
|
|
* TODO: this should not be static, it should actually be bound to an app instance.
|
|
|
|
*/
|
2020-09-25 23:42:15 +02:00
|
|
|
private static readonly routes: { [p: string]: string | undefined } = {};
|
|
|
|
|
|
|
|
public static route(
|
|
|
|
route: string,
|
|
|
|
params: RouteParams = [],
|
|
|
|
query: ParsedUrlQueryInput = {},
|
|
|
|
absolute: boolean = false,
|
|
|
|
): string {
|
2020-04-22 15:52:17 +02:00
|
|
|
let path = this.routes[route];
|
|
|
|
if (path === undefined) throw new Error(`Unknown route for name ${route}.`);
|
|
|
|
|
2021-03-30 10:45:14 +02:00
|
|
|
const regExp = this.getRouteParamRegExp('[a-zA-Z0-9_-]+', 'g');
|
2020-04-22 15:52:17 +02:00
|
|
|
if (typeof params === 'string' || typeof params === 'number') {
|
2021-03-30 10:45:14 +02:00
|
|
|
path = path.replace(regExp, '' + params);
|
2020-04-22 15:52:17 +02:00
|
|
|
} else if (Array.isArray(params)) {
|
|
|
|
let i = 0;
|
2021-03-30 10:45:14 +02:00
|
|
|
for (const match of path.matchAll(regExp)) {
|
2020-04-22 15:52:17 +02:00
|
|
|
if (match.length > 0) {
|
|
|
|
path = path.replace(match[0], typeof params[i] !== 'undefined' ? params[i] : '');
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
2020-05-02 11:35:01 +02:00
|
|
|
path = path.replace(/\/+/g, '/');
|
2020-04-22 15:52:17 +02:00
|
|
|
} else {
|
2020-09-25 23:42:15 +02:00
|
|
|
for (const key of Object.keys(params)) {
|
2021-03-30 10:45:14 +02:00
|
|
|
path = path.replace(this.getRouteParamRegExp(key), params[key].toString());
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-15 11:42:49 +02:00
|
|
|
const queryStr = querystring.stringify(query);
|
2021-01-21 17:12:59 +01:00
|
|
|
return `${absolute ? config.get<string>('public_url') : ''}${path}` + (queryStr.length > 0 ? '?' + queryStr : '');
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
|
2021-03-30 10:45:14 +02:00
|
|
|
private static getRouteParamRegExp(key: string, flags?: string): RegExp {
|
|
|
|
return new RegExp(`:${key}(\\(.+?\\))?\\??`, flags);
|
|
|
|
}
|
|
|
|
|
2020-07-11 11:08:57 +02:00
|
|
|
private readonly router: Router = express.Router();
|
|
|
|
private readonly fileUploadFormRouter: Router = express.Router();
|
2020-09-25 22:03:22 +02:00
|
|
|
private app?: Application;
|
2020-04-22 15:52:17 +02:00
|
|
|
|
2020-09-25 22:03:22 +02:00
|
|
|
public getGlobalMiddlewares(): Middleware[] {
|
2020-04-22 15:52:17 +02:00
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2020-09-25 22:03:22 +02:00
|
|
|
public hasGlobalMiddlewares(): boolean {
|
|
|
|
return this.getGlobalMiddlewares().length > 0;
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public setupGlobalHandlers(router: Router): void {
|
2020-09-25 22:03:22 +02:00
|
|
|
for (const middleware of this.getGlobalMiddlewares()) {
|
|
|
|
router.use(this.wrap(middleware.getRequestHandler()));
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public getRoutesPrefix(): string {
|
|
|
|
return '/';
|
|
|
|
}
|
|
|
|
|
|
|
|
public abstract routes(): void;
|
|
|
|
|
2020-09-25 23:42:15 +02:00
|
|
|
public setupRoutes(): { mainRouter: Router, fileUploadFormRouter: Router } {
|
2020-04-22 15:52:17 +02:00
|
|
|
this.routes();
|
2020-07-11 11:08:57 +02:00
|
|
|
return {
|
|
|
|
mainRouter: this.router,
|
|
|
|
fileUploadFormRouter: this.fileUploadFormRouter,
|
|
|
|
};
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
|
2020-09-25 23:42:15 +02:00
|
|
|
protected use(handler: RequestHandler): void {
|
2020-11-11 18:15:23 +01:00
|
|
|
this.router.use(this.wrap(handler));
|
2021-01-22 15:54:26 +01:00
|
|
|
logger.info('Installed anonymous middleware on ' + this.getRoutesPrefix());
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
|
2020-12-30 14:10:58 +01:00
|
|
|
protected useMiddleware(...middlewares: MiddlewareType<Middleware>[]): void {
|
|
|
|
for (const middleware of middlewares) {
|
|
|
|
const instance = new middleware(this.getApp());
|
|
|
|
if (instance instanceof FileUploadMiddleware) {
|
|
|
|
this.fileUploadFormRouter.use(this.wrap(instance.getRequestHandler()));
|
|
|
|
} else {
|
|
|
|
this.router.use(this.wrap(instance.getRequestHandler()));
|
|
|
|
}
|
2021-01-21 15:44:30 +01:00
|
|
|
|
2021-01-22 15:54:26 +01:00
|
|
|
logger.info('Installed ' + middleware.name + ' on ' + this.getRoutesPrefix());
|
2020-12-30 14:10:58 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-25 23:42:15 +02:00
|
|
|
protected get(
|
|
|
|
path: PathParams,
|
|
|
|
handler: RequestHandler,
|
|
|
|
routeName?: string,
|
|
|
|
...middlewares: (MiddlewareType<Middleware>)[]
|
|
|
|
): void {
|
2020-07-11 11:08:57 +02:00
|
|
|
this.handle('get', path, handler, routeName, ...middlewares);
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
|
2020-09-25 23:42:15 +02:00
|
|
|
protected post(
|
|
|
|
path: PathParams,
|
|
|
|
handler: RequestHandler,
|
|
|
|
routeName?: string,
|
|
|
|
...middlewares: (MiddlewareType<Middleware>)[]
|
|
|
|
): void {
|
2020-07-11 11:08:57 +02:00
|
|
|
this.handle('post', path, handler, routeName, ...middlewares);
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
|
2020-09-25 23:42:15 +02:00
|
|
|
protected put(
|
|
|
|
path: PathParams,
|
|
|
|
handler: RequestHandler,
|
|
|
|
routeName?: string,
|
|
|
|
...middlewares: (MiddlewareType<Middleware>)[]
|
|
|
|
): void {
|
2020-07-11 11:08:57 +02:00
|
|
|
this.handle('put', path, handler, routeName, ...middlewares);
|
|
|
|
}
|
|
|
|
|
2020-09-25 23:42:15 +02:00
|
|
|
protected delete(
|
|
|
|
path: PathParams,
|
|
|
|
handler: RequestHandler,
|
|
|
|
routeName?: string,
|
|
|
|
...middlewares: (MiddlewareType<Middleware>)[]
|
|
|
|
): void {
|
2020-07-11 11:08:57 +02:00
|
|
|
this.handle('delete', path, handler, routeName, ...middlewares);
|
2020-06-14 11:43:49 +02:00
|
|
|
}
|
|
|
|
|
2020-07-11 11:08:57 +02:00
|
|
|
private handle(
|
|
|
|
action: Exclude<keyof IRouter, 'stack' | 'param' | 'route' | 'use'>,
|
|
|
|
path: PathParams,
|
|
|
|
handler: RequestHandler,
|
|
|
|
routeName?: string,
|
2020-09-25 23:42:15 +02:00
|
|
|
...middlewares: (MiddlewareType<Middleware>)[]
|
2020-07-11 11:08:57 +02:00
|
|
|
): void {
|
2020-06-14 11:43:49 +02:00
|
|
|
this.registerRoutes(path, handler, routeName);
|
|
|
|
for (const middleware of middlewares) {
|
2020-09-25 22:03:22 +02:00
|
|
|
const instance = new middleware(this.getApp());
|
|
|
|
if (instance instanceof FileUploadMiddleware) {
|
|
|
|
this.fileUploadFormRouter[action](path, this.wrap(instance.getRequestHandler()));
|
2020-07-11 11:08:57 +02:00
|
|
|
} else {
|
2020-09-25 22:03:22 +02:00
|
|
|
this.router[action](path, this.wrap(instance.getRequestHandler()));
|
2020-07-11 11:08:57 +02:00
|
|
|
}
|
2020-06-14 11:43:49 +02:00
|
|
|
}
|
2020-07-11 11:08:57 +02:00
|
|
|
this.router[action](path, this.wrap(handler));
|
2020-06-14 11:43:49 +02:00
|
|
|
}
|
|
|
|
|
2020-04-22 15:52:17 +02:00
|
|
|
private wrap(handler: RequestHandler): RequestHandler {
|
2020-07-11 11:08:57 +02:00
|
|
|
return async (req, res, next) => {
|
2020-04-25 16:09:47 +02:00
|
|
|
try {
|
2020-07-11 11:08:57 +02:00
|
|
|
await handler.call(this, req, res, next);
|
2020-04-25 16:09:47 +02:00
|
|
|
} catch (e) {
|
2020-06-14 21:48:50 +02:00
|
|
|
next(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') {
|
2021-01-22 15:54:26 +01:00
|
|
|
logger.info(`Route ${routeName} has path ${routePath}`);
|
2020-04-22 15:52:17 +02:00
|
|
|
Controller.routes[routeName] = routePath;
|
|
|
|
} else {
|
2021-01-22 15:54:26 +01:00
|
|
|
logger.warn(`Cannot assign path to route ${routeName}.`);
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-04-25 09:33:33 +02:00
|
|
|
|
2020-09-25 22:03:22 +02:00
|
|
|
protected getApp(): Application {
|
|
|
|
if (!this.app) throw new Error('Application not initialized.');
|
|
|
|
return this.app;
|
|
|
|
}
|
|
|
|
|
2020-09-25 23:42:15 +02:00
|
|
|
public setApp(app: Application): void {
|
2020-09-25 22:03:22 +02:00
|
|
|
this.app = app;
|
|
|
|
}
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
|
2021-03-30 10:45:14 +02:00
|
|
|
export type RouteParams = { [p: string]: string | number } | string[] | string | number;
|