/[sudobot]/branches/7.x/src/api/Server.ts
ViewVC logotype

Annotation of /branches/7.x/src/api/Server.ts

Parent Directory Parent Directory | Revision Log Revision Log


Revision 577 - (hide annotations)
Mon Jul 29 18:52:37 2024 UTC (8 months ago) by rakinar2
File MIME type: application/typescript
File size: 7894 byte(s)
chore: add old version archive branches (2.x to 9.x-dev)
1 rakinar2 577 /**
2     * This file is part of SudoBot.
3     *
4     * Copyright (C) 2021-2023 OSN Developers.
5     *
6     * SudoBot is free software; you can redistribute it and/or modify it
7     * under the terms of the GNU Affero General Public License as published by
8     * the Free Software Foundation, either version 3 of the License, or
9     * (at your option) any later version.
10     *
11     * SudoBot is distributed in the hope that it will be useful, but
12     * WITHOUT ANY WARRANTY; without even the implied warranty of
13     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14     * GNU Affero General Public License for more details.
15     *
16     * You should have received a copy of the GNU Affero General Public License
17     * along with SudoBot. If not, see <https://www.gnu.org/licenses/>.
18     */
19    
20     import cors from "cors";
21     import express, { Request as ExpressRequest, Response as ExpressResponse, NextFunction } from "express";
22     import ratelimiter from "express-rate-limit";
23     import { Router } from "express-serve-static-core";
24     import { Server as HttpServer } from "http";
25     import Client from "../core/Client";
26     import { RouteMetadata, RouteMetadataEntry } from "../types/RouteMetadata";
27     import { log, logInfo, logWarn } from "../utils/logger";
28     import Controller from "./Controller";
29     import Response from "./Response";
30    
31     // FIXME: Support new decorators
32     export default class Server {
33     protected readonly expressApp = express();
34     protected readonly rateLimiter = ratelimiter({
35     windowMs: 30 * 1000,
36     max: 28,
37     standardHeaders: true,
38     legacyHeaders: false
39     });
40     protected readonly configRateLimiter = ratelimiter({
41     windowMs: 10 * 1000,
42     max: 7,
43     standardHeaders: true,
44     legacyHeaders: false
45     });
46     public readonly port = process.env.PORT ?? 4000;
47     public expressServer?: HttpServer;
48    
49     constructor(protected readonly client: Client) {}
50    
51     async onReady() {
52     if (this.client.configManager.systemConfig.api.enabled) {
53     await this.boot();
54     }
55     }
56    
57     async boot() {
58     this.expressApp.use(this.onError);
59     this.expressApp.use(cors());
60    
61     if (this.client.configManager.systemConfig.trust_proxies !== undefined) {
62     logInfo("Set express trust proxy option value to ", this.client.configManager.systemConfig.trust_proxies);
63     this.expressApp.set("trust proxy", this.client.configManager.systemConfig.trust_proxies);
64     }
65    
66     this.expressApp.use(this.rateLimiter);
67     this.expressApp.use("/config", this.configRateLimiter);
68     this.expressApp.use(express.json());
69    
70     const router = await this.createRouter();
71     this.expressApp.use("/", router);
72     }
73    
74     async createRouter() {
75     const router = express.Router();
76     await this.client.dynamicLoader.loadControllers(router);
77     return router;
78     }
79    
80     loadController(controller: Controller, controllerClass: typeof Controller, router: Router) {
81     const actions = (
82     Symbol.metadata in controllerClass && controllerClass[Symbol.metadata]
83     ? controllerClass[Symbol.metadata]?.actionMethods
84     : Reflect.getMetadata("action_methods", controllerClass.prototype)
85     ) as Record<string, RouteMetadata> | null;
86     const aacMiddlewareList = (
87     Symbol.metadata in controllerClass && controllerClass[Symbol.metadata]
88     ? controllerClass[Symbol.metadata]?.adminAccessControlMiddleware
89     : Reflect.getMetadata("aac_middleware", controllerClass.prototype)
90     ) as Record<string, Function> | null;
91     const gacMiddlewareList = (
92     Symbol.metadata in controllerClass && controllerClass[Symbol.metadata]
93     ? controllerClass[Symbol.metadata]?.guildAccessControlMiddleware
94     : Reflect.getMetadata("gac_middleware", controllerClass.prototype)
95     ) as Record<string, Function> | null;
96     const requireAuthMiddlewareList = (
97     Symbol.metadata in controllerClass && controllerClass[Symbol.metadata]
98     ? controllerClass[Symbol.metadata]?.authMiddleware
99     : Reflect.getMetadata("auth_middleware", controllerClass.prototype)
100     ) as Record<string, Function> | null;
101     const validationMiddlewareList = (
102     Symbol.metadata in controllerClass && controllerClass[Symbol.metadata]
103     ? controllerClass[Symbol.metadata]?.validationMiddleware
104     : Reflect.getMetadata("validation_middleware", controllerClass.prototype)
105     ) as Record<string, Function> | null;
106    
107     if (!actions) {
108     return;
109     }
110    
111     for (const callbackName in actions) {
112     for (const method in actions[callbackName]) {
113     const data = actions[callbackName][
114     method as keyof (typeof actions)[keyof typeof actions]
115     ] as RouteMetadataEntry | null;
116    
117     if (!data) {
118     continue;
119     }
120    
121     const middleware = [];
122    
123     if (requireAuthMiddlewareList?.[callbackName]) {
124     middleware.push(requireAuthMiddlewareList?.[callbackName]);
125     }
126    
127     if (aacMiddlewareList?.[callbackName]) {
128     middleware.push(aacMiddlewareList?.[callbackName]);
129     }
130    
131     if (gacMiddlewareList?.[callbackName]) {
132     middleware.push(gacMiddlewareList?.[callbackName]);
133     }
134    
135     if (validationMiddlewareList?.[callbackName]) {
136     middleware.push(validationMiddlewareList?.[callbackName]);
137     }
138    
139     middleware.push(...(data.middleware ?? []));
140    
141     const wrappedMiddleware = middleware.map(
142     m => (request: ExpressRequest, response: ExpressResponse, next: NextFunction) =>
143     m(this.client, request, response, next)
144     );
145    
146     router[data.method.toLowerCase() as "head"].call(
147     router,
148     data.path,
149     ...(wrappedMiddleware as []),
150     <any>this.wrapControllerAction(controller, callbackName)
151     );
152    
153     log(`Discovered API Route: ${data.method} ${data.path} -- in ${controllerClass.name}`);
154     }
155     }
156     }
157    
158     wrapControllerAction(controller: Controller, callbackName: string) {
159     return async (request: ExpressRequest, response: ExpressResponse) => {
160     const callback = controller[callbackName as keyof typeof controller] as Function;
161     const controllerResponse = await callback.call(controller, request, response);
162    
163     if (!response.headersSent) {
164     if (controllerResponse instanceof Response) {
165     controllerResponse.send(response);
166     } else if (controllerResponse && typeof controllerResponse === "object") {
167     response.json(controllerResponse);
168     } else if (typeof controllerResponse === "string") {
169     response.send(controllerResponse);
170     } else if (typeof controllerResponse === "number") {
171     response.send(controllerResponse.toString());
172     } else {
173     logWarn("Invalid value was returned from the controller. Not sending a response.");
174     }
175     }
176     };
177     }
178    
179     onError(err: unknown, req: ExpressRequest, res: ExpressResponse, next: NextFunction) {
180     if (err instanceof SyntaxError && "status" in err && err.status === 400 && "body" in err) {
181     res.status(400).json({
182     error: "Invalid JSON payload"
183     });
184    
185     return;
186     }
187     }
188    
189     async start() {
190     this.expressServer = this.expressApp.listen(this.port, () => logInfo(`API server is listening at port ${this.port}`));
191     }
192     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26