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.
- package/.dockerignore +10 -0
- package/Rte.zip +0 -0
- package/cli/app/config.ts +54 -0
- package/cli/app/index.ts +195 -0
- package/cli/bin.js +11 -0
- package/cli/commands/build.ts +34 -0
- package/cli/commands/deploy/app.ts +29 -0
- package/cli/commands/deploy/web.ts +60 -0
- package/cli/commands/dev.ts +109 -0
- package/cli/commands/init.ts +85 -0
- package/cli/compiler/client/identite.ts +72 -0
- package/cli/compiler/client/index.ts +334 -0
- package/cli/compiler/common/babel/index.ts +170 -0
- package/cli/compiler/common/babel/plugins/index.ts +0 -0
- package/cli/compiler/common/babel/plugins/services.ts +579 -0
- package/cli/compiler/common/babel/routes/imports.ts +127 -0
- package/cli/compiler/common/babel/routes/routes.ts +1130 -0
- package/cli/compiler/common/files/autres.ts +39 -0
- package/cli/compiler/common/files/images.ts +35 -0
- package/cli/compiler/common/files/style.ts +78 -0
- package/cli/compiler/common/index.ts +154 -0
- package/cli/compiler/index.ts +532 -0
- package/cli/compiler/server/index.ts +211 -0
- package/cli/index.ts +189 -0
- package/cli/paths.ts +165 -0
- package/cli/print.ts +12 -0
- package/cli/tsconfig.json +38 -0
- package/cli/utils/index.ts +22 -0
- package/cli/utils/keyboard.ts +78 -0
- package/client/app/component.tsx +54 -0
- package/client/app/index.ts +142 -0
- package/client/app/service.ts +34 -0
- package/client/app.tsconfig.json +28 -0
- package/client/components/Button.tsx +298 -0
- package/client/components/Dialog/Manager.tsx +309 -0
- package/client/components/Dialog/card.tsx +208 -0
- package/client/components/Dialog/index.less +151 -0
- package/client/components/Dialog/status.less +176 -0
- package/client/components/Dialog/status.tsx +48 -0
- package/client/components/index.ts +2 -0
- package/client/components/types.d.ts +3 -0
- package/client/data/input.ts +32 -0
- package/client/global.d.ts +5 -0
- package/client/hooks.ts +22 -0
- package/client/index.ts +6 -0
- package/client/pages/_layout/index.less +6 -0
- package/client/pages/_layout/index.tsx +43 -0
- package/client/pages/bug.tsx.old +60 -0
- package/client/pages/useHeader.tsx +50 -0
- package/client/services/captcha/index.ts +67 -0
- package/client/services/router/components/Link.tsx +46 -0
- package/client/services/router/components/Page.tsx +55 -0
- package/client/services/router/components/router.tsx +218 -0
- package/client/services/router/index.tsx +521 -0
- package/client/services/router/request/api.ts +267 -0
- package/client/services/router/request/history.ts +5 -0
- package/client/services/router/request/index.ts +53 -0
- package/client/services/router/request/multipart.ts +147 -0
- package/client/services/router/response/index.tsx +128 -0
- package/client/services/router/response/page.ts +86 -0
- package/client/services/socket/index.ts +147 -0
- package/client/utils/dom.ts +77 -0
- package/common/app/index.ts +9 -0
- package/common/data/chaines/index.ts +54 -0
- package/common/data/dates.ts +179 -0
- package/common/data/markdown.ts +73 -0
- package/common/data/rte/nodes.ts +83 -0
- package/common/data/stats.ts +90 -0
- package/common/errors/index.tsx +326 -0
- package/common/router/index.ts +213 -0
- package/common/router/layouts.ts +93 -0
- package/common/router/register.ts +55 -0
- package/common/router/request/api.ts +77 -0
- package/common/router/request/index.ts +35 -0
- package/common/router/response/index.ts +45 -0
- package/common/router/response/page.ts +128 -0
- package/common/utils/rte.ts +183 -0
- package/common/utils.ts +7 -0
- package/doc/TODO.md +71 -0
- package/doc/front/router.md +27 -0
- package/doc/workspace/workspace.png +0 -0
- package/doc/workspace/workspace2.png +0 -0
- package/doc/workspace/workspace_26.01.22.png +0 -0
- package/package.json +171 -0
- package/server/app/commands.ts +141 -0
- package/server/app/container/config.ts +203 -0
- package/server/app/container/console/index.ts +550 -0
- package/server/app/container/index.ts +137 -0
- package/server/app/index.ts +273 -0
- package/server/app/service/container.ts +88 -0
- package/server/app/service/index.ts +235 -0
- package/server/app.tsconfig.json +28 -0
- package/server/context.ts +4 -0
- package/server/index.ts +4 -0
- package/server/services/auth/index.ts +250 -0
- package/server/services/auth/old.ts +277 -0
- package/server/services/auth/router/index.ts +95 -0
- package/server/services/auth/router/request.ts +54 -0
- package/server/services/auth/router/service.json +6 -0
- package/server/services/auth/service.json +6 -0
- package/server/services/cache/commands.ts +41 -0
- package/server/services/cache/index.ts +297 -0
- package/server/services/cache/service.json +6 -0
- package/server/services/cron/CronTask.ts +86 -0
- package/server/services/cron/index.ts +112 -0
- package/server/services/cron/service.json +6 -0
- package/server/services/disks/driver.ts +103 -0
- package/server/services/disks/drivers/local/index.ts +188 -0
- package/server/services/disks/drivers/local/service.json +6 -0
- package/server/services/disks/drivers/s3/index.ts +301 -0
- package/server/services/disks/drivers/s3/service.json +6 -0
- package/server/services/disks/index.ts +90 -0
- package/server/services/disks/service.json +6 -0
- package/server/services/email/index.ts +188 -0
- package/server/services/email/utils.ts +53 -0
- package/server/services/fetch/index.ts +201 -0
- package/server/services/fetch/service.json +7 -0
- package/server/services/models.7z +0 -0
- package/server/services/prisma/Facet.ts +142 -0
- package/server/services/prisma/index.ts +201 -0
- package/server/services/prisma/service.json +6 -0
- package/server/services/router/http/index.ts +217 -0
- package/server/services/router/http/multipart.ts +102 -0
- package/server/services/router/http/session.ts.old +40 -0
- package/server/services/router/index.ts +801 -0
- package/server/services/router/request/api.ts +87 -0
- package/server/services/router/request/index.ts +184 -0
- package/server/services/router/request/service.ts +21 -0
- package/server/services/router/request/validation/zod.ts +180 -0
- package/server/services/router/response/index.ts +338 -0
- package/server/services/router/response/mask/Filter.ts +323 -0
- package/server/services/router/response/mask/index.ts +60 -0
- package/server/services/router/response/mask/selecteurs.ts +92 -0
- package/server/services/router/response/page/document.tsx +160 -0
- package/server/services/router/response/page/index.tsx +196 -0
- package/server/services/router/service.json +6 -0
- package/server/services/router/service.ts +36 -0
- package/server/services/schema/index.ts +44 -0
- package/server/services/schema/request.ts +49 -0
- package/server/services/schema/router/index.ts +28 -0
- package/server/services/schema/router/service.json +6 -0
- package/server/services/schema/service.json +6 -0
- package/server/services/security/encrypt/aes/index.ts +85 -0
- package/server/services/security/encrypt/aes/service.json +6 -0
- package/server/services/socket/index.ts +162 -0
- package/server/services/socket/scope.ts +226 -0
- package/server/services/socket/service.json +6 -0
- package/server/services_old/SocketClient.ts +92 -0
- package/server/services_old/Token.old.ts +97 -0
- package/server/utils/slug.ts +79 -0
- package/tsconfig.common.json +45 -0
- package/tsconfig.json +3 -0
- package/types/aliases.d.ts +54 -0
- package/types/global/modules.d.ts +49 -0
- package/types/global/utils.d.ts +103 -0
- 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
|
+
}
|