proteum 1.0.0-1

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.
Files changed (156) hide show
  1. package/.dockerignore +10 -0
  2. package/Rte.zip +0 -0
  3. package/cli/app/config.ts +54 -0
  4. package/cli/app/index.ts +195 -0
  5. package/cli/bin.js +11 -0
  6. package/cli/commands/build.ts +34 -0
  7. package/cli/commands/deploy/app.ts +29 -0
  8. package/cli/commands/deploy/web.ts +60 -0
  9. package/cli/commands/dev.ts +109 -0
  10. package/cli/commands/init.ts +85 -0
  11. package/cli/compiler/client/identite.ts +72 -0
  12. package/cli/compiler/client/index.ts +334 -0
  13. package/cli/compiler/common/babel/index.ts +170 -0
  14. package/cli/compiler/common/babel/plugins/index.ts +0 -0
  15. package/cli/compiler/common/babel/plugins/services.ts +579 -0
  16. package/cli/compiler/common/babel/routes/imports.ts +127 -0
  17. package/cli/compiler/common/babel/routes/routes.ts +1130 -0
  18. package/cli/compiler/common/files/autres.ts +39 -0
  19. package/cli/compiler/common/files/images.ts +35 -0
  20. package/cli/compiler/common/files/style.ts +78 -0
  21. package/cli/compiler/common/index.ts +154 -0
  22. package/cli/compiler/index.ts +532 -0
  23. package/cli/compiler/server/index.ts +211 -0
  24. package/cli/index.ts +189 -0
  25. package/cli/paths.ts +165 -0
  26. package/cli/print.ts +12 -0
  27. package/cli/tsconfig.json +38 -0
  28. package/cli/utils/index.ts +22 -0
  29. package/cli/utils/keyboard.ts +78 -0
  30. package/client/app/component.tsx +54 -0
  31. package/client/app/index.ts +142 -0
  32. package/client/app/service.ts +34 -0
  33. package/client/app.tsconfig.json +28 -0
  34. package/client/components/Button.tsx +298 -0
  35. package/client/components/Dialog/Manager.tsx +309 -0
  36. package/client/components/Dialog/card.tsx +208 -0
  37. package/client/components/Dialog/index.less +151 -0
  38. package/client/components/Dialog/status.less +176 -0
  39. package/client/components/Dialog/status.tsx +48 -0
  40. package/client/components/index.ts +2 -0
  41. package/client/components/types.d.ts +3 -0
  42. package/client/data/input.ts +32 -0
  43. package/client/global.d.ts +5 -0
  44. package/client/hooks.ts +22 -0
  45. package/client/index.ts +6 -0
  46. package/client/pages/_layout/index.less +6 -0
  47. package/client/pages/_layout/index.tsx +43 -0
  48. package/client/pages/bug.tsx.old +60 -0
  49. package/client/pages/useHeader.tsx +50 -0
  50. package/client/services/captcha/index.ts +67 -0
  51. package/client/services/router/components/Link.tsx +46 -0
  52. package/client/services/router/components/Page.tsx +55 -0
  53. package/client/services/router/components/router.tsx +218 -0
  54. package/client/services/router/index.tsx +521 -0
  55. package/client/services/router/request/api.ts +267 -0
  56. package/client/services/router/request/history.ts +5 -0
  57. package/client/services/router/request/index.ts +53 -0
  58. package/client/services/router/request/multipart.ts +147 -0
  59. package/client/services/router/response/index.tsx +128 -0
  60. package/client/services/router/response/page.ts +86 -0
  61. package/client/services/socket/index.ts +147 -0
  62. package/client/utils/dom.ts +77 -0
  63. package/common/app/index.ts +9 -0
  64. package/common/data/chaines/index.ts +54 -0
  65. package/common/data/dates.ts +179 -0
  66. package/common/data/markdown.ts +73 -0
  67. package/common/data/rte/nodes.ts +83 -0
  68. package/common/data/stats.ts +90 -0
  69. package/common/errors/index.tsx +326 -0
  70. package/common/router/index.ts +213 -0
  71. package/common/router/layouts.ts +93 -0
  72. package/common/router/register.ts +55 -0
  73. package/common/router/request/api.ts +77 -0
  74. package/common/router/request/index.ts +35 -0
  75. package/common/router/response/index.ts +45 -0
  76. package/common/router/response/page.ts +128 -0
  77. package/common/utils/rte.ts +183 -0
  78. package/common/utils.ts +7 -0
  79. package/doc/TODO.md +71 -0
  80. package/doc/front/router.md +27 -0
  81. package/doc/workspace/workspace.png +0 -0
  82. package/doc/workspace/workspace2.png +0 -0
  83. package/doc/workspace/workspace_26.01.22.png +0 -0
  84. package/package.json +171 -0
  85. package/server/app/commands.ts +141 -0
  86. package/server/app/container/config.ts +203 -0
  87. package/server/app/container/console/index.ts +550 -0
  88. package/server/app/container/index.ts +137 -0
  89. package/server/app/index.ts +273 -0
  90. package/server/app/service/container.ts +88 -0
  91. package/server/app/service/index.ts +235 -0
  92. package/server/app.tsconfig.json +28 -0
  93. package/server/context.ts +4 -0
  94. package/server/index.ts +4 -0
  95. package/server/services/auth/index.ts +250 -0
  96. package/server/services/auth/old.ts +277 -0
  97. package/server/services/auth/router/index.ts +95 -0
  98. package/server/services/auth/router/request.ts +54 -0
  99. package/server/services/auth/router/service.json +6 -0
  100. package/server/services/auth/service.json +6 -0
  101. package/server/services/cache/commands.ts +41 -0
  102. package/server/services/cache/index.ts +297 -0
  103. package/server/services/cache/service.json +6 -0
  104. package/server/services/cron/CronTask.ts +86 -0
  105. package/server/services/cron/index.ts +112 -0
  106. package/server/services/cron/service.json +6 -0
  107. package/server/services/disks/driver.ts +103 -0
  108. package/server/services/disks/drivers/local/index.ts +188 -0
  109. package/server/services/disks/drivers/local/service.json +6 -0
  110. package/server/services/disks/drivers/s3/index.ts +301 -0
  111. package/server/services/disks/drivers/s3/service.json +6 -0
  112. package/server/services/disks/index.ts +90 -0
  113. package/server/services/disks/service.json +6 -0
  114. package/server/services/email/index.ts +188 -0
  115. package/server/services/email/utils.ts +53 -0
  116. package/server/services/fetch/index.ts +201 -0
  117. package/server/services/fetch/service.json +7 -0
  118. package/server/services/models.7z +0 -0
  119. package/server/services/prisma/Facet.ts +142 -0
  120. package/server/services/prisma/index.ts +201 -0
  121. package/server/services/prisma/service.json +6 -0
  122. package/server/services/router/http/index.ts +217 -0
  123. package/server/services/router/http/multipart.ts +102 -0
  124. package/server/services/router/http/session.ts.old +40 -0
  125. package/server/services/router/index.ts +801 -0
  126. package/server/services/router/request/api.ts +87 -0
  127. package/server/services/router/request/index.ts +184 -0
  128. package/server/services/router/request/service.ts +21 -0
  129. package/server/services/router/request/validation/zod.ts +180 -0
  130. package/server/services/router/response/index.ts +338 -0
  131. package/server/services/router/response/mask/Filter.ts +323 -0
  132. package/server/services/router/response/mask/index.ts +60 -0
  133. package/server/services/router/response/mask/selecteurs.ts +92 -0
  134. package/server/services/router/response/page/document.tsx +160 -0
  135. package/server/services/router/response/page/index.tsx +196 -0
  136. package/server/services/router/service.json +6 -0
  137. package/server/services/router/service.ts +36 -0
  138. package/server/services/schema/index.ts +44 -0
  139. package/server/services/schema/request.ts +49 -0
  140. package/server/services/schema/router/index.ts +28 -0
  141. package/server/services/schema/router/service.json +6 -0
  142. package/server/services/schema/service.json +6 -0
  143. package/server/services/security/encrypt/aes/index.ts +85 -0
  144. package/server/services/security/encrypt/aes/service.json +6 -0
  145. package/server/services/socket/index.ts +162 -0
  146. package/server/services/socket/scope.ts +226 -0
  147. package/server/services/socket/service.json +6 -0
  148. package/server/services_old/SocketClient.ts +92 -0
  149. package/server/services_old/Token.old.ts +97 -0
  150. package/server/utils/slug.ts +79 -0
  151. package/tsconfig.common.json +45 -0
  152. package/tsconfig.json +3 -0
  153. package/types/aliases.d.ts +54 -0
  154. package/types/global/modules.d.ts +49 -0
  155. package/types/global/utils.d.ts +103 -0
  156. package/types/icons.d.ts +1 -0
