opticore-api-gateway 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 +618 -0
- package/dist/index.cjs +1100 -0
- package/dist/index.d.cts +329 -0
- package/dist/index.d.ts +329 -0
- package/dist/index.js +1056 -0
- package/dist/utils/translations/message.translation.en.json +5 -0
- package/dist/utils/translations/message.translation.fr.json +5 -0
- package/package.json +50 -0
- package/scripts/postinstall.cjs +127 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1056 @@
|
|
|
1
|
+
// src/core/gateway.core.ts
|
|
2
|
+
import * as http2 from "http";
|
|
3
|
+
import { OpticoreRegisterRouter, OpticoreStandaloneRouterFactory as OpticoreStandaloneRouterFactory2 } from "opticore-router";
|
|
4
|
+
|
|
5
|
+
// src/infrastructure/services/strategies/serviceRegistry.strategy.ts
|
|
6
|
+
var ServiceRegistry = class {
|
|
7
|
+
services = /* @__PURE__ */ new Map();
|
|
8
|
+
serviceHealthChecks = /* @__PURE__ */ new Map();
|
|
9
|
+
circuitBreakers = /* @__PURE__ */ new Map();
|
|
10
|
+
registerService(service) {
|
|
11
|
+
if (!this.services.has(service.name)) {
|
|
12
|
+
this.services.set(service.name, []);
|
|
13
|
+
}
|
|
14
|
+
const instance = {
|
|
15
|
+
...service,
|
|
16
|
+
healthy: true,
|
|
17
|
+
currentConnections: 0,
|
|
18
|
+
lastHealthCheck: /* @__PURE__ */ new Date(),
|
|
19
|
+
failureCount: 0,
|
|
20
|
+
circuitState: "closed"
|
|
21
|
+
};
|
|
22
|
+
this.services.get(service.name).push(instance);
|
|
23
|
+
this.circuitBreakers.set(`${service.name}-${service.url}`, new CircuitBreaker());
|
|
24
|
+
if (service.healthCheck) {
|
|
25
|
+
this.startHealthCheck(service.name, instance);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
startHealthCheck(serviceName, instance) {
|
|
29
|
+
const healthCheck = setInterval(async () => {
|
|
30
|
+
try {
|
|
31
|
+
instance.healthy = true;
|
|
32
|
+
instance.lastHealthCheck = /* @__PURE__ */ new Date();
|
|
33
|
+
} catch (error) {
|
|
34
|
+
instance.healthy = false;
|
|
35
|
+
console.error(`Health check failed for ${serviceName}: ${error}`);
|
|
36
|
+
}
|
|
37
|
+
}, 3e4);
|
|
38
|
+
this.serviceHealthChecks.set(`${serviceName}-${instance.url}`, healthCheck);
|
|
39
|
+
}
|
|
40
|
+
getServiceInstances(serviceName) {
|
|
41
|
+
return this.services.get(serviceName) || [];
|
|
42
|
+
}
|
|
43
|
+
getHealthyInstances(serviceName) {
|
|
44
|
+
const instances = this.services.get(serviceName) || [];
|
|
45
|
+
const now = /* @__PURE__ */ new Date();
|
|
46
|
+
return instances.filter((instance) => {
|
|
47
|
+
const circuitBreaker = this.circuitBreakers.get(`${serviceName}-${instance.url}`);
|
|
48
|
+
if (circuitBreaker && !circuitBreaker.allowRequest()) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return instance.healthy;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
recordSuccess(serviceName, url) {
|
|
55
|
+
const circuitBreaker = this.circuitBreakers.get(`${serviceName}-${url}`);
|
|
56
|
+
if (circuitBreaker) {
|
|
57
|
+
circuitBreaker.recordSuccess();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
recordFailure(serviceName, url) {
|
|
61
|
+
const circuitBreaker = this.circuitBreakers.get(`${serviceName}-${url}`);
|
|
62
|
+
if (circuitBreaker) {
|
|
63
|
+
circuitBreaker.recordFailure();
|
|
64
|
+
}
|
|
65
|
+
const instances = this.services.get(serviceName);
|
|
66
|
+
if (instances) {
|
|
67
|
+
const instance = instances.find((inst) => inst.url === url);
|
|
68
|
+
if (instance) {
|
|
69
|
+
instance.failureCount++;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
deregisterService(serviceName, url) {
|
|
74
|
+
const instances = this.services.get(serviceName);
|
|
75
|
+
if (instances) {
|
|
76
|
+
const filtered = instances.filter((instance) => instance.url !== url);
|
|
77
|
+
this.services.set(serviceName, filtered);
|
|
78
|
+
const healthCheckKey = `${serviceName}-${url}`;
|
|
79
|
+
const interval = this.serviceHealthChecks.get(healthCheckKey);
|
|
80
|
+
if (interval) {
|
|
81
|
+
clearInterval(interval);
|
|
82
|
+
this.serviceHealthChecks.delete(healthCheckKey);
|
|
83
|
+
}
|
|
84
|
+
this.circuitBreakers.delete(healthCheckKey);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
getAllServices() {
|
|
88
|
+
return new Map(this.services);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var CircuitBreaker = class {
|
|
92
|
+
state = "closed";
|
|
93
|
+
failureCount = 0;
|
|
94
|
+
successCount = 0;
|
|
95
|
+
lastFailureTime = null;
|
|
96
|
+
failureThreshold = 5;
|
|
97
|
+
resetTimeout = 3e4;
|
|
98
|
+
halfOpenMaxAttempts = 3;
|
|
99
|
+
allowRequest() {
|
|
100
|
+
if (this.state === "closed") {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
if (this.state === "open") {
|
|
104
|
+
if (this.lastFailureTime) {
|
|
105
|
+
const now = /* @__PURE__ */ new Date();
|
|
106
|
+
const timeSinceFailure = now.getTime() - this.lastFailureTime.getTime();
|
|
107
|
+
if (timeSinceFailure > this.resetTimeout) {
|
|
108
|
+
this.state = "half-open";
|
|
109
|
+
this.successCount = 0;
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return this.successCount < this.halfOpenMaxAttempts;
|
|
116
|
+
}
|
|
117
|
+
recordSuccess() {
|
|
118
|
+
if (this.state === "half-open") {
|
|
119
|
+
this.successCount++;
|
|
120
|
+
if (this.successCount >= this.halfOpenMaxAttempts) {
|
|
121
|
+
this.reset();
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
this.failureCount = Math.max(0, this.failureCount - 1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
recordFailure() {
|
|
128
|
+
this.failureCount++;
|
|
129
|
+
this.lastFailureTime = /* @__PURE__ */ new Date();
|
|
130
|
+
if (this.failureCount >= this.failureThreshold) {
|
|
131
|
+
this.state = "open";
|
|
132
|
+
} else if (this.state === "half-open") {
|
|
133
|
+
this.state = "open";
|
|
134
|
+
this.successCount = 0;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
reset() {
|
|
138
|
+
this.state = "closed";
|
|
139
|
+
this.failureCount = 0;
|
|
140
|
+
this.successCount = 0;
|
|
141
|
+
this.lastFailureTime = null;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// src/infrastructure/services/strategies/loadBalancer.strategy.ts
|
|
146
|
+
var LoadBalancer = class {
|
|
147
|
+
strategy;
|
|
148
|
+
counters = /* @__PURE__ */ new Map();
|
|
149
|
+
constructor(strategy = "round-robin") {
|
|
150
|
+
this.strategy = strategy;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
*
|
|
154
|
+
* @param serviceName
|
|
155
|
+
* @param instances
|
|
156
|
+
*/
|
|
157
|
+
selectInstance(serviceName, instances) {
|
|
158
|
+
if (instances.length === 0) return null;
|
|
159
|
+
switch (this.strategy) {
|
|
160
|
+
case "round-robin":
|
|
161
|
+
return this.roundRobin(serviceName, instances);
|
|
162
|
+
case "random":
|
|
163
|
+
return this.random(instances);
|
|
164
|
+
case "weighted":
|
|
165
|
+
return this.weighted(instances);
|
|
166
|
+
case "least-connections":
|
|
167
|
+
return this.leastConnections(instances);
|
|
168
|
+
default:
|
|
169
|
+
return this.roundRobin(serviceName, instances);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
*
|
|
174
|
+
* @param serviceName
|
|
175
|
+
* @param instances
|
|
176
|
+
* @private
|
|
177
|
+
*/
|
|
178
|
+
roundRobin(serviceName, instances) {
|
|
179
|
+
const counter = this.counters.get(serviceName) || 0;
|
|
180
|
+
const index = counter % instances.length;
|
|
181
|
+
this.counters.set(serviceName, counter + 1);
|
|
182
|
+
return instances[index];
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
*
|
|
186
|
+
* @param instances
|
|
187
|
+
* @private
|
|
188
|
+
*/
|
|
189
|
+
random(instances) {
|
|
190
|
+
const index = Math.floor(Math.random() * instances.length);
|
|
191
|
+
return instances[index];
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
*
|
|
195
|
+
* @param instances
|
|
196
|
+
* @private
|
|
197
|
+
*/
|
|
198
|
+
weighted(instances) {
|
|
199
|
+
const totalWeight = instances.reduce((sum, instance) => sum + (instance.weight || 1), 0);
|
|
200
|
+
let random = Math.random() * totalWeight;
|
|
201
|
+
for (const instance of instances) {
|
|
202
|
+
random -= instance.weight || 1;
|
|
203
|
+
if (random <= 0) {
|
|
204
|
+
return instance;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return instances[0];
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
*
|
|
211
|
+
* @param instances
|
|
212
|
+
* @private
|
|
213
|
+
*/
|
|
214
|
+
leastConnections(instances) {
|
|
215
|
+
return instances.reduce(
|
|
216
|
+
(prev, current) => prev.currentConnections < current.currentConnections ? prev : current
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
*
|
|
221
|
+
* @param strategy
|
|
222
|
+
*/
|
|
223
|
+
setStrategy(strategy) {
|
|
224
|
+
this.strategy = strategy;
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// src/utils/httpClient.utils.ts
|
|
229
|
+
import * as http from "http";
|
|
230
|
+
import * as https from "https";
|
|
231
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
232
|
+
var HttpClient = class {
|
|
233
|
+
/**
|
|
234
|
+
*
|
|
235
|
+
* @param url
|
|
236
|
+
* @param options
|
|
237
|
+
*/
|
|
238
|
+
async request(url, options) {
|
|
239
|
+
return new Promise((resolve, reject) => {
|
|
240
|
+
try {
|
|
241
|
+
const urlObj = new URL(url);
|
|
242
|
+
const isHttps = urlObj.protocol === "https:";
|
|
243
|
+
const headers = {
|
|
244
|
+
"User-Agent": "API-Gateway/1.0",
|
|
245
|
+
"Accept": "application/json",
|
|
246
|
+
...options.headers
|
|
247
|
+
};
|
|
248
|
+
let requestBody;
|
|
249
|
+
if (options.body !== void 0 && options.body !== null) {
|
|
250
|
+
if (typeof options.body === "string") {
|
|
251
|
+
requestBody = options.body;
|
|
252
|
+
headers["Content-Type"] = headers["Content-Type"] || "text/plain";
|
|
253
|
+
} else if (Buffer2.isBuffer(options.body)) {
|
|
254
|
+
requestBody = options.body;
|
|
255
|
+
} else if (typeof options.body === "object") {
|
|
256
|
+
requestBody = JSON.stringify(options.body);
|
|
257
|
+
headers["Content-Type"] = headers["Content-Type"] || "application/json";
|
|
258
|
+
}
|
|
259
|
+
if (requestBody) {
|
|
260
|
+
headers["Content-Length"] = Buffer2.byteLength(requestBody).toString();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const requestOptions = {
|
|
264
|
+
method: options.method.toUpperCase(),
|
|
265
|
+
headers,
|
|
266
|
+
timeout: options.timeout || 1e4
|
|
267
|
+
};
|
|
268
|
+
const client = isHttps ? https : http;
|
|
269
|
+
const req = client.request(
|
|
270
|
+
url,
|
|
271
|
+
requestOptions,
|
|
272
|
+
(res) => {
|
|
273
|
+
const responseHeaders = {};
|
|
274
|
+
Object.keys(res.headers).forEach((key) => {
|
|
275
|
+
const value = res.headers[key];
|
|
276
|
+
if (value !== void 0) {
|
|
277
|
+
responseHeaders[key] = value;
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
const chunks = [];
|
|
281
|
+
res.on("data", (chunk) => {
|
|
282
|
+
chunks.push(chunk);
|
|
283
|
+
});
|
|
284
|
+
res.on("end", () => {
|
|
285
|
+
const body = Buffer2.concat(chunks).toString("utf8");
|
|
286
|
+
resolve({
|
|
287
|
+
status: res.statusCode || 500,
|
|
288
|
+
headers: responseHeaders,
|
|
289
|
+
body
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
res.on("error", (error) => {
|
|
293
|
+
reject(error);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
);
|
|
297
|
+
req.on("error", (error) => {
|
|
298
|
+
reject(error);
|
|
299
|
+
});
|
|
300
|
+
req.on("timeout", () => {
|
|
301
|
+
req.destroy();
|
|
302
|
+
reject(new Error(`Request timeout after ${requestOptions.timeout}ms`));
|
|
303
|
+
});
|
|
304
|
+
if (requestBody) {
|
|
305
|
+
req.write(requestBody);
|
|
306
|
+
}
|
|
307
|
+
req.end();
|
|
308
|
+
} catch (error) {
|
|
309
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
*
|
|
315
|
+
* @param method
|
|
316
|
+
* @private
|
|
317
|
+
*/
|
|
318
|
+
getDefaultHeaders(method) {
|
|
319
|
+
const headers = {};
|
|
320
|
+
if (["POST", "PUT", "PATCH"].includes(method.toUpperCase())) {
|
|
321
|
+
headers["Content-Type"] = "application/json";
|
|
322
|
+
}
|
|
323
|
+
return headers;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
*
|
|
327
|
+
* @param req
|
|
328
|
+
* @param body
|
|
329
|
+
* @param headers
|
|
330
|
+
* @private
|
|
331
|
+
*/
|
|
332
|
+
writeRequestBody(req, body, headers) {
|
|
333
|
+
const contentType = this.getContentType(headers);
|
|
334
|
+
try {
|
|
335
|
+
if (Buffer2.isBuffer(body)) {
|
|
336
|
+
req.write(body);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (typeof body === "string") {
|
|
340
|
+
req.write(body);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (contentType?.includes("application/json")) {
|
|
344
|
+
if (typeof body === "object" && body !== null) {
|
|
345
|
+
req.write(JSON.stringify(body));
|
|
346
|
+
} else {
|
|
347
|
+
req.write(String(body));
|
|
348
|
+
}
|
|
349
|
+
} else if (contentType?.includes("application/x-www-form-urlencoded")) {
|
|
350
|
+
if (typeof body === "object" && body !== null) {
|
|
351
|
+
const params = new URLSearchParams();
|
|
352
|
+
for (const [key, value] of Object.entries(body)) {
|
|
353
|
+
if (value !== void 0 && value !== null) {
|
|
354
|
+
params.append(key, String(value));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
req.write(params.toString());
|
|
358
|
+
} else {
|
|
359
|
+
req.write(String(body));
|
|
360
|
+
}
|
|
361
|
+
} else if (contentType?.includes("text/")) {
|
|
362
|
+
req.write(String(body));
|
|
363
|
+
} else {
|
|
364
|
+
if (typeof body === "object" && body !== null) {
|
|
365
|
+
req.write(JSON.stringify(body));
|
|
366
|
+
} else {
|
|
367
|
+
req.write(String(body));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
} catch (error) {
|
|
371
|
+
console.error("Error writing request body:", error);
|
|
372
|
+
throw new Error(`Failed to write request body: ${error.message}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
*
|
|
377
|
+
* @param headers
|
|
378
|
+
* @private
|
|
379
|
+
*/
|
|
380
|
+
getContentType(headers) {
|
|
381
|
+
const contentType = headers["Content-Type"] || headers["content-type"];
|
|
382
|
+
if (!contentType) return void 0;
|
|
383
|
+
if (Array.isArray(contentType)) {
|
|
384
|
+
return contentType[0];
|
|
385
|
+
}
|
|
386
|
+
return contentType;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
*
|
|
390
|
+
* @param url
|
|
391
|
+
* @param headers
|
|
392
|
+
*/
|
|
393
|
+
async get(url, headers) {
|
|
394
|
+
return this.request(url, {
|
|
395
|
+
method: "GET",
|
|
396
|
+
headers: headers || {}
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
*
|
|
401
|
+
* @param url
|
|
402
|
+
* @param body
|
|
403
|
+
* @param headers
|
|
404
|
+
*/
|
|
405
|
+
async post(url, body, headers) {
|
|
406
|
+
return this.request(url, {
|
|
407
|
+
method: "POST",
|
|
408
|
+
headers: headers || {},
|
|
409
|
+
body
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
*
|
|
414
|
+
* @param url
|
|
415
|
+
* @param body
|
|
416
|
+
* @param headers
|
|
417
|
+
*/
|
|
418
|
+
async put(url, body, headers) {
|
|
419
|
+
return this.request(url, {
|
|
420
|
+
method: "PUT",
|
|
421
|
+
headers: headers || {},
|
|
422
|
+
body
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
*
|
|
427
|
+
* @param url
|
|
428
|
+
* @param headers
|
|
429
|
+
*/
|
|
430
|
+
async delete(url, headers) {
|
|
431
|
+
return this.request(url, {
|
|
432
|
+
method: "DELETE",
|
|
433
|
+
headers: headers || {}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
*
|
|
438
|
+
* @param url
|
|
439
|
+
* @param body
|
|
440
|
+
* @param headers
|
|
441
|
+
*/
|
|
442
|
+
async patch(url, body, headers) {
|
|
443
|
+
return this.request(url, {
|
|
444
|
+
method: "PATCH",
|
|
445
|
+
headers: headers || {},
|
|
446
|
+
body
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
// src/core/middleware.core.ts
|
|
452
|
+
var BaseMiddleware = class {
|
|
453
|
+
};
|
|
454
|
+
var MiddlewareChain = class {
|
|
455
|
+
middlewares = [];
|
|
456
|
+
add(middleware) {
|
|
457
|
+
this.middlewares.push(middleware);
|
|
458
|
+
}
|
|
459
|
+
async execute(req, res, finalHandler) {
|
|
460
|
+
let index = 0;
|
|
461
|
+
const next = () => {
|
|
462
|
+
if (index < this.middlewares.length) {
|
|
463
|
+
const middleware = this.middlewares[index++];
|
|
464
|
+
try {
|
|
465
|
+
const result = middleware(req, res, next);
|
|
466
|
+
if (result instanceof Promise) {
|
|
467
|
+
result.catch((error) => {
|
|
468
|
+
console.error("Middleware error:", error);
|
|
469
|
+
res.statusCode = 500;
|
|
470
|
+
res.end(JSON.stringify({ error: "Internal Server Error" }));
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
} catch (error) {
|
|
474
|
+
console.error("Middleware error:", error);
|
|
475
|
+
res.statusCode = 500;
|
|
476
|
+
res.end(JSON.stringify({ error: "Internal Server Error" }));
|
|
477
|
+
}
|
|
478
|
+
} else {
|
|
479
|
+
finalHandler(req, res, () => {
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
next();
|
|
484
|
+
}
|
|
485
|
+
getAll() {
|
|
486
|
+
return [...this.middlewares];
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
// src/core/gatewayRoute.core.ts
|
|
491
|
+
import {
|
|
492
|
+
OpticoreRouterCollectionRouterFactory,
|
|
493
|
+
OpticoreStandaloneRouterFactory
|
|
494
|
+
} from "opticore-router";
|
|
495
|
+
import { LoggerCore } from "opticore-logger";
|
|
496
|
+
import { HttpStatusCode } from "opticore-http-response";
|
|
497
|
+
import { TranslationLoader } from "opticore-translator";
|
|
498
|
+
var GatewayRoute = class {
|
|
499
|
+
localLanguage;
|
|
500
|
+
config;
|
|
501
|
+
standaloneRouter;
|
|
502
|
+
collectionRouter = null;
|
|
503
|
+
routeHandler;
|
|
504
|
+
logger = new LoggerCore();
|
|
505
|
+
/**
|
|
506
|
+
*
|
|
507
|
+
* @param config
|
|
508
|
+
* @param routeHandler
|
|
509
|
+
* @param localLanguage
|
|
510
|
+
*/
|
|
511
|
+
constructor(config, routeHandler, localLanguage) {
|
|
512
|
+
this.localLanguage = localLanguage;
|
|
513
|
+
this.config = config;
|
|
514
|
+
this.routeHandler = routeHandler;
|
|
515
|
+
this.standaloneRouter = new OpticoreStandaloneRouterFactory();
|
|
516
|
+
this.logger = new LoggerCore();
|
|
517
|
+
this.standaloneRouter.storeRoute(
|
|
518
|
+
config.method,
|
|
519
|
+
config.path,
|
|
520
|
+
async (context) => routeHandler(context.req, context.res, context.next),
|
|
521
|
+
false
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Method for creating a multi-router configuration
|
|
526
|
+
*
|
|
527
|
+
* @param controller
|
|
528
|
+
*/
|
|
529
|
+
createMultipleRouterConfig(controller) {
|
|
530
|
+
return {
|
|
531
|
+
path: this.config.path,
|
|
532
|
+
method: this.config.method,
|
|
533
|
+
middlewares: this.config.middlewares || [],
|
|
534
|
+
handler: async (context) => {
|
|
535
|
+
return await this.routeHandler(context.req, context.res, context.next);
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Method for creating a router collection
|
|
541
|
+
*
|
|
542
|
+
* @param controller
|
|
543
|
+
* @param routes
|
|
544
|
+
*/
|
|
545
|
+
createCollectionRouter(controller, routes) {
|
|
546
|
+
this.collectionRouter = new OpticoreRouterCollectionRouterFactory(
|
|
547
|
+
controller,
|
|
548
|
+
routes
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Get the standalone router
|
|
553
|
+
*
|
|
554
|
+
* @param strategy
|
|
555
|
+
* @param options
|
|
556
|
+
*/
|
|
557
|
+
getStandaloneRoute(strategy, options) {
|
|
558
|
+
return this.standaloneRouter.getRoute(strategy, options);
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Get the definition of multiple routes
|
|
562
|
+
*/
|
|
563
|
+
getMultipleRouteDefinition() {
|
|
564
|
+
if (!this.collectionRouter) {
|
|
565
|
+
this.logger.error({
|
|
566
|
+
errorType: TranslationLoader.t("MISSING_ROUTES", this.localLanguage),
|
|
567
|
+
httpCodeValue: HttpStatusCode.NOT_FOUND,
|
|
568
|
+
message: TranslationLoader.t("ROUTER_COLLECTION_NOT_FOUND", this.localLanguage),
|
|
569
|
+
stackTrace: void 0,
|
|
570
|
+
title: TranslationLoader.t("ROUTER_COLLECTION", this.localLanguage)
|
|
571
|
+
});
|
|
572
|
+
throw new Error(TranslationLoader.t("ROUTER_COLLECTION_NOT_FOUND", this.localLanguage));
|
|
573
|
+
}
|
|
574
|
+
return this.collectionRouter.getRoute();
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
*
|
|
578
|
+
*/
|
|
579
|
+
getConfig() {
|
|
580
|
+
return this.config;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Utility method for creating an Opticore-compatible route
|
|
584
|
+
*
|
|
585
|
+
* @param path
|
|
586
|
+
* @param handler
|
|
587
|
+
*/
|
|
588
|
+
static createOpticoreRouteDefinition(path, handler) {
|
|
589
|
+
return {
|
|
590
|
+
path,
|
|
591
|
+
handler: async (context) => {
|
|
592
|
+
return await handler(context.req, context.res, context.next);
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
// src/core/gateway.core.ts
|
|
599
|
+
var APIGateway = class {
|
|
600
|
+
config;
|
|
601
|
+
server = null;
|
|
602
|
+
serviceRegistry;
|
|
603
|
+
loadBalancer;
|
|
604
|
+
httpClient;
|
|
605
|
+
middlewareChain;
|
|
606
|
+
routes = /* @__PURE__ */ new Map();
|
|
607
|
+
opticoreRoutes = [];
|
|
608
|
+
registerRouter;
|
|
609
|
+
constructor(config) {
|
|
610
|
+
this.config = config;
|
|
611
|
+
this.serviceRegistry = new ServiceRegistry();
|
|
612
|
+
this.loadBalancer = new LoadBalancer(config.loadBalancer || "round-robin");
|
|
613
|
+
this.httpClient = new HttpClient();
|
|
614
|
+
this.middlewareChain = new MiddlewareChain();
|
|
615
|
+
this.registerRouter = new OpticoreRegisterRouter();
|
|
616
|
+
this.initializeServices();
|
|
617
|
+
this.initializeGlobalMiddlewares();
|
|
618
|
+
this.initializeRoutes();
|
|
619
|
+
this.buildOpticoreRoutes();
|
|
620
|
+
}
|
|
621
|
+
initializeServices() {
|
|
622
|
+
this.config.services.forEach((service) => {
|
|
623
|
+
this.serviceRegistry.registerService(service);
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
initializeGlobalMiddlewares() {
|
|
627
|
+
if (this.config.globalMiddlewares) {
|
|
628
|
+
this.config.globalMiddlewares.forEach((middleware) => {
|
|
629
|
+
if (typeof middleware === "function") {
|
|
630
|
+
this.middlewareChain.add(middleware);
|
|
631
|
+
} else if (middleware.handle && typeof middleware.handle === "function") {
|
|
632
|
+
this.middlewareChain.add(middleware.handle());
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
initializeRoutes() {
|
|
638
|
+
this.config.routes.forEach((routeConfig) => {
|
|
639
|
+
const handler = async (req, res, next) => {
|
|
640
|
+
await this.handleGatewayRequest(req, res, routeConfig);
|
|
641
|
+
};
|
|
642
|
+
const route = new GatewayRoute(routeConfig, handler, "");
|
|
643
|
+
this.routes.set(`${routeConfig.method}:${routeConfig.path}`, route);
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
buildOpticoreRoutes() {
|
|
647
|
+
const featureRoutes = [];
|
|
648
|
+
const gatewayFeature = { routes: [] };
|
|
649
|
+
this.routes.forEach((gatewayRoute) => {
|
|
650
|
+
const config = gatewayRoute.getConfig();
|
|
651
|
+
const standaloneRouter = new OpticoreStandaloneRouterFactory2();
|
|
652
|
+
const contextHandler = async (context) => {
|
|
653
|
+
const { req, res } = context;
|
|
654
|
+
req.url = req.originalUrl || req.url;
|
|
655
|
+
return new Promise((resolve) => {
|
|
656
|
+
this.middlewareChain.execute(req, res, (_r, _s, _n) => {
|
|
657
|
+
this.handleGatewayRequest(req, res, config).then(resolve).catch(() => {
|
|
658
|
+
if (!res.headersSent) {
|
|
659
|
+
res.statusCode = 502;
|
|
660
|
+
res.end(JSON.stringify({ error: "Bad Gateway" }));
|
|
661
|
+
}
|
|
662
|
+
resolve();
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
};
|
|
667
|
+
standaloneRouter.storeRoute(
|
|
668
|
+
config.method,
|
|
669
|
+
"*",
|
|
670
|
+
contextHandler
|
|
671
|
+
);
|
|
672
|
+
gatewayFeature.routes.push({
|
|
673
|
+
path: config.path,
|
|
674
|
+
handler: standaloneRouter.getRoute()
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
featureRoutes.push(gatewayFeature);
|
|
678
|
+
this.opticoreRoutes = this.registerRouter.registered(featureRoutes);
|
|
679
|
+
}
|
|
680
|
+
async handleGatewayRequest(req, res, routeConfig) {
|
|
681
|
+
const startTime = Date.now();
|
|
682
|
+
try {
|
|
683
|
+
if (routeConfig.middlewares && routeConfig.middlewares.length > 0) {
|
|
684
|
+
const routeMiddlewareChain = new MiddlewareChain();
|
|
685
|
+
routeConfig.middlewares.forEach((middleware) => {
|
|
686
|
+
if (typeof middleware === "function") {
|
|
687
|
+
routeMiddlewareChain.add(middleware);
|
|
688
|
+
} else if (middleware.handle && typeof middleware.handle === "function") {
|
|
689
|
+
routeMiddlewareChain.add(middleware.handle());
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
await new Promise((resolve, reject) => {
|
|
693
|
+
routeMiddlewareChain.execute(req, res, (req2, res2, next) => {
|
|
694
|
+
resolve();
|
|
695
|
+
});
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
let serviceUrl;
|
|
699
|
+
let serviceName;
|
|
700
|
+
if (Array.isArray(routeConfig.target)) {
|
|
701
|
+
serviceName = routeConfig.serviceName || this.extractServiceName(routeConfig.target[0]);
|
|
702
|
+
const instances = this.serviceRegistry.getHealthyInstances(serviceName);
|
|
703
|
+
if (instances.length === 0) {
|
|
704
|
+
res.statusCode = 503;
|
|
705
|
+
res.end(JSON.stringify({ error: "Service unavailable" }));
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
const instance = this.loadBalancer.selectInstance(serviceName, instances);
|
|
709
|
+
if (!instance) {
|
|
710
|
+
res.statusCode = 503;
|
|
711
|
+
res.end(JSON.stringify({ error: "No healthy instances available" }));
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
serviceUrl = this.rewriteUrl(req.url, instance.url, routeConfig.target[0]);
|
|
715
|
+
instance.currentConnections++;
|
|
716
|
+
const response = await this.forwardRequest(req, res, serviceUrl, instance);
|
|
717
|
+
this.serviceRegistry.recordSuccess(serviceName, instance.url);
|
|
718
|
+
instance.currentConnections--;
|
|
719
|
+
this.sendResponse(res, response);
|
|
720
|
+
} else {
|
|
721
|
+
serviceName = routeConfig.serviceName || this.extractServiceName(routeConfig.target);
|
|
722
|
+
const instances = this.serviceRegistry.getHealthyInstances(serviceName);
|
|
723
|
+
if (instances.length === 0) {
|
|
724
|
+
res.statusCode = 503;
|
|
725
|
+
res.end(JSON.stringify({ error: "Service unavailable" }));
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const instance = instances[0];
|
|
729
|
+
serviceUrl = this.rewriteUrl(req.url, instance.url, routeConfig.target);
|
|
730
|
+
instance.currentConnections++;
|
|
731
|
+
const response = await this.forwardRequest(req, res, serviceUrl, instance);
|
|
732
|
+
this.serviceRegistry.recordSuccess(serviceName, instance.url);
|
|
733
|
+
instance.currentConnections--;
|
|
734
|
+
this.sendResponse(res, response);
|
|
735
|
+
}
|
|
736
|
+
} catch (error) {
|
|
737
|
+
console.error("Gateway error:", error);
|
|
738
|
+
res.statusCode = 502;
|
|
739
|
+
res.end(JSON.stringify({
|
|
740
|
+
error: "Bad Gateway",
|
|
741
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
742
|
+
}));
|
|
743
|
+
} finally {
|
|
744
|
+
const duration = Date.now() - startTime;
|
|
745
|
+
if (this.config.enableLogging) {
|
|
746
|
+
console.log(`Gateway request completed in ${duration}ms`);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
extractServiceName(url) {
|
|
751
|
+
try {
|
|
752
|
+
const urlObj = new URL(url);
|
|
753
|
+
return urlObj.hostname;
|
|
754
|
+
} catch {
|
|
755
|
+
return "unknown-service";
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
rewriteUrl(originalUrl, baseUrl, pattern) {
|
|
759
|
+
if (pattern && originalUrl.startsWith(pattern)) {
|
|
760
|
+
const url = new URL(baseUrl);
|
|
761
|
+
const path = originalUrl.replace(new RegExp(`^${pattern}`), "");
|
|
762
|
+
return `${url.protocol}//${url.host}${path}`;
|
|
763
|
+
}
|
|
764
|
+
try {
|
|
765
|
+
const base = new URL(baseUrl);
|
|
766
|
+
return `${base.protocol}//${base.host}${originalUrl}`;
|
|
767
|
+
} catch {
|
|
768
|
+
return `${baseUrl}${originalUrl}`;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
async forwardRequest(req, res, targetUrl, instance) {
|
|
772
|
+
const headers = { ...req.headers };
|
|
773
|
+
delete headers["host"];
|
|
774
|
+
delete headers["connection"];
|
|
775
|
+
headers["x-forwarded-for"] = req.socket?.remoteAddress || "";
|
|
776
|
+
headers["x-forwarded-proto"] = req.protocol || "http";
|
|
777
|
+
headers["x-gateway-service"] = instance.name || "unknown";
|
|
778
|
+
const timeout = instance.timeout || this.config.routes.find(
|
|
779
|
+
(r) => r.target === instance.url || Array.isArray(r.target) && r.target.includes(instance.url)
|
|
780
|
+
)?.timeout || 3e4;
|
|
781
|
+
try {
|
|
782
|
+
return await this.httpClient.request(targetUrl, {
|
|
783
|
+
method: req.method,
|
|
784
|
+
headers,
|
|
785
|
+
body: req.body,
|
|
786
|
+
timeout
|
|
787
|
+
});
|
|
788
|
+
} catch (error) {
|
|
789
|
+
this.serviceRegistry.recordFailure(instance.name, instance.url);
|
|
790
|
+
throw error;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
sendResponse(res, httpResponse) {
|
|
794
|
+
if (httpResponse.headers) {
|
|
795
|
+
Object.keys(httpResponse.headers).forEach((key) => {
|
|
796
|
+
const value = httpResponse.headers[key];
|
|
797
|
+
if (value !== void 0) {
|
|
798
|
+
res.setHeader(key, value);
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
res.statusCode = httpResponse.status;
|
|
803
|
+
res.end(httpResponse.body);
|
|
804
|
+
}
|
|
805
|
+
addRoute(routeConfig) {
|
|
806
|
+
const handler = async (req, res, next) => {
|
|
807
|
+
await this.handleGatewayRequest(req, res, routeConfig);
|
|
808
|
+
};
|
|
809
|
+
const route = new GatewayRoute(routeConfig, handler, "");
|
|
810
|
+
this.routes.set(`${routeConfig.method}:${routeConfig.path}`, route);
|
|
811
|
+
this.buildOpticoreRoutes();
|
|
812
|
+
}
|
|
813
|
+
addMiddleware(middleware) {
|
|
814
|
+
this.middlewareChain.add(middleware);
|
|
815
|
+
}
|
|
816
|
+
registerService(service) {
|
|
817
|
+
this.serviceRegistry.registerService(service);
|
|
818
|
+
}
|
|
819
|
+
getOpticoreRoutes() {
|
|
820
|
+
return this.opticoreRoutes;
|
|
821
|
+
}
|
|
822
|
+
async parseRequestBody(req) {
|
|
823
|
+
if (req.body !== void 0) return;
|
|
824
|
+
return new Promise((resolve, reject) => {
|
|
825
|
+
const chunks = [];
|
|
826
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
827
|
+
req.on("end", () => {
|
|
828
|
+
if (chunks.length === 0) {
|
|
829
|
+
resolve();
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
833
|
+
const contentType = req.headers["content-type"] || "";
|
|
834
|
+
if (contentType.includes("application/json")) {
|
|
835
|
+
try {
|
|
836
|
+
req.body = JSON.parse(raw);
|
|
837
|
+
} catch {
|
|
838
|
+
req.body = raw;
|
|
839
|
+
}
|
|
840
|
+
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
841
|
+
req.body = Object.fromEntries(new URLSearchParams(raw));
|
|
842
|
+
} else {
|
|
843
|
+
req.body = raw || void 0;
|
|
844
|
+
}
|
|
845
|
+
resolve();
|
|
846
|
+
});
|
|
847
|
+
req.on("error", reject);
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
getExpressMiddleware() {
|
|
851
|
+
return (req, res, next) => {
|
|
852
|
+
this.middlewareChain.execute(req, res, (_r, _s, _n) => next());
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
start() {
|
|
856
|
+
return new Promise((resolve, reject) => {
|
|
857
|
+
this.server = http2.createServer(async (req, res) => {
|
|
858
|
+
await this.parseRequestBody(req);
|
|
859
|
+
this.middlewareChain.execute(req, res, () => {
|
|
860
|
+
let handled = false;
|
|
861
|
+
this.routes.forEach((gatewayRoute) => {
|
|
862
|
+
if (handled) return;
|
|
863
|
+
const config = gatewayRoute.getConfig();
|
|
864
|
+
if (req.method?.toLowerCase() === config.method && req.url?.startsWith(config.path)) {
|
|
865
|
+
this.handleGatewayRequest(req, res, config).catch(() => {
|
|
866
|
+
if (!res.headersSent) {
|
|
867
|
+
res.statusCode = 502;
|
|
868
|
+
res.end(JSON.stringify({ error: "Bad Gateway" }));
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
handled = true;
|
|
872
|
+
}
|
|
873
|
+
});
|
|
874
|
+
if (!handled) {
|
|
875
|
+
res.statusCode = 404;
|
|
876
|
+
res.end(JSON.stringify({ error: "Route not found" }));
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
});
|
|
880
|
+
this.server.listen(this.config.port, () => {
|
|
881
|
+
console.log(`API Gateway running on port ${this.config.port}`);
|
|
882
|
+
console.log(`Registered ${this.routes.size} routes`);
|
|
883
|
+
resolve();
|
|
884
|
+
});
|
|
885
|
+
this.server.on("error", (error) => {
|
|
886
|
+
reject(error);
|
|
887
|
+
});
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
stop() {
|
|
891
|
+
return new Promise((resolve, reject) => {
|
|
892
|
+
if (this.server) {
|
|
893
|
+
this.server.close((error) => {
|
|
894
|
+
if (error) {
|
|
895
|
+
reject(error);
|
|
896
|
+
} else {
|
|
897
|
+
console.log("API Gateway stopped");
|
|
898
|
+
this.server = null;
|
|
899
|
+
resolve();
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
} else {
|
|
903
|
+
resolve();
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
// src/presentation/middleware/auth.middleware.ts
|
|
910
|
+
var AuthMiddleware = class extends BaseMiddleware {
|
|
911
|
+
apiKeys;
|
|
912
|
+
constructor(apiKeys = []) {
|
|
913
|
+
super();
|
|
914
|
+
this.apiKeys = new Set(apiKeys);
|
|
915
|
+
}
|
|
916
|
+
handle() {
|
|
917
|
+
return (req, res, next) => {
|
|
918
|
+
const apiKey = req.headers["x-api-key"] || req.query && req.query.apiKey;
|
|
919
|
+
if (!apiKey) {
|
|
920
|
+
res.statusCode = 401;
|
|
921
|
+
res.end(JSON.stringify({ error: "API key required" }));
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (!this.apiKeys.has(apiKey)) {
|
|
925
|
+
res.statusCode = 403;
|
|
926
|
+
res.end(JSON.stringify({ error: "Invalid API key" }));
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
next();
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
// src/presentation/middleware/rateLimit.middleware.ts
|
|
935
|
+
var RateLimitMiddleware = class extends BaseMiddleware {
|
|
936
|
+
requestsPerWindow;
|
|
937
|
+
windowMs;
|
|
938
|
+
store;
|
|
939
|
+
constructor(requestsPerWindow = 100, windowMs = 6e4) {
|
|
940
|
+
super();
|
|
941
|
+
this.requestsPerWindow = requestsPerWindow;
|
|
942
|
+
this.windowMs = windowMs;
|
|
943
|
+
this.store = /* @__PURE__ */ new Map();
|
|
944
|
+
}
|
|
945
|
+
handle() {
|
|
946
|
+
return (req, res, next) => {
|
|
947
|
+
const clientId = req.headers["x-forwarded-for"] || req.socket?.remoteAddress || "unknown";
|
|
948
|
+
const key = `rate-limit:${clientId}`;
|
|
949
|
+
const now = Date.now();
|
|
950
|
+
let window = this.store.get(key);
|
|
951
|
+
if (!window || now > window.resetTime) {
|
|
952
|
+
window = {
|
|
953
|
+
count: 0,
|
|
954
|
+
resetTime: now + this.windowMs
|
|
955
|
+
};
|
|
956
|
+
this.store.set(key, window);
|
|
957
|
+
}
|
|
958
|
+
window.count++;
|
|
959
|
+
res.setHeader("X-RateLimit-Limit", this.requestsPerWindow.toString());
|
|
960
|
+
res.setHeader("X-RateLimit-Remaining", Math.max(0, this.requestsPerWindow - window.count).toString());
|
|
961
|
+
res.setHeader("X-RateLimit-Reset", Math.ceil(window.resetTime / 1e3).toString());
|
|
962
|
+
if (window.count > this.requestsPerWindow) {
|
|
963
|
+
res.statusCode = 429;
|
|
964
|
+
res.end(JSON.stringify({
|
|
965
|
+
error: "Too many requests",
|
|
966
|
+
retryAfter: Math.ceil((window.resetTime - now) / 1e3)
|
|
967
|
+
}));
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
next();
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
// src/presentation/middleware/loggin.middleware.ts
|
|
976
|
+
var LoggingMiddleware = class extends BaseMiddleware {
|
|
977
|
+
constructor(logLevel = "info") {
|
|
978
|
+
super();
|
|
979
|
+
this.logLevel = logLevel;
|
|
980
|
+
}
|
|
981
|
+
handle() {
|
|
982
|
+
return (req, res, next) => {
|
|
983
|
+
const start = Date.now();
|
|
984
|
+
res.on("finish", () => {
|
|
985
|
+
const duration = Date.now() - start;
|
|
986
|
+
const message = `${req.method} ${req.originalUrl} ${res.statusCode} ${duration}ms`;
|
|
987
|
+
if (res.statusCode >= 500) console.error(message);
|
|
988
|
+
else console.log(message);
|
|
989
|
+
});
|
|
990
|
+
next();
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
|
|
995
|
+
// src/presentation/middleware/validation.middleware.ts
|
|
996
|
+
import { Validator } from "opticore-validator";
|
|
997
|
+
var ValidationMiddleware = class extends BaseMiddleware {
|
|
998
|
+
validator;
|
|
999
|
+
target;
|
|
1000
|
+
/**
|
|
1001
|
+
* Crée un middleware de validation.
|
|
1002
|
+
* @param schema - Le schéma de validation au format opticore-validator.
|
|
1003
|
+
* @param target - La partie de la requête à valider (par défaut 'body').
|
|
1004
|
+
*/
|
|
1005
|
+
constructor(schema, target = "body") {
|
|
1006
|
+
super();
|
|
1007
|
+
this.validator = new Validator(schema);
|
|
1008
|
+
this.target = target;
|
|
1009
|
+
}
|
|
1010
|
+
handle() {
|
|
1011
|
+
return (req, res, next) => {
|
|
1012
|
+
let dataToValidate;
|
|
1013
|
+
switch (this.target) {
|
|
1014
|
+
case "body":
|
|
1015
|
+
dataToValidate = req.body;
|
|
1016
|
+
break;
|
|
1017
|
+
case "query":
|
|
1018
|
+
dataToValidate = req.query;
|
|
1019
|
+
break;
|
|
1020
|
+
case "params":
|
|
1021
|
+
dataToValidate = req.params;
|
|
1022
|
+
break;
|
|
1023
|
+
case "all":
|
|
1024
|
+
dataToValidate = { ...req.body, ...req.query, ...req.params };
|
|
1025
|
+
break;
|
|
1026
|
+
default:
|
|
1027
|
+
dataToValidate = req.body;
|
|
1028
|
+
}
|
|
1029
|
+
if (dataToValidate === void 0 || dataToValidate === null) {
|
|
1030
|
+
dataToValidate = {};
|
|
1031
|
+
}
|
|
1032
|
+
const errors = this.validator.validate(dataToValidate);
|
|
1033
|
+
if (errors && typeof errors === "object" && Object.keys(errors).length > 0) {
|
|
1034
|
+
res.status(400).json({
|
|
1035
|
+
message: "Validation failed",
|
|
1036
|
+
errors
|
|
1037
|
+
});
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
next();
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
export {
|
|
1045
|
+
APIGateway,
|
|
1046
|
+
AuthMiddleware,
|
|
1047
|
+
BaseMiddleware,
|
|
1048
|
+
GatewayRoute,
|
|
1049
|
+
HttpClient,
|
|
1050
|
+
LoadBalancer,
|
|
1051
|
+
LoggingMiddleware,
|
|
1052
|
+
MiddlewareChain,
|
|
1053
|
+
RateLimitMiddleware,
|
|
1054
|
+
ServiceRegistry,
|
|
1055
|
+
ValidationMiddleware
|
|
1056
|
+
};
|