1 | import * as express from "express";
|
2 | import App from "./app";
|
3 | import SkipResponse from "./skip.response";
|
4 | import { ValidateObject } from "./validate-object";
|
5 | import { HttpVerb } from "./http-verb";
|
6 | import { LynxControllerMetadata, LynxRouteMetadata } from "./decorators";
|
7 | import { BaseMiddleware, BLOCK_CHAIN } from "./base.middleware";
|
8 |
|
9 | import { logger } from "./logger";
|
10 |
|
11 | function retrieveArgumentsNamesFromRoute(path: string) {
|
12 | const args = [];
|
13 | const parts = path.split("/");
|
14 | for (let part of parts) {
|
15 | if (part.startsWith(":")) {
|
16 | let arg = part.substring(1, part.length);
|
17 | let endIndex = arg.indexOf("(");
|
18 | if (endIndex != -1) {
|
19 | arg = arg.substring(0, endIndex);
|
20 | }
|
21 | args.push(arg);
|
22 | }
|
23 | }
|
24 | return args;
|
25 | }
|
26 |
|
27 | function generateStandardCallback(controller: any, route: LynxRouteMetadata, app: App) {
|
28 | return async (
|
29 | req: express.Request,
|
30 | res: express.Response,
|
31 | next: express.NextFunction
|
32 | ) => {
|
33 | (req as any).lynx = {
|
34 | route: route
|
35 | };
|
36 | if (route.verifiers) {
|
37 | for (let verify of route.verifiers) {
|
38 | let passed = true;
|
39 | if (verify.isAsync) {
|
40 | passed = await verify.fun(req, res);
|
41 | } else {
|
42 | if (verify.fun) {
|
43 | passed = verify.fun(req, res);
|
44 | } else {
|
45 | try {
|
46 | passed = (verify as any)(req, res);
|
47 | } catch(e) {
|
48 | logger.error(e);
|
49 | }
|
50 | }
|
51 | }
|
52 | if (!passed) {
|
53 | return next();
|
54 | }
|
55 | }
|
56 | }
|
57 |
|
58 | if (!controller._hasBeenInit) {
|
59 | await controller.postConstructor();
|
60 | }
|
61 |
|
62 | let f = controller[route.method];
|
63 | let argsNames = retrieveArgumentsNamesFromRoute(route.path);
|
64 | let argsValues = [];
|
65 | for (let arg of argsNames) {
|
66 | argsValues.push(req.params[arg]);
|
67 | }
|
68 | if (route.body != null) {
|
69 | let b = req.body;
|
70 | if (route.body.schema) {
|
71 | b = new ValidateObject(
|
72 | b,
|
73 | route.body.schema,
|
74 | req.acceptsLanguages()
|
75 | );
|
76 | }
|
77 | argsValues.push(b);
|
78 | }
|
79 | argsValues.push(req);
|
80 | argsValues.push(res);
|
81 | controller._ctxMap = {};
|
82 | f.apply(controller, argsValues)
|
83 | .then((r: any) => {
|
84 | if (!r) {
|
85 | logger.error("Wait, you have a method in a controller without any return!!");
|
86 | logger.error(`Method info: ${route.type} ${route.path}`);
|
87 | throw new Error("Method without any return!");
|
88 | }
|
89 | if (route.isAPI) {
|
90 | let body = app.apiResponseWrapper.onSuccess(r);
|
91 | return res.send(body);
|
92 | } else {
|
93 | if (r instanceof SkipResponse) {
|
94 | r.performResponse(req, res);
|
95 | return next();
|
96 | }
|
97 | if (r.performResponse) {
|
98 | return r.performResponse(req, res);
|
99 | }
|
100 | res.send(r);
|
101 | }
|
102 | })
|
103 | .catch((error: Error) => {
|
104 | logger.info(error);
|
105 | let status = 400;
|
106 | let e = error as any;
|
107 | if (e.statusCode) {
|
108 | status = e.statusCode;
|
109 | }
|
110 | if (!res.headersSent) {
|
111 | res.status(status);
|
112 | }
|
113 | if (route.isAPI) {
|
114 | let body = app.apiResponseWrapper.onError(error);
|
115 | res.send(body);
|
116 | } else {
|
117 | next(error);
|
118 | }
|
119 | });
|
120 | };
|
121 | }
|
122 |
|
123 | export function generateRouter(
|
124 | app: App,
|
125 | controller: any,
|
126 | originalController: LynxControllerMetadata,
|
127 | routes: any
|
128 | ): express.Router {
|
129 | const router = express.Router();
|
130 |
|
131 | for (let route of originalController.routes) {
|
132 | if (route.isDisabledOn) {
|
133 | if (route.isDisabledOn()) {
|
134 | continue;
|
135 | }
|
136 | }
|
137 | let func: Function;
|
138 | switch (route.type) {
|
139 | case HttpVerb.GET:
|
140 | func = router.get;
|
141 | break;
|
142 | case HttpVerb.POST:
|
143 | func = router.post;
|
144 | break;
|
145 | case HttpVerb.PUT:
|
146 | func = router.put;
|
147 | break;
|
148 | case HttpVerb.DELETE:
|
149 | func = router.delete;
|
150 | break;
|
151 | case HttpVerb.PATCH:
|
152 | func = router.patch;
|
153 | break;
|
154 | default:
|
155 | throw new Error(
|
156 | "The decoration type for the method " +
|
157 | route.method +
|
158 | " is invalid"
|
159 | );
|
160 | }
|
161 | if (route.name) {
|
162 | routes[route.name] = (
|
163 | originalController.controllerPath +
|
164 | "/" +
|
165 | route.path
|
166 | ).replace(/\/\/+/g, "/");
|
167 | }
|
168 | const callback = generateStandardCallback(controller, route, app);
|
169 | let p = route.path;
|
170 | if (!p.startsWith("/")) {
|
171 | p = "/" + p;
|
172 | }
|
173 | if (route.isMultipartForm) {
|
174 | func.call(router, p, app.upload.any(), callback);
|
175 | } else {
|
176 | func.call(router, p, callback);
|
177 | }
|
178 | }
|
179 |
|
180 | return router;
|
181 | }
|
182 |
|
183 | export function useController(
|
184 | app: App,
|
185 | Controller: LynxControllerMetadata,
|
186 | routes: any
|
187 | ) {
|
188 | if (!Controller.controllerPath) {
|
189 | throw new Error(
|
190 | 'You should decorate the "' +
|
191 | (<any>Controller).name +
|
192 | '" class in order to use it'
|
193 | );
|
194 | }
|
195 | const controller = new (<any>Controller)(app);
|
196 | controller._metadata = Controller;
|
197 | const router = generateRouter(app, controller, Controller, routes);
|
198 | app.express.use(Controller.controllerPath, router);
|
199 | }
|
200 |
|
201 | function generateMiddlewareCallback(middleware: BaseMiddleware) {
|
202 | return (
|
203 | req: express.Request,
|
204 | res: express.Response,
|
205 | next: express.NextFunction
|
206 | ) => {
|
207 | let f = middleware.apply;
|
208 | let argsValues = [];
|
209 | argsValues.push(req);
|
210 | argsValues.push(res);
|
211 | f.apply(middleware, argsValues)
|
212 | .then((r: any) => {
|
213 | if (r === BLOCK_CHAIN) {
|
214 | return;
|
215 | }
|
216 | next();
|
217 | })
|
218 | .catch((error: Error) => {
|
219 | logger.info(error);
|
220 | let status = 400;
|
221 | let e = error as any;
|
222 | if (e.statusCode) {
|
223 | status = e.statusCode;
|
224 | }
|
225 | res.status(status).send(error);
|
226 | });
|
227 | };
|
228 | }
|
229 |
|
230 | function generateMiddlewares(
|
231 | app: App,
|
232 | middleware: BaseMiddleware,
|
233 | path: string
|
234 | ) {
|
235 | const callback = generateMiddlewareCallback(middleware);
|
236 | app.express.use(path, callback);
|
237 | }
|
238 |
|
239 | export function useMiddleware(app: App, Middleware: any) {
|
240 | if (!Middleware.middlewarePath) {
|
241 | throw new Error(
|
242 | 'You should use at least one Middleware() decorator to a method of the "' +
|
243 | Middleware.name +
|
244 | '" class.'
|
245 | );
|
246 | }
|
247 | const middleware = new Middleware(app);
|
248 | generateMiddlewares(
|
249 | app,
|
250 | middleware as BaseMiddleware,
|
251 | Middleware.middlewarePath
|
252 | );
|
253 | }
|