@@ -0,0 +1,801 @@
1
+
2
+ // INSPIRATION:
3
+ // https://adonisjs.com/docs/4.1/routing
4
+ // https://laravel.com/docs/8.x/routing
5
+ // https://github.com/adonisjs/http-server/blob/develop/src/ServerRouter/indexApi.ts
6
+ // https://github.com/expressjs/express/blob/06d11755c99fe4c1cddf8b889a687448b568472d/lib/response.js#L1016
7
+
8
+ /*----------------------------------
9
+ - DEPENDANCES
10
+ ----------------------------------*/
11
+
12
+ // Node
13
+ // Npm
14
+ import got from 'got';
15
+ import hInterval from 'human-interval';
16
+ import type express from 'express';
17
+ import type { Request, Response, NextFunction } from 'express';
18
+ import { v4 as uuid } from 'uuid';
19
+ import zod, { ZodError } from 'zod';
20
+ export { default as schema } from 'zod';
21
+ import type { GlobImportedWithMetas } from 'babel-plugin-glob-import';
22
+
23
+ // Core
24
+ import type { Application } from '@server/app';
25
+ import Service, { AnyService, TServiceArgs } from '@server/app/service';
26
+ import context from '@server/context';
27
+ import type DisksManager from '@server/services/disks';
28
+ import { CoreError, InputError, NotFound, toJson as errorToJson } from '@common/errors';
29
+ import BaseRouter, {
30
+ TRoute, TErrorRoute, TRouteModule,
31
+ TRouteOptions, defaultOptions,
32
+ matchRoute, buildUrl, TDomainsList
33
+ } from '@common/router';
34
+ import { buildRegex, getRegisterPageArgs } from '@common/router/register';
35
+ import { layoutsList, getLayout } from '@common/router/layouts';
36
+ import { TFetcherList } from '@common/router/request/api';
37
+ import type { TFrontRenderer } from '@common/router/response/page';
38
+ import type { TSsrUnresolvedRoute, TRegisterPageArgs } from '@client/services/router';
39
+
40
+ // Specific
41
+ import { AnyRouterService } from './service';
42
+ import ServerRequest from "./request";
43
+ import ServerResponse, { TRouterContext, TRouterContextServices } from './response';
44
+ import Page from './response/page';
45
+ import HTTP, { Config as HttpServiceConfig } from './http';
46
+ import DocumentRenderer from './response/page/document';
47
+
48
+ /*----------------------------------
49
+ - TYPES
50
+ ----------------------------------*/
51
+
52
+ export { type AnyRouterService, default as RouterService } from './service';
53
+ export { default as RequestService } from './request/service';
54
+ export type { default as Request, UploadedFile } from "./request";
55
+ export type { default as Response, TRouterContext } from "./response";
56
+ export type { TRoute, TAnyRoute } from '@common/router';
57
+
58
+ export type TApiRegisterArgs<TRouter extends TServerRouter> = ([
59
+ path: string,
60
+ controller: TServerController<TRouter>
61
+ ] | [
62
+ path: string,
63
+ options: Partial<TRouteOptions>,
64
+ controller: TServerController<TRouter>
65
+ ])
66
+
67
+ export type TServerController<TRouter extends TServerRouter> = (context: TRouterContext<TRouter>) => any;
68
+
69
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS'
70
+ export type TRouteHttpMethod = HttpMethod | '*';
71
+
72
+ export type TApiResponseData = {
73
+ data: any,
74
+ triggers?: { [cle: string]: any }
75
+ }
76
+
77
+ export type HttpHeaders = { [cle: string]: string }
78
+
79
+ /*----------------------------------
80
+ - SERVICE CONFIG
81
+ ----------------------------------*/
82
+
83
+ export type TAnyRouter = ServerRouter<Application, TRouterServicesList, Config<TRouterServicesList>>;
84
+
85
+ const LogPrefix = '[router]';
86
+
87
+ export type Config<
88
+ TServices extends TRouterServicesList
89
+ > = {
90
+
91
+ debug: boolean,
92
+
93
+ disk?: string, // Disk driver ID
94
+
95
+ domains: TDomainsList,
96
+
97
+ http: HttpServiceConfig,
98
+
99
+ context: (
100
+ request: ServerRequest<TServerRouter>,
101
+ app: Application
102
+ ) => {},
103
+
104
+ plugins: TServices
105
+ }
106
+
107
+ // Set it as a function, so when we instanciate the services, we can callthis.router to pass the router instance in roiuter services
108
+ type TRouterServicesList = {
109
+ [serviceName: string]: AnyRouterService
110
+ }
111
+
112
+ export type Hooks = {
113
+
114
+ }
115
+
116
+ export type TControllerDefinition = {
117
+ path?: string,
118
+ schema?: zod.ZodSchema,
119
+ controller: TServerController<TServerRouter>,
120
+ }
121
+
122
+ export type TServerRouter = ServerRouter<Application, TRouterServicesList, Config<TRouterServicesList>>;
123
+
124
+ /*----------------------------------
125
+ - CLASSE
126
+ ----------------------------------*/
127
+ export default class ServerRouter<
128
+ TApplication extends Application,
129
+ TServices extends TRouterServicesList,
130
+ TConfig extends Config<TServices>,
131
+ >
132
+ extends Service<TConfig, Hooks, TApplication, TApplication> implements BaseRouter {
133
+
134
+ public disks = this.use<DisksManager>('Core/Disks', { optional: true });
135
+
136
+ // Services
137
+ public http: HTTP;
138
+ public render: DocumentRenderer<this>;
139
+
140
+ // Indexed
141
+ public routes: TRoute[] = []; // API + pages front front
142
+ public errors: { [code: number]: TErrorRoute } = {};
143
+ public controllers: {[path: string]: TRoute} = {};
144
+ public ssrRoutes: TSsrUnresolvedRoute[] = [];
145
+
146
+ // Cache (ex: for static pages)
147
+ public cache: {
148
+ [pageId: string]: {
149
+ rendered: any,
150
+ expire: number | undefined,
151
+ options: TRouteOptions["static"]
152
+ }
153
+ } = {}
154
+
155
+ /*----------------------------------
156
+ - SERVICE
157
+ ----------------------------------*/
158
+
159
+ public constructor( ...args: TServiceArgs< ServerRouter<TApplication, TServices, TConfig> >) {
160
+
161
+ super(...args);
162
+
163
+ this.http = new HTTP(this.config.http, this);
164
+ this.render = new DocumentRenderer(this);
165
+ }
166
+
167
+ /*----------------------------------
168
+ - LIFECYCLE
169
+ ----------------------------------*/
170
+
171
+ public async ready() {
172
+
173
+ // Detect router services
174
+ for (const serviceName in this.config.plugins) {
175
+ const service = this.config.plugins[serviceName];
176
+ service.parent = this;
177
+ this.app.register( service )
178
+ }
179
+
180
+ // Use require to avoid circular references
181
+ this.registerRoutes([
182
+ ...require("metas:@/server/routes/**/*.ts"),
183
+ ...require("metas:@/client/pages/**/([a-z0-9]*).tsx"),
184
+ //...require("metas:@/client/pages/**/([a-z0-9]*).tsx")
185
+ ]);
186
+
187
+ // Start HTTP server
188
+ await this.http.start();
189
+
190
+ // override
191
+ const originalLog = console.log;
192
+ console.log = (...args: any[]) => {
193
+
194
+ // parse stack trace: skip this function and the console.log call
195
+ /*const stackLine = (new Error()).stack?.split('\n')[2] || '';
196
+ const match = stackLine.match(/at (\w+)\.(\w+) /);
197
+ const className = match ? match[1] : '<global>';
198
+ const methodName = match ? match[2] : '<anonymous>';*/
199
+
200
+ const contextData = context.getStore() || {
201
+ channelType: 'master',
202
+ }
203
+
204
+ const requestPrefix = contextData.channelType === 'request'
205
+ ? `[${contextData.user ? contextData.user : 'guest'}] ${contextData.method} ${contextData.path} |`
206
+ : 'master';
207
+
208
+ // prefix and forward
209
+ originalLog.call(
210
+ console,
211
+ `${requestPrefix}`, // ${className}.${methodName}
212
+ ...args
213
+ );
214
+ };
215
+
216
+
217
+ // When all the services are ready, initialize static routes
218
+ this.app.on('ready', () => {
219
+ this.initStaticRoutes();
220
+ });
221
+
222
+ }
223
+
224
+ public async shutdown() {
225
+
226
+ }
227
+
228
+ /*----------------------------------
229
+ - ACTIONS
230
+ ----------------------------------*/
231
+
232
+ public async renderStatic(
233
+ url: string,
234
+ options: TRouteOptions["static"],
235
+ rendered?: any
236
+ ) {
237
+
238
+ // Wildcard: tell that the newly rendered pages should be cached
239
+ if (url === '*' || !url)
240
+ return;
241
+
242
+ if (!rendered) {
243
+
244
+ console.log('[router] renderStatic: url', url);
245
+
246
+ const fullUrl = this.url(url, {}, true);
247
+ const response = await got( fullUrl, {
248
+ method: 'GET',
249
+ headers: {
250
+ 'Accept': 'text/html',
251
+ 'bypasscache': '1'
252
+ },
253
+ throwHttpErrors: false,
254
+ });
255
+
256
+ if (response.statusCode !== 200) {
257
+ console.error("[router] renderStatic: page returned code", response.statusCode, fullUrl);
258
+ return;
259
+ }
260
+
261
+ rendered = response.body;
262
+ }
263
+
264
+ this.cache[url] = {
265
+ rendered: rendered,
266
+ options: options,
267
+ expire: typeof options === 'object'
268
+ ? Date.now() + (hInterval(options.refresh) || 3600)
269
+ : undefined
270
+ };
271
+
272
+ }
273
+
274
+ private initStaticRoutes() {
275
+
276
+ for (const route of this.routes) {
277
+
278
+ if (!route.options.static)
279
+ continue;
280
+
281
+ // Add to static pages
282
+ // Should be a GET oage that don't take any parameter
283
+ for (const url of route.options.static.urls) {
284
+ this.renderStatic(url, route.options.static);
285
+ }
286
+ }
287
+
288
+ // Every hours, refresh static pages
289
+ setInterval(() => {
290
+ this.refreshStaticPages();
291
+ }, 1000 * 60 * 60);
292
+ }
293
+
294
+ private refreshStaticPages() {
295
+
296
+ console.log('[router] refreshStaticPages');
297
+
298
+ for (const pageUrl in this.cache) {
299
+ const page = this.cache[pageUrl];
300
+ if (page.expire && page.expire < Date.now()) {
301
+
302
+ this.renderStatic(pageUrl, page.options);
303
+ }
304
+ }
305
+ }
306
+
307
+ private registerRoutes(defModules: GlobImportedWithMetas<TRouteModule>) {
308
+ for (const routeModule of defModules) {
309
+
310
+ const register = routeModule.exports.__register;
311
+ if (!register)
312
+ continue;
313
+
314
+ this.config.debug && console.log(LogPrefix, `Register file:`, routeModule.matches.join('/'));
315
+ try {
316
+ register(this.app);
317
+ } catch (error) {
318
+ console.error("Failed to register route file:", routeModule);
319
+ console.error('Register function:', register.toString());
320
+ throw error;
321
+ }
322
+ }
323
+
324
+ this.afterRegister();
325
+ }
326
+
327
+ public url = (path: string, params: {} = {}, absolute: boolean = true) =>
328
+ buildUrl(path, params, this.config.domains, absolute);
329
+
330
+ /*----------------------------------
331
+ - REGISTER
332
+ ----------------------------------*/
333
+
334
+ public page(...args: TRegisterPageArgs) {
335
+
336
+ const { path, options, renderer, layout } = getRegisterPageArgs(...args);
337
+
338
+ const { regex, keys } = buildRegex(path);
339
+
340
+ const route: TRoute = {
341
+ method: 'GET',
342
+ path,
343
+ regex,
344
+ keys,
345
+ controller: (context: TRouterContext<this>) => new Page(route, renderer, context, layout),
346
+ options: {
347
+ ...defaultOptions,
348
+ accept: 'html', // Les pages retournent forcémment du html
349
+ ...options
350
+ },
351
+ }
352
+
353
+ this.routes.push(route);
354
+
355
+ return this;
356
+
357
+ }
358
+
359
+ public error(
360
+ code: number,
361
+ options: TRoute["options"],
362
+ renderer: TFrontRenderer<{}, { message: string }>
363
+ ) {
364
+
365
+ // Automatic layout form the nearest _layout folder
366
+ const layout = getLayout('Error ' + code, options);
367
+
368
+ const route = {
369
+ code,
370
+ controller: (context: TRouterContext<this>) => new Page(route, renderer, context, layout),
371
+ options
372
+ };
373
+
374
+ this.errors[code] = route;
375
+ }
376
+
377
+ public all = (...args: TApiRegisterArgs<this>) => this.registerApi('*', ...args);
378
+ public options = (...args: TApiRegisterArgs<this>) => this.registerApi('OPTIONS', ...args);
379
+ public get = (...args: TApiRegisterArgs<this>) => this.registerApi('GET', ...args);
380
+ public post = (...args: TApiRegisterArgs<this>) => this.registerApi('POST', ...args);
381
+ public put = (...args: TApiRegisterArgs<this>) => this.registerApi('PUT', ...args);
382
+ public patch = (...args: TApiRegisterArgs<this>) => this.registerApi('PATCH', ...args);
383
+ public delete = (...args: TApiRegisterArgs<this>) => this.registerApi('DELETE', ...args);
384
+
385
+ public express(
386
+ middleware: (
387
+ req: Request,
388
+ res: Response,
389
+ next: NextFunction,
390
+ requestContext: TRouterContext<this>
391
+ ) => void
392
+ ) {
393
+ return (context: TRouterContext<this>) => new Promise((resolve) => {
394
+
395
+ context.request.res.on('finish', function() {
396
+ //console.log('the response has been sent', request.res.statusCode);
397
+ resolve(true);
398
+ });
399
+
400
+ middleware(
401
+ context.request.req,
402
+ context.request.res,
403
+ () => { resolve(true); },
404
+ context
405
+ )
406
+ })
407
+ }
408
+
409
+ protected registerApi(method: TRouteHttpMethod, ...args: TApiRegisterArgs<this>): this {
410
+
411
+ let path: string;
412
+ let options: Partial<TRouteOptions> = {};
413
+ let controller: TServerController<this>;
414
+
415
+ if (args.length === 2)
416
+ ([path, controller] = args)
417
+ else
418
+ ([path, options, controller] = args)
419
+
420
+ const { regex, keys } = buildRegex(path);
421
+
422
+ const route: TRoute = {
423
+
424
+ method: method,
425
+ path: path,
426
+ regex,
427
+ keys: keys,
428
+ options: {
429
+ ...defaultOptions,
430
+ ...options
431
+ },
432
+ controller
433
+ }
434
+
435
+ this.routes.push(route);
436
+
437
+ return this;
438
+ }
439
+
440
+ private async afterRegister() {
441
+
442
+ // Ordonne par ordre de priorité
443
+ this.config.debug && console.info("Loading routes ...");
444
+ this.routes.sort((r1, r2) => {
445
+
446
+ const prioDelta = r2.options.priority - r1.options.priority;
447
+ if (prioDelta !== 0)
448
+ return prioDelta;
449
+
450
+ // HTML avant json
451
+ if (r1.options.accept === 'html' && r2.options.accept !== 'html')
452
+ return -1;
453
+
454
+ // Unchanged
455
+ return 0;
456
+ })
457
+ // - Génère les définitions de route pour le client
458
+ this.config.debug && console.info(`Registered routes:`);
459
+ for (const route of this.routes) {
460
+
461
+ const chunkId = route.options["id"];
462
+
463
+ this.config.debug && console.info('-',
464
+ route.method,
465
+ route.path,
466
+ ' :: ', JSON.stringify(route.options)
467
+ );
468
+
469
+ if (chunkId)
470
+ this.ssrRoutes.push({
471
+ regex: route.regex.source,
472
+ keys: route.keys,
473
+ chunk: chunkId
474
+ });
475
+
476
+ }
477
+
478
+ this.config.debug && console.info(`Registered error pages:`);
479
+ for (const code in this.errors) {
480
+
481
+ const route = this.errors[code];
482
+ const chunkId = route.options["id"];
483
+
484
+ this.config.debug && console.info('-', code,
485
+ ' :: ', JSON.stringify(route.options)
486
+ );
487
+
488
+ this.ssrRoutes.push({
489
+ code: parseInt(code),
490
+ chunk: chunkId,
491
+ });
492
+ }
493
+
494
+ this.config.debug && console.info(`Registered layouts:`);
495
+ for (const layoutId in layoutsList) {
496
+
497
+ const layout = layoutsList[layoutId];
498
+
499
+ this.config.debug && console.info('-', layoutId, layout);
500
+ }
501
+
502
+ this.config.debug && console.info(this.routes.length + " routes where registered.");
503
+ }
504
+
505
+ /*----------------------------------
506
+ - RESOLUTION
507
+ ----------------------------------*/
508
+ public async middleware(req: express.Request, res: express.Response) {
509
+
510
+ // Don't cache HTML, because in case of update, assets file name will change (hash.ext)
511
+ // https://github.com/helmetjs/nocache/blob/main/index.ts
512
+ res.setHeader("Surrogate-Control", "no-store");
513
+ res.setHeader(
514
+ "Cache-Control",
515
+ "no-store, no-cache, must-revalidate, proxy-revalidate"
516
+ );
517
+
518
+ // Create request
519
+ let requestId = uuid();
520
+ const cachedPage = req.headers['bypasscache']
521
+ ? undefined
522
+ : this.cache[req.path];
523
+
524
+ const request = new ServerRequest(
525
+ requestId,
526
+
527
+ req.method as HttpMethod,
528
+ req.path, // url sans params
529
+ // Exclusion de req.files, car le middleware multipart les a normalisé dans req.body
530
+ { ...req.query, ...req.body },
531
+ req.headers,
532
+
533
+ res,
534
+ this
535
+ );
536
+
537
+ let response: ServerResponse<this>;
538
+ try {
539
+
540
+ // Hook
541
+ await this.runHook('request', request);
542
+
543
+ // Bulk API Requests
544
+ if (request.path === '/api' && typeof request.data.fetchers === "object") {
545
+
546
+ return await this.resolveApiBatch(request.data.fetchers, request);
547
+
548
+ } else {
549
+ response = await this.resolve(
550
+ request,
551
+ // If cached page, we only run routes with priority >= 10
552
+ cachedPage ? true : false
553
+ );
554
+ }
555
+ } catch (e) {
556
+ response = await this.handleError(e, request);
557
+ }
558
+
559
+ if (!res.headersSent) {
560
+
561
+ // Static pages
562
+ if (cachedPage) {
563
+ console.log('[router] Get static page from cache', req.path);
564
+ res.send( cachedPage.rendered );
565
+ return;
566
+ }
567
+
568
+ // Status
569
+ res.status(response.statusCode);
570
+ // Headers
571
+ res.header(response.headers);
572
+ // Data
573
+ res.send(response.data);
574
+ } else if (response.data !== 'true') {
575
+ throw new Error("Can't return data from the controller since response has already been sent via express.");
576
+ }
577
+ }
578
+
579
+ public createContextServices( request: ServerRequest<this> ) {
580
+
581
+ const contextServices: Partial<TRouterContextServices<this>> = {}
582
+ for (const serviceName in this.config.plugins) {
583
+
584
+ const routerService = this.config.plugins[serviceName];
585
+ if (!routerService)
586
+ throw new Error(`Could not access router service ${serviceName}. Maybe the referenced service is not started yet? Try to reduce its priority.`);
587
+
588
+ if (!routerService.requestService)
589
+ throw new Error(`Router service ${serviceName} is not implementing the requestService method from the RouterService interface.`);
590
+
591
+ const requestService = routerService.requestService( request );
592
+ if (requestService !== null)
593
+ contextServices[ serviceName ] = requestService;
594
+
595
+ }
596
+
597
+ return contextServices;
598
+ }
599
+
600
+ public resolve = (
601
+ request: ServerRequest<this>,
602
+ isStatic?: boolean
603
+ ) => new Promise<ServerResponse<this>>((resolve, reject) => {
604
+
605
+ // Create request context so we can access request context across all the request-triggered libs
606
+ context.run({
607
+ // This is for debugging
608
+ channelType: 'request',
609
+ channelId: request.id,
610
+ method: request.method,
611
+ path: request.path,
612
+ }, async () => {
613
+
614
+ const timeStart = Date.now();
615
+
616
+ if (this.status === 'starting') {
617
+ console.log(LogPrefix, `Waiting for servert to be resdy before resolving request`);
618
+ await this.started;
619
+ }
620
+
621
+ try {
622
+
623
+ const response = new ServerResponse<this>(request);
624
+
625
+ await this.runHook('resolve', request);
626
+
627
+ // Controller route
628
+ let route = this.controllers[request.path];
629
+ if (route !== undefined) {
630
+
631
+ // Create response
632
+ await this.resolvedRoute(route, response, timeStart);
633
+ if (response.wasProvided)
634
+ return resolve(response);
635
+ }
636
+
637
+ const contextStore = context.getStore();
638
+ if (contextStore)
639
+ contextStore.user = request.user?.email;
640
+
641
+ // Classic routes
642
+ for (route of this.routes) {
643
+
644
+ if (isStatic && !route.options.whenStatic)
645
+ continue;
646
+
647
+ // Match Method
648
+ if (request.method !== route.method && route.method !== '*')
649
+ continue;
650
+
651
+ // Match Response format
652
+ if (!request.accepts(route.options.accept))
653
+ continue;
654
+
655
+ const isMatching = matchRoute(route, request);
656
+ if (!isMatching)
657
+ continue;
658
+
659
+ await this.resolvedRoute(route, response, timeStart);
660
+ if (response.wasProvided)
661
+ return resolve(response);
662
+ }
663
+
664
+ reject( new NotFound() );
665
+
666
+ } catch (error) {
667
+
668
+ if (this.app.env.profile === 'dev') {
669
+ console.log('API batch error:', request.method, request.path, error);
670
+ const errOrigin = request.method + ' ' + request.path;
671
+ if (error.details === undefined)
672
+ error.details = { origin: errOrigin }
673
+ else
674
+ error.details.origin = errOrigin;
675
+ }
676
+
677
+ this.printTakenTime(timeStart);
678
+ reject( error );
679
+ }
680
+ });
681
+ });
682
+
683
+ private async resolvedRoute(
684
+ route: TRoute,
685
+ response: ServerResponse<this>,
686
+ timeStart: number
687
+ ) {
688
+
689
+ // Run on resolution hooks. Ex: authentication check
690
+ await this.runHook('resolved', route, response.request, response);
691
+
692
+ // Create response
693
+ await response.runController(route);
694
+ if (!response.wasProvided)
695
+ return;
696
+
697
+ // Set in cache
698
+ if (
699
+ response.request.path
700
+ && route.options.static
701
+ && route.options.static.urls.includes('*')
702
+ ) {
703
+ console.log('[router] Set in cache', response.request.path);
704
+ this.renderStatic(response.request.path, route.options.static, response.data);
705
+ }
706
+
707
+ const timeEndResolving = Date.now();
708
+ this.printTakenTime(timeStart, timeEndResolving);
709
+ }
710
+
711
+ private printTakenTime = (timeStart: number, timeEndResolving?: number) => {
712
+
713
+ if (this.app.env.name === 'server') return;
714
+
715
+ console.log( Math.round(Date.now() - timeStart) + 'ms' +
716
+ (timeEndResolving === undefined ? '' : ' | Routing: ' + Math.round(timeEndResolving - timeStart))
717
+ );
718
+ }
719
+
720
+ private async resolveApiBatch( fetchers: TFetcherList, request: ServerRequest<this> ) {
721
+
722
+ // TODO: use api.fetchSync instead
723
+
724
+ const responseData: TObjetDonnees = {};
725
+ for (const id in fetchers) {
726
+
727
+ const { method, path, data } = fetchers[id];
728
+
729
+ const response = await this.resolve(
730
+ request.children(method, path, data)
731
+ );
732
+
733
+ responseData[id] = response.data;
734
+
735
+ // TODO: merge response.headers ?
736
+ }
737
+
738
+ // Status
739
+ request.res.status(200);
740
+ // Data
741
+ request.res.json(responseData);
742
+ }
743
+
744
+ private async handleError( e: Error | CoreError | ZodError, request: ServerRequest<ServerRouter> ) {
745
+
746
+ if (e instanceof ZodError)
747
+ e = new InputError(
748
+ e.issues.map((e) => e.path.join('.') + ': ' + e.message).join(', ')
749
+ );
750
+
751
+ const code = 'http' in e ? e.http : 500;
752
+
753
+ const response = new ServerResponse(request).status(code)
754
+
755
+ // Rapport / debug
756
+ if (code === 500) {
757
+
758
+ // Print the error here so the stacktrace appears in the bug report logs
759
+ console.log(LogPrefix, "Error catched from the router:", e);
760
+
761
+ // Report error
762
+ await this.app.runHook('error', e, request);
763
+
764
+ // Don't exose technical errors to users
765
+ if (this.app.env.profile === 'prod')
766
+ e = new Error(
767
+ "We encountered an internal error, and our team has just been notified. Sorry for the inconvenience."
768
+ );
769
+
770
+ } else {
771
+
772
+ // For debugging HTTP errors
773
+ /*if (this.app.env.profile === "dev")
774
+ console.warn(e);*/
775
+
776
+ await this.app.runHook('error.' + code, e, request);
777
+ }
778
+
779
+ // Return error based on the request format
780
+ if (request.accepts("html")) {
781
+
782
+ const route = this.errors[code];
783
+ if (route === undefined)
784
+ throw new Error(`No route for error code ${code}`);
785
+
786
+ const jsonError = errorToJson(e);
787
+ await response.setRoute(route).runController(route, {
788
+ error: jsonError
789
+ });
790
+
791
+ } else if (request.accepts("json")) {
792
+ const jsonError = errorToJson(e);
793
+ await response.json(jsonError);
794
+ } else
795
+ await response.text(e.message);
796
+
797
+ return response;
798
+
799
+ }
800
+
801
+ }