@synchjs/ewb 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +292 -0
- package/dist/Components/ServeMemoryStore.d.ts +14 -0
- package/dist/Components/ServeMemoryStore.d.ts.map +1 -0
- package/dist/Components/ServeMemoryStore.js +77 -0
- package/dist/Components/Server.d.ts +48 -0
- package/dist/Components/Server.d.ts.map +1 -0
- package/dist/Components/Server.js +441 -0
- package/dist/Decorations/Authorized.d.ts +8 -0
- package/dist/Decorations/Authorized.d.ts.map +1 -0
- package/dist/Decorations/Authorized.js +20 -0
- package/dist/Decorations/Controller.d.ts +8 -0
- package/dist/Decorations/Controller.d.ts.map +1 -0
- package/dist/Decorations/Controller.js +21 -0
- package/dist/Decorations/Metadata.d.ts +30 -0
- package/dist/Decorations/Metadata.d.ts.map +1 -0
- package/dist/Decorations/Metadata.js +32 -0
- package/dist/Decorations/Methods.d.ts +8 -0
- package/dist/Decorations/Methods.d.ts.map +1 -0
- package/dist/Decorations/Methods.js +52 -0
- package/dist/Decorations/Middleware.d.ts +5 -0
- package/dist/Decorations/Middleware.d.ts.map +1 -0
- package/dist/Decorations/Middleware.js +19 -0
- package/dist/Decorations/Security.d.ts +9 -0
- package/dist/Decorations/Security.d.ts.map +1 -0
- package/dist/Decorations/Security.js +24 -0
- package/dist/Decorations/Serve.d.ts +2 -0
- package/dist/Decorations/Serve.d.ts.map +1 -0
- package/dist/Decorations/Serve.js +31 -0
- package/dist/Decorations/Tailwind.d.ts +7 -0
- package/dist/Decorations/Tailwind.d.ts.map +1 -0
- package/dist/Decorations/Tailwind.js +8 -0
- package/dist/Decorations/index.d.ts +9 -0
- package/dist/Decorations/index.d.ts.map +1 -0
- package/dist/Decorations/index.js +8 -0
- package/dist/globals.d.ts +6 -0
- package/dist/globals.d.ts.map +1 -0
- package/dist/globals.js +0 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/package.json +51 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import swaggerUi from "swagger-ui-express";
|
|
5
|
+
const swaggerJsdoc = require("swagger-jsdoc");
|
|
6
|
+
import helmet from "helmet";
|
|
7
|
+
import cors from "cors";
|
|
8
|
+
import rateLimit from "express-rate-limit";
|
|
9
|
+
import Ajv from "ajv";
|
|
10
|
+
import addFormats from "ajv-formats";
|
|
11
|
+
import { MetadataStorage } from "../Decorations";
|
|
12
|
+
import * as jwt from "jsonwebtoken";
|
|
13
|
+
import { AUTH_METADATA_KEY, PUBLIC_METADATA_KEY, } from "../Decorations/Authorized";
|
|
14
|
+
import { SECURITY_METADATA_KEY, } from "../Decorations/Security";
|
|
15
|
+
import { TAILWIND_METADATA_KEY, } from "../Decorations/Tailwind";
|
|
16
|
+
import { ServeMemoryStore } from "./ServeMemoryStore";
|
|
17
|
+
import boxen from "boxen";
|
|
18
|
+
import pc from "picocolors";
|
|
19
|
+
export class Server {
|
|
20
|
+
_app;
|
|
21
|
+
_port;
|
|
22
|
+
_controllersDir;
|
|
23
|
+
_id;
|
|
24
|
+
_enableSwagger;
|
|
25
|
+
_swaggerPath;
|
|
26
|
+
_enableLogging;
|
|
27
|
+
_securitySchemes;
|
|
28
|
+
_ajv;
|
|
29
|
+
_securityHandlers;
|
|
30
|
+
_container;
|
|
31
|
+
_serverInstance;
|
|
32
|
+
// Stats
|
|
33
|
+
_controllerCount = 0;
|
|
34
|
+
_routeCount = 0;
|
|
35
|
+
_tailwindEnabled = false;
|
|
36
|
+
constructor(options) {
|
|
37
|
+
this._port = options.port;
|
|
38
|
+
this._app = express();
|
|
39
|
+
this._id = options.id;
|
|
40
|
+
this._enableSwagger = options.enableSwagger || false;
|
|
41
|
+
this._swaggerPath = options.swaggerPath || "/api-docs";
|
|
42
|
+
this._enableLogging =
|
|
43
|
+
options.logging === undefined ? true : options.logging;
|
|
44
|
+
this._controllersDir = options.controllersDir || "controllers";
|
|
45
|
+
this._securityHandlers = options.securityHandlers || {};
|
|
46
|
+
this._securitySchemes = options.securitySchemes;
|
|
47
|
+
this._container = options.container;
|
|
48
|
+
// Initialize AJV
|
|
49
|
+
this._ajv = new Ajv({ allErrors: true, strict: false });
|
|
50
|
+
addFormats(this._ajv);
|
|
51
|
+
// Security Middleware
|
|
52
|
+
this._app.use(helmet(options.helmetOptions));
|
|
53
|
+
this._app.use(cors(options.corsOptions));
|
|
54
|
+
this._app.use(rateLimit(options.rateLimitOptions || {
|
|
55
|
+
windowMs: 15 * 60 * 1000,
|
|
56
|
+
max: 100,
|
|
57
|
+
}));
|
|
58
|
+
this._app.use(express.json());
|
|
59
|
+
global.servers.set(this._id, this._app);
|
|
60
|
+
}
|
|
61
|
+
log(message) {
|
|
62
|
+
if (this._enableLogging) {
|
|
63
|
+
console.log(message);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async init() {
|
|
67
|
+
await this.loadControllers();
|
|
68
|
+
if (this._enableSwagger) {
|
|
69
|
+
this.setupSwagger();
|
|
70
|
+
}
|
|
71
|
+
// Serve Static Assets from Memory Store
|
|
72
|
+
this._app.use((req, res, next) => {
|
|
73
|
+
if (req.method !== "GET" && req.method !== "HEAD")
|
|
74
|
+
return next();
|
|
75
|
+
const asset = ServeMemoryStore.instance.getAsset(req.path);
|
|
76
|
+
if (asset) {
|
|
77
|
+
res.type(asset.type).send(Buffer.from(asset.content));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
next();
|
|
81
|
+
});
|
|
82
|
+
// Global Error Handler
|
|
83
|
+
this._app.use((err, req, res, next) => {
|
|
84
|
+
console.error(`[${this._id}] Error:`, err);
|
|
85
|
+
res.status(err.status || 500).json({
|
|
86
|
+
error: "Internal Server Error",
|
|
87
|
+
message: "An unexpected error occurred",
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
this._serverInstance = this._app.listen(this._port, () => {
|
|
91
|
+
if (this._enableLogging) {
|
|
92
|
+
this.printStartupMessage();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
printStartupMessage() {
|
|
97
|
+
const pad = (str) => str.padEnd(15);
|
|
98
|
+
const lines = [
|
|
99
|
+
pc.green(`Server '${this._id}' is running!`),
|
|
100
|
+
"",
|
|
101
|
+
`${pc.bold(pad("Port:"))}${this._port}`,
|
|
102
|
+
`${pc.bold(pad("PID:"))}${process.pid}`,
|
|
103
|
+
`${pc.bold(pad("Controllers:"))}${this._controllerCount}`,
|
|
104
|
+
`${pc.bold(pad("Routes:"))}${this._routeCount}`,
|
|
105
|
+
`${pc.bold(pad("Tailwind:"))}${this._tailwindEnabled ? pc.blue("Enabled") : pc.dim("Disabled")}`,
|
|
106
|
+
];
|
|
107
|
+
if (this._enableSwagger) {
|
|
108
|
+
lines.push(`${pc.bold(pad("Swagger:"))}http://localhost:${this._port}${this._swaggerPath}`);
|
|
109
|
+
}
|
|
110
|
+
console.log(boxen(lines.join("\n"), {
|
|
111
|
+
padding: 1,
|
|
112
|
+
margin: 1,
|
|
113
|
+
borderStyle: "round",
|
|
114
|
+
borderColor: "green",
|
|
115
|
+
title: "Server Status",
|
|
116
|
+
titleAlignment: "center",
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
close() {
|
|
120
|
+
if (this._serverInstance) {
|
|
121
|
+
this._serverInstance.close(() => {
|
|
122
|
+
this.log(`Server '${this._id}' stopped.`);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
global.servers.delete(this._id);
|
|
126
|
+
}
|
|
127
|
+
async loadControllers() {
|
|
128
|
+
const absoluteControllersPath = path.resolve(process.cwd(), this._controllersDir);
|
|
129
|
+
if (!fs.existsSync(absoluteControllersPath)) {
|
|
130
|
+
console.warn(`Controllers directory not found: ${absoluteControllersPath}`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const files = fs
|
|
134
|
+
.readdirSync(absoluteControllersPath)
|
|
135
|
+
.filter((file) => file.endsWith(".ts") || file.endsWith(".js"));
|
|
136
|
+
for (const file of files) {
|
|
137
|
+
const filePath = path.join(absoluteControllersPath, file);
|
|
138
|
+
await import(filePath);
|
|
139
|
+
}
|
|
140
|
+
const controllers = MetadataStorage.instance.getControllers();
|
|
141
|
+
for (const controller of controllers) {
|
|
142
|
+
if (controller.serverIds && !controller.serverIds.includes(this._id)) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
this._controllerCount++;
|
|
146
|
+
// Check for Tailwind metadata
|
|
147
|
+
const tailwindOptions = Reflect.getMetadata(TAILWIND_METADATA_KEY, controller.target);
|
|
148
|
+
if (tailwindOptions?.enable) {
|
|
149
|
+
this._tailwindEnabled = true;
|
|
150
|
+
}
|
|
151
|
+
const instance = this._container?.get
|
|
152
|
+
? this._container.get(controller.target)
|
|
153
|
+
: new controller.target();
|
|
154
|
+
// Check class level auth
|
|
155
|
+
const classAuthInfo = Reflect.getMetadata(AUTH_METADATA_KEY, controller.target);
|
|
156
|
+
for (const route of controller.routes) {
|
|
157
|
+
const fullPath = (controller.path + "/" + route.path).replace(/\/+/g, "/");
|
|
158
|
+
const middlewares = [];
|
|
159
|
+
// Check method level metadata
|
|
160
|
+
const methodAuthInfo = Reflect.getMetadata(AUTH_METADATA_KEY, controller.target.prototype, route.handlerName);
|
|
161
|
+
const isPublic = Reflect.getMetadata(PUBLIC_METADATA_KEY, controller.target.prototype, route.handlerName);
|
|
162
|
+
// Determine if auth is required and which secret to use
|
|
163
|
+
let authSecret;
|
|
164
|
+
if (isPublic) {
|
|
165
|
+
authSecret = undefined;
|
|
166
|
+
}
|
|
167
|
+
else if (methodAuthInfo) {
|
|
168
|
+
authSecret = methodAuthInfo.secret || process.env.JWT_SECRET;
|
|
169
|
+
}
|
|
170
|
+
else if (classAuthInfo) {
|
|
171
|
+
authSecret = classAuthInfo.secret || process.env.JWT_SECRET;
|
|
172
|
+
}
|
|
173
|
+
// 1. Auth Middleware (New Decorator Logic)
|
|
174
|
+
if (authSecret) {
|
|
175
|
+
middlewares.push(this.createJwtMiddleware(authSecret));
|
|
176
|
+
// Inject Swagger security definition automatically
|
|
177
|
+
if (!route.swagger) {
|
|
178
|
+
route.swagger = {
|
|
179
|
+
responses: {
|
|
180
|
+
200: {
|
|
181
|
+
description: "Default response",
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
if (!route.swagger.security) {
|
|
187
|
+
route.swagger.security = [];
|
|
188
|
+
}
|
|
189
|
+
// specific check to avoid duplicating if user manually added it
|
|
190
|
+
const hasBearer = route.swagger.security.some((s) => "bearerAuth" in s);
|
|
191
|
+
if (!hasBearer) {
|
|
192
|
+
route.swagger.security.push({ bearerAuth: [] });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// 2. Generic Security Middleware (@Security, @OAuth, etc)
|
|
196
|
+
const classGenericSecurity = Reflect.getMetadata(SECURITY_METADATA_KEY, controller.target) || [];
|
|
197
|
+
const methodGenericSecurity = Reflect.getMetadata(SECURITY_METADATA_KEY, controller.target.prototype, route.handlerName) || [];
|
|
198
|
+
let genericRequirements = [];
|
|
199
|
+
if (isPublic) {
|
|
200
|
+
// If public, we might want to skip class generic security too?
|
|
201
|
+
// Usually yes.
|
|
202
|
+
genericRequirements = [...methodGenericSecurity];
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
genericRequirements = [
|
|
206
|
+
...classGenericSecurity,
|
|
207
|
+
...methodGenericSecurity,
|
|
208
|
+
];
|
|
209
|
+
}
|
|
210
|
+
// Add to Swagger
|
|
211
|
+
if (genericRequirements.length > 0) {
|
|
212
|
+
if (!route.swagger) {
|
|
213
|
+
route.swagger = {
|
|
214
|
+
responses: {
|
|
215
|
+
200: { description: "Default response" },
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
if (!route.swagger.security) {
|
|
220
|
+
route.swagger.security = [];
|
|
221
|
+
}
|
|
222
|
+
route.swagger.security.push(...genericRequirements);
|
|
223
|
+
}
|
|
224
|
+
// 3. Auth Middleware (Old logic - support existing manual config + generic)
|
|
225
|
+
const manualRequirements = [
|
|
226
|
+
...(route.swagger?.security || []),
|
|
227
|
+
...(controller.security || []),
|
|
228
|
+
];
|
|
229
|
+
// We already pushed genericRequirements to route.swagger.security, so manualRequirements includes them?
|
|
230
|
+
// Wait, route.swagger.security is updated above.
|
|
231
|
+
// So manualRequirements now has everything including bearerAuth (pushed above) and generic (pushed above).
|
|
232
|
+
// BUT, controller.security (from @Controller options) might duplicate classGenericSecurity?
|
|
233
|
+
// Let's rely on route.swagger.security + controller.security.
|
|
234
|
+
// And we should filter out duplicates if necessary, but Swagger UI handles it usually (OR logic between items in array).
|
|
235
|
+
// Actually, genericRequirements are added to route.swagger.security.
|
|
236
|
+
// So we can just use route.swagger.security.
|
|
237
|
+
// However, controller.security is separate.
|
|
238
|
+
const allSecurityRequirements = [
|
|
239
|
+
...(route.swagger?.security || []),
|
|
240
|
+
...(controller.security || []),
|
|
241
|
+
];
|
|
242
|
+
// Filter out bearerAuth if handled by createJwtMiddleware (to avoid double check if handler is missing)
|
|
243
|
+
// createJwtMiddleware handles 'bearerAuth'.
|
|
244
|
+
// createAuthMiddleware handles everything else via securityHandlers.
|
|
245
|
+
// If we have 'bearerAuth' in requirements, createAuthMiddleware will look for a handler for 'bearerAuth'.
|
|
246
|
+
// If user didn't provide one, it warns.
|
|
247
|
+
// We should probably allow 'bearerAuth' in createAuthMiddleware IF user provided a handler.
|
|
248
|
+
// But we added createJwtMiddleware explicitly.
|
|
249
|
+
// So we should filter 'bearerAuth' out of requirements passed to createAuthMiddleware UNLESS user provided a handler for it?
|
|
250
|
+
// Or just let it run. If user provides handler, it runs twice?
|
|
251
|
+
// createJwtMiddleware is added if `authSecret` is true (from @BearerAuth).
|
|
252
|
+
// If `bearerAuth` is in swagger because of @BearerAuth, we risk running twice if user adds handler.
|
|
253
|
+
// But `@BearerAuth` logic uses `createJwtMiddleware`.
|
|
254
|
+
// The `genericRequirements` logic adds to swagger.
|
|
255
|
+
// Let's assume standard usage: use @BearerAuth OR @Security("bearerAuth"). Not both.
|
|
256
|
+
if (allSecurityRequirements.length > 0) {
|
|
257
|
+
middlewares.push(this.createAuthMiddleware(allSecurityRequirements));
|
|
258
|
+
}
|
|
259
|
+
// 4. Validation Middleware
|
|
260
|
+
// 2. Validation Middleware
|
|
261
|
+
if (route.swagger?.requestBody &&
|
|
262
|
+
route.swagger.requestBody.content?.["application/json"]
|
|
263
|
+
?.schema) {
|
|
264
|
+
const schema = route.swagger.requestBody.content["application/json"].schema;
|
|
265
|
+
middlewares.push(this.createValidationMiddleware(schema));
|
|
266
|
+
}
|
|
267
|
+
// 3. Custom Middlewares
|
|
268
|
+
// Controller Level
|
|
269
|
+
if (controller.middlewares) {
|
|
270
|
+
middlewares.push(...controller.middlewares);
|
|
271
|
+
}
|
|
272
|
+
// Route Level
|
|
273
|
+
if (route.middlewares) {
|
|
274
|
+
middlewares.push(...route.middlewares);
|
|
275
|
+
}
|
|
276
|
+
// 3. Route Handler
|
|
277
|
+
const handler = (req, res, next) => {
|
|
278
|
+
try {
|
|
279
|
+
const result = instance[route.handlerName](req, res, next);
|
|
280
|
+
const handleResult = (val) => {
|
|
281
|
+
if (val !== undefined && !res.headersSent) {
|
|
282
|
+
if (typeof val === "string") {
|
|
283
|
+
res.send(val);
|
|
284
|
+
}
|
|
285
|
+
else if (typeof val === "object") {
|
|
286
|
+
res.json(val);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
if (result instanceof Promise) {
|
|
291
|
+
result.then(handleResult).catch(next);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
handleResult(result);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
next(error);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
this._app[route.method](fullPath, ...middlewares, handler);
|
|
302
|
+
this._routeCount++;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
createJwtMiddleware(secret) {
|
|
307
|
+
return (req, res, next) => {
|
|
308
|
+
const authHeader = req.headers.authorization;
|
|
309
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
310
|
+
return res
|
|
311
|
+
.status(401)
|
|
312
|
+
.json({ error: "Unauthorized: Missing Bearer token" });
|
|
313
|
+
}
|
|
314
|
+
const token = authHeader.split(" ")[1];
|
|
315
|
+
try {
|
|
316
|
+
const decoded = jwt.verify(token, secret);
|
|
317
|
+
req.user = decoded; // Attach user to request
|
|
318
|
+
next();
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
return res.status(403).json({ error: "Forbidden: Invalid token" });
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
createAuthMiddleware(requirements) {
|
|
326
|
+
return (req, res, next) => {
|
|
327
|
+
if (requirements.length === 0)
|
|
328
|
+
return next();
|
|
329
|
+
let requirementIndex = 0;
|
|
330
|
+
const checkRequirement = (index) => {
|
|
331
|
+
if (index >= requirements.length) {
|
|
332
|
+
// All requirements failed
|
|
333
|
+
return res.status(401).json({ error: "Unauthorized" });
|
|
334
|
+
}
|
|
335
|
+
const requirement = requirements[index];
|
|
336
|
+
const schemes = Object.keys(requirement);
|
|
337
|
+
if (schemes.length === 0) {
|
|
338
|
+
// Empty requirement means public access is allowed
|
|
339
|
+
return next();
|
|
340
|
+
}
|
|
341
|
+
const checkScheme = (schemeIndex) => {
|
|
342
|
+
if (schemeIndex >= schemes.length) {
|
|
343
|
+
// All schemes in this requirement passed
|
|
344
|
+
return next();
|
|
345
|
+
}
|
|
346
|
+
const scheme = schemes[schemeIndex];
|
|
347
|
+
const handler = this._securityHandlers[scheme];
|
|
348
|
+
if (!handler) {
|
|
349
|
+
console.warn(`[${this._id}] Security handler for '${scheme}' not configured.`);
|
|
350
|
+
// If handler is missing, this scheme fails. Ideally, we should treat it as an error.
|
|
351
|
+
// But since this is one of potentially many AND schemes, failure here means this requirement fails.
|
|
352
|
+
// Move to next requirement.
|
|
353
|
+
return checkRequirement(index + 1);
|
|
354
|
+
}
|
|
355
|
+
handler(req, res, (err) => {
|
|
356
|
+
if (err) {
|
|
357
|
+
// This scheme failed, so the whole requirement fails.
|
|
358
|
+
// Move to the next requirement (OR logic).
|
|
359
|
+
return checkRequirement(index + 1);
|
|
360
|
+
}
|
|
361
|
+
// This scheme passed, move to the next scheme in the same requirement (AND logic).
|
|
362
|
+
checkScheme(schemeIndex + 1);
|
|
363
|
+
});
|
|
364
|
+
};
|
|
365
|
+
checkScheme(0);
|
|
366
|
+
};
|
|
367
|
+
checkRequirement(0);
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
createValidationMiddleware(schema) {
|
|
371
|
+
const validate = this._ajv.compile(schema);
|
|
372
|
+
return (req, res, next) => {
|
|
373
|
+
const valid = validate(req.body);
|
|
374
|
+
if (!valid) {
|
|
375
|
+
return res.status(400).json({
|
|
376
|
+
error: "Validation Error",
|
|
377
|
+
details: validate.errors,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
next();
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
setupSwagger() {
|
|
384
|
+
const controllers = MetadataStorage.instance
|
|
385
|
+
.getControllers()
|
|
386
|
+
.filter((c) => !c.serverIds || c.serverIds.includes(this._id));
|
|
387
|
+
const paths = {};
|
|
388
|
+
for (const controller of controllers) {
|
|
389
|
+
for (const route of controller.routes) {
|
|
390
|
+
const fullPath = (controller.path + "/" + route.path)
|
|
391
|
+
.replace(/\/+/g, "/")
|
|
392
|
+
.replace(/:([^/]+)/g, "{$1}");
|
|
393
|
+
if (!paths[fullPath]) {
|
|
394
|
+
paths[fullPath] = {};
|
|
395
|
+
}
|
|
396
|
+
const operation = route.swagger
|
|
397
|
+
? { ...route.swagger }
|
|
398
|
+
: {
|
|
399
|
+
responses: {
|
|
400
|
+
200: {
|
|
401
|
+
description: "Default response",
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
if (controller.tags) {
|
|
406
|
+
operation.tags = [...(operation.tags || []), ...controller.tags];
|
|
407
|
+
}
|
|
408
|
+
if (controller.security) {
|
|
409
|
+
operation.security = [
|
|
410
|
+
...(operation.security || []),
|
|
411
|
+
...controller.security,
|
|
412
|
+
];
|
|
413
|
+
}
|
|
414
|
+
paths[fullPath][route.method] = operation;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const options = {
|
|
418
|
+
definition: {
|
|
419
|
+
openapi: "3.0.0",
|
|
420
|
+
info: {
|
|
421
|
+
title: `Server ${this._id} API`,
|
|
422
|
+
version: "1.0.0",
|
|
423
|
+
},
|
|
424
|
+
paths: paths,
|
|
425
|
+
components: {
|
|
426
|
+
securitySchemes: {
|
|
427
|
+
bearerAuth: {
|
|
428
|
+
type: "http",
|
|
429
|
+
scheme: "bearer",
|
|
430
|
+
bearerFormat: "JWT",
|
|
431
|
+
},
|
|
432
|
+
...this._securitySchemes,
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
apis: [],
|
|
437
|
+
};
|
|
438
|
+
const specs = swaggerJsdoc(options);
|
|
439
|
+
this._app.use(this._swaggerPath, swaggerUi.serve, swaggerUi.setup(specs));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const AUTH_METADATA_KEY = "auth:info";
|
|
2
|
+
export declare const PUBLIC_METADATA_KEY = "auth:public";
|
|
3
|
+
export interface AuthInfo {
|
|
4
|
+
secret?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function BearerAuth(secret?: string): MethodDecorator & ClassDecorator;
|
|
7
|
+
export declare function Public(): MethodDecorator;
|
|
8
|
+
//# sourceMappingURL=Authorized.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Authorized.d.ts","sourceRoot":"","sources":["../../src/Decorations/Authorized.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAC7C,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AAEjD,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,cAAc,CAmB5E;AAED,wBAAgB,MAAM,IAAI,eAAe,CAQxC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Key to store auth metadata
|
|
2
|
+
export const AUTH_METADATA_KEY = "auth:info";
|
|
3
|
+
export const PUBLIC_METADATA_KEY = "auth:public";
|
|
4
|
+
export function BearerAuth(secret) {
|
|
5
|
+
return function (target, propertyKey, descriptor) {
|
|
6
|
+
// If used on a method
|
|
7
|
+
if (propertyKey) {
|
|
8
|
+
Reflect.defineMetadata(AUTH_METADATA_KEY, { secret }, target, propertyKey);
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
// If used on a class
|
|
12
|
+
Reflect.defineMetadata(AUTH_METADATA_KEY, { secret }, target);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function Public() {
|
|
17
|
+
return function (target, propertyKey, descriptor) {
|
|
18
|
+
Reflect.defineMetadata(PUBLIC_METADATA_KEY, true, target, propertyKey);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { OpenAPIV3 } from "openapi-types";
|
|
2
|
+
export interface ControllerOptions {
|
|
3
|
+
serverIds?: string[];
|
|
4
|
+
tags?: string[];
|
|
5
|
+
security?: OpenAPIV3.SecurityRequirementObject[];
|
|
6
|
+
}
|
|
7
|
+
export declare function Controller(path: string, options?: ControllerOptions): (target: Function) => void;
|
|
8
|
+
//# sourceMappingURL=Controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Controller.d.ts","sourceRoot":"","sources":["../../src/Decorations/Controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAG1C,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,SAAS,CAAC,yBAAyB,EAAE,CAAC;CAClD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,IACjD,QAAQ,QAAQ,UAyBlC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { MetadataStorage } from "./Metadata";
|
|
2
|
+
export function Controller(path, options) {
|
|
3
|
+
return function (target) {
|
|
4
|
+
const routes = MetadataStorage.instance.getRoutes(target);
|
|
5
|
+
const controllerMiddlewares = Reflect.getMetadata("middlewares", target) || [];
|
|
6
|
+
// Inject middleware into routes
|
|
7
|
+
routes.forEach((route) => {
|
|
8
|
+
route.middlewares =
|
|
9
|
+
Reflect.getMetadata("middlewares", target.prototype, route.handlerName) || [];
|
|
10
|
+
});
|
|
11
|
+
MetadataStorage.instance.addController({
|
|
12
|
+
target: target,
|
|
13
|
+
path: path,
|
|
14
|
+
serverIds: options?.serverIds,
|
|
15
|
+
tags: options?.tags,
|
|
16
|
+
security: options?.security,
|
|
17
|
+
middlewares: controllerMiddlewares,
|
|
18
|
+
routes: routes,
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
import type { RequestHandler } from "express";
|
|
3
|
+
import { OpenAPIV3 } from "openapi-types";
|
|
4
|
+
export interface RouteDefinition {
|
|
5
|
+
method: "get" | "post" | "put" | "delete" | "patch";
|
|
6
|
+
path: string;
|
|
7
|
+
handlerName: string;
|
|
8
|
+
swagger?: OpenAPIV3.OperationObject;
|
|
9
|
+
middlewares?: RequestHandler[];
|
|
10
|
+
}
|
|
11
|
+
export interface ControllerDefinition {
|
|
12
|
+
target: any;
|
|
13
|
+
path: string;
|
|
14
|
+
serverIds?: string[];
|
|
15
|
+
routes: RouteDefinition[];
|
|
16
|
+
tags?: string[];
|
|
17
|
+
security?: OpenAPIV3.SecurityRequirementObject[];
|
|
18
|
+
middlewares?: RequestHandler[];
|
|
19
|
+
}
|
|
20
|
+
export declare class MetadataStorage {
|
|
21
|
+
private static _instance;
|
|
22
|
+
private _controllers;
|
|
23
|
+
private constructor();
|
|
24
|
+
static get instance(): MetadataStorage;
|
|
25
|
+
addController(controller: ControllerDefinition): void;
|
|
26
|
+
getControllers(): ControllerDefinition[];
|
|
27
|
+
addRoute(target: any, route: RouteDefinition): void;
|
|
28
|
+
getRoutes(target: any): RouteDefinition[];
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=Metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Metadata.d.ts","sourceRoot":"","sources":["../../src/Decorations/Metadata.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,SAAS,CAAC,eAAe,CAAC;IACpC,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,GAAG,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,SAAS,CAAC,yBAAyB,EAAE,CAAC;IACjD,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;CAChC;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAC,SAAS,CAAkB;IAC1C,OAAO,CAAC,YAAY,CAA8B;IAElD,OAAO;IAEP,WAAkB,QAAQ,IAAI,eAAe,CAK5C;IAEM,aAAa,CAAC,UAAU,EAAE,oBAAoB;IAI9C,cAAc,IAAI,oBAAoB,EAAE;IAIxC,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,eAAe;IAc5C,SAAS,CAAC,MAAM,EAAE,GAAG,GAAG,eAAe,EAAE;CAGjD"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
export class MetadataStorage {
|
|
3
|
+
static _instance;
|
|
4
|
+
_controllers = [];
|
|
5
|
+
constructor() { }
|
|
6
|
+
static get instance() {
|
|
7
|
+
if (!MetadataStorage._instance) {
|
|
8
|
+
MetadataStorage._instance = new MetadataStorage();
|
|
9
|
+
}
|
|
10
|
+
return MetadataStorage._instance;
|
|
11
|
+
}
|
|
12
|
+
addController(controller) {
|
|
13
|
+
this._controllers.push(controller);
|
|
14
|
+
}
|
|
15
|
+
getControllers() {
|
|
16
|
+
return this._controllers;
|
|
17
|
+
}
|
|
18
|
+
addRoute(target, route) {
|
|
19
|
+
const existingController = this._controllers.find((c) => c.target === target);
|
|
20
|
+
if (existingController) {
|
|
21
|
+
existingController.routes.push(route);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const routes = Reflect.getMetadata("routes", target) || [];
|
|
25
|
+
routes.push(route);
|
|
26
|
+
Reflect.defineMetadata("routes", routes, target);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
getRoutes(target) {
|
|
30
|
+
return Reflect.getMetadata("routes", target) || [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
import { OpenAPIV3 } from "openapi-types";
|
|
3
|
+
export declare function Get(path: string, swagger?: OpenAPIV3.OperationObject): MethodDecorator;
|
|
4
|
+
export declare function Post(path: string, swagger?: OpenAPIV3.OperationObject): MethodDecorator;
|
|
5
|
+
export declare function Put(path: string, swagger?: OpenAPIV3.OperationObject): MethodDecorator;
|
|
6
|
+
export declare function Delete(path: string, swagger?: OpenAPIV3.OperationObject): MethodDecorator;
|
|
7
|
+
export declare function Patch(path: string, swagger?: OpenAPIV3.OperationObject): MethodDecorator;
|
|
8
|
+
//# sourceMappingURL=Methods.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Methods.d.ts","sourceRoot":"","sources":["../../src/Decorations/Methods.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,wBAAgB,GAAG,CACjB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,SAAS,CAAC,eAAe,GAClC,eAAe,CAajB;AAED,wBAAgB,IAAI,CAClB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,SAAS,CAAC,eAAe,GAClC,eAAe,CAajB;AAED,wBAAgB,GAAG,CACjB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,SAAS,CAAC,eAAe,GAClC,eAAe,CAajB;AAED,wBAAgB,MAAM,CACpB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,SAAS,CAAC,eAAe,GAClC,eAAe,CAajB;AAED,wBAAgB,KAAK,CACnB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,SAAS,CAAC,eAAe,GAClC,eAAe,CAajB"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
import { MetadataStorage } from "./Metadata";
|
|
3
|
+
export function Get(path, swagger) {
|
|
4
|
+
return function (target, propertyKey, descriptor) {
|
|
5
|
+
MetadataStorage.instance.addRoute(target.constructor, {
|
|
6
|
+
method: "get",
|
|
7
|
+
path,
|
|
8
|
+
handlerName: propertyKey,
|
|
9
|
+
swagger,
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function Post(path, swagger) {
|
|
14
|
+
return function (target, propertyKey, descriptor) {
|
|
15
|
+
MetadataStorage.instance.addRoute(target.constructor, {
|
|
16
|
+
method: "post",
|
|
17
|
+
path,
|
|
18
|
+
handlerName: propertyKey,
|
|
19
|
+
swagger,
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function Put(path, swagger) {
|
|
24
|
+
return function (target, propertyKey, descriptor) {
|
|
25
|
+
MetadataStorage.instance.addRoute(target.constructor, {
|
|
26
|
+
method: "put",
|
|
27
|
+
path,
|
|
28
|
+
handlerName: propertyKey,
|
|
29
|
+
swagger,
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function Delete(path, swagger) {
|
|
34
|
+
return function (target, propertyKey, descriptor) {
|
|
35
|
+
MetadataStorage.instance.addRoute(target.constructor, {
|
|
36
|
+
method: "delete",
|
|
37
|
+
path,
|
|
38
|
+
handlerName: propertyKey,
|
|
39
|
+
swagger,
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function Patch(path, swagger) {
|
|
44
|
+
return function (target, propertyKey, descriptor) {
|
|
45
|
+
MetadataStorage.instance.addRoute(target.constructor, {
|
|
46
|
+
method: "patch",
|
|
47
|
+
path,
|
|
48
|
+
handlerName: propertyKey,
|
|
49
|
+
swagger,
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
import type { RequestHandler } from "express";
|
|
3
|
+
export declare function Use(...middlewares: RequestHandler[]): (target: any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor) => void;
|
|
4
|
+
export declare function Middleware(...middlewares: RequestHandler[]): (target: any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor) => void;
|
|
5
|
+
//# sourceMappingURL=Middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Middleware.d.ts","sourceRoot":"","sources":["../../src/Decorations/Middleware.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,wBAAgB,GAAG,CAAC,GAAG,WAAW,EAAE,cAAc,EAAE,IAEhD,QAAQ,GAAG,EACX,cAAc,MAAM,GAAG,MAAM,EAC7B,aAAa,kBAAkB,UAuBlC;AACD,wBAAgB,UAAU,CAAC,GAAG,WAAW,EAAE,cAAc,EAAE,YA1B/C,GAAG,gBACG,MAAM,GAAG,MAAM,eAChB,kBAAkB,UA0BlC"}
|