Passed
Push — main ( a24599...473189 )
by Lorenzo
01:33 queued 37s
created

ExpressBeans.serializeRequest   A

Complexity

Conditions 5

Size

Total Lines 16
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 5

Importance

Changes 0
Metric Value
eloc 15
dl 0
loc 16
c 0
b 0
f 0
ccs 7
cts 7
cp 1
rs 9.1832
cc 5
crap 5
1 5
import express, { Express, Request } from 'express';
2 5
import { pinoHttp, startTime } from 'pino-http';
3
import { ServerResponse, IncomingMessage } from 'http';
4
import { CreateExpressBeansOptions, ExpressBeansOptions, ExpressRouterBean } from '@/ExpressBeansTypes';
5 5
import { logger, registeredBeans } from '@/core';
6
7 5
export default class ExpressBeans {
8 17
  private readonly app: Express;
9
10 17
  private onInitialized: (() => void) | undefined;
11
12
  /**
13
   * Creates a new ExpressBeans application
14
   * @param options {ExpressBeansOptions}
15
   */
16
  static createApp(options?: Partial<CreateExpressBeansOptions>): Promise<ExpressBeans> {
17 4
    return new Promise((resolve, reject) => {
18
      let app: ExpressBeans;
19 4
      const onInitialized = () => {
20 1
        resolve(app);
21
      };
22 4
      app = new ExpressBeans({ ...options, onInitialized, onError: reject });
23
    });
24
  }
25
26
  /**
27
   * Constructor of ExpressBeans application
28
   * @constructor
29
   * @param options {ExpressBeansOptions}
30
   */
31
  constructor(options?: Partial<ExpressBeansOptions>) {
32 17
    this.app = express();
33 17
    this.app.disable('x-powered-by');
34 17
    this.app.use(pinoHttp(
35
      {
36
        logger,
37
        customSuccessMessage: this.serializeRequest.bind(this),
38
        customErrorMessage: this.serializeRequest.bind(this),
39
      },
40
    ));
41 17
    this.initialize(options ?? {});
42
  }
43
44
  private serializeRequest(req: IncomingMessage, res: ServerResponse) {
45 5
    const request: Request = req as Request;
46 5
    const remoteAddress = request.headers['x-forwarded-for'] ?? request.socket.remoteAddress;
47 5
    const { method, originalUrl, httpVersion } = request;
48 5
    const responseTime = Date.now() - res[startTime];
49 5
    const optionals = [
50
      res.statusCode,
51
      res.getHeader('content-length'),
52
      res.getHeader('content-type'),
53
      request.headers.referer,
54
      request.headers['user-agent'],
55
    ]
56 25
      .filter((i) => !!i)
57
      .join(' ');
58 5
    return `${remoteAddress} - "${method} ${originalUrl} HTTP/${httpVersion}" ${optionals} - ${responseTime}ms`;
59
  }
60
61
  /**
62
   * Initializes the application and checks
63
   * if all beans are valid
64
   * @param listen {boolean}
65
   * @param port {number}
66
   * @param beans {Object[]}
67
   * @param onInitialized {Function}
68
   * @private
69
   */
70
  private initialize({
71
    listen = true,
72
    port = 8080,
73
    routerBeans = [],
74
    onInitialized,
75
    onError,
76
  }: Partial<ExpressBeansOptions>) {
77 17
    this.onInitialized = onInitialized;
78 17
    this.checkRouterBeans(routerBeans);
79 16
    setImmediate(() => {
80 16
      try {
81 16
        this.registerRouters();
82 14
        if (listen) {
83 10
          this.listen(port);
84
        }
85
      } catch (err: any) {
86 5
        if (onError) {
87 4
          onError(err);
88
        } else {
89 1
          logger.error(new Error('Critical error', { cause: err }));
90 1
          process.exit(1);
91
        }
92
      }
93
    });
94
  }
95
96
  /**
97
   * Starts the server and calls onInitialized callback
98
   * @param {number} port
99
   */
100
  listen(port: number) {
101 13
    return this.app.listen(port, () => {
102 6
      logger.info(`Server listening on port ${port}`);
103 6
      if (this.onInitialized) {
104 2
        this.onInitialized();
105
      }
106
    });
107
  }
108
109
  private registerRouters() {
110 16
    Array.from(registeredBeans.values())
111 13
      .map((bean) => bean as ExpressRouterBean)
112 13
      .filter((bean) => bean.routerConfig)
113
      .forEach((bean) => {
114 12
        try {
115
          const {
116
            path,
117
            router,
118 12
          } = bean.routerConfig;
119 12
          logger.debug(`Registering router ${bean.className}`);
120 12
          this.app.use(path, router);
121
        } catch (e) {
122 2
          logger.error(e);
123 2
          throw new Error(`Router ${bean.className} not initialized correctly`);
124
        }
125
      });
126
  }
127
128
  private checkRouterBeans(routerBeans: Array<ExpressRouterBean>) {
129 17
    const invalidBeans = routerBeans
130 15
      .filter(((bean) => !bean.isExpressBean))
131 1
      .map((object: any) => object.prototype.constructor.name);
132 17
    if (invalidBeans.length > 0) {
133 1
      throw new Error(`Trying to use something that is not an ExpressBean: ${invalidBeans.join(', ')}`);
134
    }
135
  }
136
137
  /**
138
   * Gets Express application
139
   * @returns {Express}
140
   */
141
  getApp() {
142 2
    return this.app;
143
  }
144
145
  /**
146
   * Exposes use function of Express application
147
   * @param handlers
148
   */
149
  use(...handlers: any) {
150 1
    this.app.use(...handlers);
151
  }
152
}
153