Integrate file upload form middleware into controllers

This commit is contained in:
Alice Gaudon 2020-07-11 11:08:57 +02:00
parent 892cf71917
commit 4b8a3b307d
4 changed files with 90 additions and 72 deletions

View File

@ -64,8 +64,10 @@ export default abstract class Application {
// Init express // Init express
const app = express(); const app = express();
const router = express.Router({}); const mainRouter = express.Router();
app.use(router); const fileUploadFormRouter = express.Router();
app.use(fileUploadFormRouter);
app.use(mainRouter);
// Error handler // Error handler
app.use((err: any, req: Request, res: Response, next: NextFunction) => { app.use((err: any, req: Request, res: Response, next: NextFunction) => {
@ -135,11 +137,11 @@ export default abstract class Application {
// Start all components // Start all components
for (const component of this.components) { for (const component of this.components) {
await component.start(app, router); await component.start(app, mainRouter);
} }
// Routes // Routes
this.routes(router); this.routes(mainRouter, fileUploadFormRouter);
this.ready = true; this.ready = true;
} }
@ -175,24 +177,24 @@ export default abstract class Application {
Logger.info(`${this.constructor.name} v${this.version} - bye`); Logger.info(`${this.constructor.name} v${this.version} - bye`);
} }
private routes(rootRouter: Router) { private routes(mainRootRouter: Router, rootFileUploadFormRouter: Router) {
for (const controller of this.controllers) { for (const controller of this.controllers) {
if (controller.hasGlobalHandlers()) { if (controller.hasGlobalHandlers()) {
controller.setupGlobalHandlers(rootRouter); controller.setupGlobalHandlers(mainRootRouter);
Logger.info(`Registered global middlewares for controller ${controller.constructor.name}`); Logger.info(`Registered global middlewares for controller ${controller.constructor.name}`);
} }
} }
for (const controller of this.controllers) { for (const controller of this.controllers) {
const router = express.Router(); const {mainRouter, fileUploadFormRouter} = controller.setupRoutes();
controller.setupRoutes(router); mainRootRouter.use(controller.getRoutesPrefix(), mainRouter);
rootRouter.use(controller.getRoutesPrefix(), router); rootFileUploadFormRouter.use(controller.getRoutesPrefix(), fileUploadFormRouter);
Logger.info(`> Registered routes for controller ${controller.constructor.name}`); Logger.info(`> Registered routes for controller ${controller.constructor.name}`);
} }
rootRouter.use((req: Request) => { mainRootRouter.use((req: Request) => {
throw new NotFoundHttpError('page', req.originalUrl); throw new NotFoundHttpError('page', req.originalUrl);
}); });
} }

View File

@ -1,8 +1,9 @@
import {RequestHandler, Router} from "express"; import express, {IRouter, RequestHandler, Router} from "express";
import {PathParams} from "express-serve-static-core"; import {PathParams} from "express-serve-static-core";
import config from "config"; import config from "config";
import Logger from "./Logger"; import Logger from "./Logger";
import Validator, {ValidationBag} from "./db/Validator"; import Validator, {FileError, ValidationBag} from "./db/Validator";
import FileUploadMiddleware from "./FileUploadMiddleware";
export default abstract class Controller { export default abstract class Controller {
private static readonly routes: { [p: string]: string } = {}; private static readonly routes: { [p: string]: string } = {};
@ -33,7 +34,8 @@ export default abstract class Controller {
return `${absolute ? config.get<string>('public_url') : ''}${path}`; return `${absolute ? config.get<string>('public_url') : ''}${path}`;
} }
private router?: Router; private readonly router: Router = express.Router();
private readonly fileUploadFormRouter: Router = express.Router();
public getGlobalHandlers(): RequestHandler[] { public getGlobalHandlers(): RequestHandler[] {
return []; return [];
@ -55,56 +57,59 @@ export default abstract class Controller {
public abstract routes(): void; public abstract routes(): void;
public setupRoutes(router: Router): void { public setupRoutes(): {
this.router = router; mainRouter: Router,
fileUploadFormRouter: Router
} {
this.routes(); this.routes();
return {
mainRouter: this.router,
fileUploadFormRouter: this.fileUploadFormRouter,
};
} }
protected use(handler: RequestHandler) { protected use(handler: RequestHandler) {
this.router?.use(this.wrap(handler)); this.router.use(handler);
} }
protected get(path: PathParams, handler: RequestHandler, routeName?: string, ...middlewares: RequestHandler[]) { protected get(path: PathParams, handler: RequestHandler, routeName?: string, ...middlewares: (RequestHandler | FileUploadMiddleware)[]) {
this.registerRoutes(path, handler, routeName); this.handle('get', path, handler, routeName, ...middlewares);
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[]) { protected post(path: PathParams, handler: RequestHandler, routeName?: string, ...middlewares: (RequestHandler | FileUploadMiddleware)[]) {
this.registerRoutes(path, handler, routeName); this.handle('post', path, handler, routeName, ...middlewares);
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[]) { protected put(path: PathParams, handler: RequestHandler, routeName?: string, ...middlewares: (RequestHandler | FileUploadMiddleware)[]) {
this.registerRoutes(path, handler, routeName); this.handle('put', path, handler, routeName, ...middlewares);
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[]) { protected delete(path: PathParams, handler: RequestHandler, routeName?: string, ...middlewares: (RequestHandler | FileUploadMiddleware)[]) {
this.handle('delete', path, handler, routeName, ...middlewares);
}
private handle(
action: Exclude<keyof IRouter, 'stack' | 'param' | 'route' | 'use'>,
path: PathParams,
handler: RequestHandler,
routeName?: string,
...middlewares: (RequestHandler | FileUploadMiddleware)[]
): void {
this.registerRoutes(path, handler, routeName); this.registerRoutes(path, handler, routeName);
for (const middleware of middlewares) { for (const middleware of middlewares) {
this.router?.delete(path, this.wrap(middleware)); if (middleware instanceof FileUploadMiddleware) {
this.fileUploadFormRouter[action](path, this.wrap(FILE_UPLOAD_MIDDLEWARE(middleware)));
} else {
this.router[action](path, this.wrap(middleware));
} }
this.router?.delete(path, this.wrap(handler)); }
this.router[action](path, this.wrap(handler));
} }
private wrap(handler: RequestHandler): RequestHandler { private wrap(handler: RequestHandler): RequestHandler {
return (req, res, next) => { return async (req, res, next) => {
try { try {
const promise = handler.call(this, req, res, next); await handler.call(this, req, res, next);
if (promise instanceof Promise) {
promise.catch(e => {
next(e);
});
}
} catch (e) { } catch (e) {
next(e); next(e);
} }
@ -159,3 +164,30 @@ export default abstract class Controller {
} }
export type RouteParams = { [p: string]: string } | string[] | string | number; export type RouteParams = { [p: string]: string } | string[] | string | number;
const FILE_UPLOAD_MIDDLEWARE: (fileUploadMiddleware: FileUploadMiddleware) => RequestHandler = (fileUploadMiddleware: FileUploadMiddleware) => {
return async (req, res, next) => {
const form = fileUploadMiddleware.formFactory();
try {
await new Promise<any>((resolve, reject) => {
form.parse(req, (err, fields, files) => {
if (err) {
reject(err);
return;
}
req.body = fields;
req.files = files;
resolve();
});
});
} catch (e) {
const bag = new ValidationBag();
const fileError = new FileError(e);
fileError.thingName = fileUploadMiddleware.defaultField;
bag.addMessage(fileError);
next(bag);
return;
}
next();
};
};

View File

@ -0,0 +1,11 @@
import {IncomingForm} from "formidable";
export default class FileUploadMiddleware {
public readonly formFactory: () => IncomingForm;
public readonly defaultField: string;
public constructor(formFactory: () => IncomingForm, defaultField: string) {
this.formFactory = formFactory;
this.defaultField = defaultField;
}
}

View File

@ -46,30 +46,3 @@ export default class ExpressAppComponent extends ApplicationComponent<void> {
return this.server; return this.server;
} }
} }
export const FILE_UPLOAD_MIDDLEWARE: (formFactory: () => IncomingForm, defaultField: string) => RequestHandler = (formFactory: () => IncomingForm, defaultField: string) => {
return async (req, res, next) => {
const form = formFactory();
try {
await new Promise<any>((resolve, reject) => {
form.parse(req, (err, fields, files) => {
if (err) {
reject(err);
return;
}
req.body = fields;
req.files = files;
resolve();
});
});
} catch (e) {
const bag = new ValidationBag();
const fileError = new FileError(e);
fileError.thingName = defaultField;
bag.addMessage(fileError);
next(bag);
return;
}
next();
};
};