5htp-core 0.6.2 → 0.6.3
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/client/app/index.ts +2 -1
- package/client/assets/css/components/table.less +1 -0
- package/client/components/Input.tsx +0 -2
- package/client/components/Rte/Editor.tsx +2 -0
- package/client/components/Rte/index.tsx +0 -1
- package/client/services/router/request/api.ts +0 -9
- package/common/router/request/api.ts +0 -8
- package/package.json +1 -1
- package/server/app/container/console/index.ts +65 -48
- package/server/app/index.ts +19 -8
- package/server/app/service/index.ts +55 -15
- package/server/services/auth/router/index.ts +3 -1
- package/server/services/disks/driver.ts +5 -1
- package/server/services/disks/drivers/s3/index.ts +2 -2
- package/server/services/disks/index.ts +10 -5
- package/server/services/email/index.ts +1 -1
- package/server/services/prisma/Facet.ts +39 -15
- package/server/services/prisma/index.ts +5 -7
- package/server/services/router/http/multipart.ts +5 -0
- package/server/services/router/index.ts +50 -35
- package/server/services/router/request/api.ts +0 -12
- package/server/services/router/request/validation/zod.ts +180 -0
- package/server/services/router/response/index.ts +14 -9
- package/server/services/router/response/page/document.tsx +5 -3
- package/server/services/router/service.ts +7 -4
- package/server/services/schema/request.ts +21 -34
- package/server/services/schema/router/index.ts +3 -3
- package/types/icons.d.ts +1 -1
- package/common/data/input/validate.ts +0 -54
- package/server/services/router/request/validation/index.ts +0 -23
- package/server/services/router/request/validation/schema.ts +0 -211
- package/server/services/router/request/validation/validator.ts +0 -117
- package/server/services/router/request/validation/validators.ts +0 -485
|
@@ -10,7 +10,7 @@ import type { Application } from '@server/app';
|
|
|
10
10
|
import Service from '@server/app/service';
|
|
11
11
|
|
|
12
12
|
// Specific
|
|
13
|
-
import Facet, { TSubset } from './Facet';
|
|
13
|
+
import Facet, { TDelegate, TSubset, Transform } from './Facet';
|
|
14
14
|
|
|
15
15
|
/*----------------------------------
|
|
16
16
|
- TYPES
|
|
@@ -52,13 +52,11 @@ export default class ModelsManager extends Service<Config, Hooks, Application> {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
public Facet<
|
|
55
|
-
D extends
|
|
56
|
-
findMany(args?: any): Promise<any>
|
|
57
|
-
findFirst(args?: any): Promise<any>
|
|
58
|
-
},
|
|
55
|
+
D extends TDelegate<R>,
|
|
59
56
|
S extends TSubset,
|
|
60
|
-
R
|
|
61
|
-
|
|
57
|
+
R,
|
|
58
|
+
RT
|
|
59
|
+
>(...args: [D, S, Transform<S, R, RT>]) {
|
|
62
60
|
|
|
63
61
|
return new Facet(
|
|
64
62
|
this.client,
|
|
@@ -81,11 +81,16 @@ export const traiterMultipart = (...canaux: any[]) => {
|
|
|
81
81
|
&&
|
|
82
82
|
donnee.data instanceof Buffer
|
|
83
83
|
){
|
|
84
|
+
const md5 = donnee.md5;
|
|
85
|
+
const data = donnee.data;
|
|
84
86
|
donnee = new File(donnee.data, donnee.name, {
|
|
85
87
|
type: donnee.mimetype,
|
|
86
88
|
lastModified: Date.now(),
|
|
87
89
|
//size: donnee.size,
|
|
88
90
|
});
|
|
91
|
+
|
|
92
|
+
donnee.md5 = md5;
|
|
93
|
+
donnee.data = data;
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
brancheA[ cle ] = donnee;
|
|
@@ -55,7 +55,7 @@ export type { default as Request, UploadedFile } from "./request";
|
|
|
55
55
|
export type { default as Response, TRouterContext } from "./response";
|
|
56
56
|
export type { TRoute, TAnyRoute } from '@common/router';
|
|
57
57
|
|
|
58
|
-
export type TApiRegisterArgs<TRouter extends
|
|
58
|
+
export type TApiRegisterArgs<TRouter extends TServerRouter> = ([
|
|
59
59
|
path: string,
|
|
60
60
|
controller: TServerController<TRouter>
|
|
61
61
|
] | [
|
|
@@ -64,7 +64,7 @@ export type TApiRegisterArgs<TRouter extends ServerRouter> = ([
|
|
|
64
64
|
controller: TServerController<TRouter>
|
|
65
65
|
])
|
|
66
66
|
|
|
67
|
-
export type TServerController<TRouter extends
|
|
67
|
+
export type TServerController<TRouter extends TServerRouter> = (context: TRouterContext<TRouter>) => any;
|
|
68
68
|
|
|
69
69
|
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS'
|
|
70
70
|
export type TRouteHttpMethod = HttpMethod | '*';
|
|
@@ -83,8 +83,7 @@ export type HttpHeaders = { [cle: string]: string }
|
|
|
83
83
|
const LogPrefix = '[router]';
|
|
84
84
|
|
|
85
85
|
export type Config<
|
|
86
|
-
|
|
87
|
-
TAdditionnalSsrData extends {} = {}
|
|
86
|
+
TServices extends TRouterServicesList
|
|
88
87
|
> = {
|
|
89
88
|
|
|
90
89
|
debug: boolean,
|
|
@@ -96,18 +95,16 @@ export type Config<
|
|
|
96
95
|
http: HttpServiceConfig,
|
|
97
96
|
|
|
98
97
|
context: (
|
|
99
|
-
request: ServerRequest<
|
|
98
|
+
request: ServerRequest<TServerRouter>,
|
|
100
99
|
app: Application
|
|
101
|
-
) =>
|
|
100
|
+
) => {},
|
|
102
101
|
|
|
103
|
-
plugins:
|
|
104
|
-
[routerServiceId: string]: RouterService
|
|
105
|
-
}
|
|
102
|
+
plugins: TServices
|
|
106
103
|
}
|
|
107
104
|
|
|
108
105
|
// Set it as a function, so when we instanciate the services, we can callthis.router to pass the router instance in roiuter services
|
|
109
106
|
type TRouterServicesList = {
|
|
110
|
-
[serviceName: string]: RouterService
|
|
107
|
+
[serviceName: string]: RouterService
|
|
111
108
|
}
|
|
112
109
|
|
|
113
110
|
export type Hooks = {
|
|
@@ -117,14 +114,20 @@ export type Hooks = {
|
|
|
117
114
|
export type TControllerDefinition = {
|
|
118
115
|
path?: string,
|
|
119
116
|
schema?: zod.ZodSchema,
|
|
120
|
-
controller: TServerController<
|
|
117
|
+
controller: TServerController<TServerRouter>,
|
|
121
118
|
}
|
|
122
119
|
|
|
120
|
+
export type TServerRouter = ServerRouter<Application, TRouterServicesList, Config<TRouterServicesList>>;
|
|
121
|
+
|
|
123
122
|
/*----------------------------------
|
|
124
123
|
- CLASSE
|
|
125
124
|
----------------------------------*/
|
|
126
|
-
export default class ServerRouter
|
|
127
|
-
extends
|
|
125
|
+
export default class ServerRouter<
|
|
126
|
+
TApplication extends Application,
|
|
127
|
+
TServices extends TRouterServicesList,
|
|
128
|
+
TConfig extends Config<TServices>,
|
|
129
|
+
>
|
|
130
|
+
extends Service<TConfig, Hooks, TApplication, TApplication> implements BaseRouter {
|
|
128
131
|
|
|
129
132
|
public disks = this.use<DisksManager>('Core/Disks', { optional: true });
|
|
130
133
|
|
|
@@ -151,7 +154,7 @@ export default class ServerRouter
|
|
|
151
154
|
- SERVICE
|
|
152
155
|
----------------------------------*/
|
|
153
156
|
|
|
154
|
-
public constructor( ...args: TServiceArgs<ServerRouter>) {
|
|
157
|
+
public constructor( ...args: TServiceArgs< ServerRouter<TApplication, TServices, TConfig> >) {
|
|
155
158
|
|
|
156
159
|
super(...args);
|
|
157
160
|
|
|
@@ -165,14 +168,11 @@ export default class ServerRouter
|
|
|
165
168
|
|
|
166
169
|
public async ready() {
|
|
167
170
|
|
|
168
|
-
// Every hours
|
|
169
|
-
setInterval(() => {
|
|
170
|
-
this.refreshStaticPages();
|
|
171
|
-
}, 1000 * 60 * 60);
|
|
172
|
-
|
|
173
171
|
// Detect router services
|
|
174
172
|
for (const serviceName in this.config.plugins) {
|
|
175
|
-
|
|
173
|
+
const service = this.config.plugins[serviceName];
|
|
174
|
+
service.parent = this;
|
|
175
|
+
this.app.register( service )
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
// Use require to avoid circular references
|
|
@@ -211,6 +211,12 @@ export default class ServerRouter
|
|
|
211
211
|
);
|
|
212
212
|
};
|
|
213
213
|
|
|
214
|
+
|
|
215
|
+
// When all the services are ready, initialize static routes
|
|
216
|
+
this.app.on('ready', () => {
|
|
217
|
+
this.initStaticRoutes();
|
|
218
|
+
});
|
|
219
|
+
|
|
214
220
|
}
|
|
215
221
|
|
|
216
222
|
public async shutdown() {
|
|
@@ -233,6 +239,8 @@ export default class ServerRouter
|
|
|
233
239
|
|
|
234
240
|
if (!rendered) {
|
|
235
241
|
|
|
242
|
+
console.log('[router] renderStatic: url', url);
|
|
243
|
+
|
|
236
244
|
const fullUrl = this.url(url, {}, true);
|
|
237
245
|
const response = await got( fullUrl, {
|
|
238
246
|
method: 'GET',
|
|
@@ -261,6 +269,26 @@ export default class ServerRouter
|
|
|
261
269
|
|
|
262
270
|
}
|
|
263
271
|
|
|
272
|
+
private initStaticRoutes() {
|
|
273
|
+
|
|
274
|
+
for (const route of this.routes) {
|
|
275
|
+
|
|
276
|
+
if (!route.options.static)
|
|
277
|
+
continue;
|
|
278
|
+
|
|
279
|
+
// Add to static pages
|
|
280
|
+
// Should be a GET oage that don't take any parameter
|
|
281
|
+
for (const url of route.options.static.urls) {
|
|
282
|
+
this.renderStatic(url, route.options.static);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Every hours, refresh static pages
|
|
287
|
+
setInterval(() => {
|
|
288
|
+
this.refreshStaticPages();
|
|
289
|
+
}, 1000 * 60 * 60);
|
|
290
|
+
}
|
|
291
|
+
|
|
264
292
|
private refreshStaticPages() {
|
|
265
293
|
|
|
266
294
|
console.log('[router] refreshStaticPages');
|
|
@@ -270,15 +298,10 @@ export default class ServerRouter
|
|
|
270
298
|
if (page.expire && page.expire < Date.now()) {
|
|
271
299
|
|
|
272
300
|
this.renderStatic(pageUrl, page.options);
|
|
273
|
-
|
|
274
301
|
}
|
|
275
302
|
}
|
|
276
303
|
}
|
|
277
304
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
305
|
private registerRoutes(defModules: GlobImportedWithMetas<TRouteModule>) {
|
|
283
306
|
for (const routeModule of defModules) {
|
|
284
307
|
|
|
@@ -327,14 +350,6 @@ export default class ServerRouter
|
|
|
327
350
|
|
|
328
351
|
this.routes.push(route);
|
|
329
352
|
|
|
330
|
-
// Add to static pages
|
|
331
|
-
// Should be a GET oage that don't take any parameter
|
|
332
|
-
if (options.static) {
|
|
333
|
-
for (const url of options.static.urls) {
|
|
334
|
-
this.renderStatic(url, options.static);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
353
|
return this;
|
|
339
354
|
|
|
340
355
|
}
|
|
@@ -370,10 +385,10 @@ export default class ServerRouter
|
|
|
370
385
|
req: Request,
|
|
371
386
|
res: Response,
|
|
372
387
|
next: NextFunction,
|
|
373
|
-
requestContext: TRouterContext
|
|
388
|
+
requestContext: TRouterContext<this>
|
|
374
389
|
) => void
|
|
375
390
|
) {
|
|
376
|
-
return (context: TRouterContext) => new Promise((resolve) => {
|
|
391
|
+
return (context: TRouterContext<this>) => new Promise((resolve) => {
|
|
377
392
|
|
|
378
393
|
context.request.res.on('finish', function() {
|
|
379
394
|
//console.log('the response has been sent', request.res.statusCode);
|
|
@@ -31,18 +31,6 @@ export default class ApiClientRequest extends RequestService implements ApiClien
|
|
|
31
31
|
throw new Error("api.fetch shouldn't be called here.");
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
public get = <TData extends unknown = unknown>(path: string, data?: TObjetDonnees, opts?: TApiFetchOptions) =>
|
|
35
|
-
this.createFetcher<TData>('GET', path, data, opts);
|
|
36
|
-
|
|
37
|
-
public post = <TData extends unknown = unknown>(path: string, data?: TObjetDonnees, opts?: TApiFetchOptions) =>
|
|
38
|
-
this.createFetcher<TData>('POST', path, data, opts);
|
|
39
|
-
|
|
40
|
-
public put = <TData extends unknown = unknown>(path: string, data?: TObjetDonnees, opts?: TApiFetchOptions) =>
|
|
41
|
-
this.createFetcher<TData>('PUT', path, data, opts);
|
|
42
|
-
|
|
43
|
-
public delete = <TData extends unknown = unknown>(path: string, data?: TObjetDonnees, opts?: TApiFetchOptions) =>
|
|
44
|
-
this.createFetcher<TData>('DELETE', path, data, opts);
|
|
45
|
-
|
|
46
34
|
/*----------------------------------
|
|
47
35
|
- PLACEHOLDERS
|
|
48
36
|
----------------------------------*/
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { InputError } from '@common/errors';
|
|
2
|
+
import zod, { _ZodType } from 'zod';
|
|
3
|
+
|
|
4
|
+
export type TRichTextValidatorOptions = {
|
|
5
|
+
attachements?: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const preprocessSchema = (schema: zod.ZodObject): zod.ZodObject => {
|
|
9
|
+
|
|
10
|
+
// Not working, data is {}
|
|
11
|
+
return schema;
|
|
12
|
+
|
|
13
|
+
if (!(schema instanceof zod.ZodObject))
|
|
14
|
+
return schema;
|
|
15
|
+
|
|
16
|
+
if (schema.withPreprocessing)
|
|
17
|
+
return schema;
|
|
18
|
+
|
|
19
|
+
const shape = schema.def.shape;
|
|
20
|
+
const newShape: Record<string, zod.ZodTypeAny> = {};
|
|
21
|
+
|
|
22
|
+
for (const key in shape) {
|
|
23
|
+
|
|
24
|
+
if (!['newEntity', 'email'].includes(key))
|
|
25
|
+
continue;
|
|
26
|
+
|
|
27
|
+
let current: zod.ZodTypeAny = shape[key];
|
|
28
|
+
while (current) {
|
|
29
|
+
|
|
30
|
+
const origType = current.type;
|
|
31
|
+
const preprocessor = toPreprocess[origType];
|
|
32
|
+
|
|
33
|
+
if (origType === 'object') {
|
|
34
|
+
newShape[key] = preprocessSchema(current as zod.ZodObject);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (preprocessor) {
|
|
39
|
+
newShape[key] = preprocessor(current);
|
|
40
|
+
console.log('====newShape', key, newShape[key]);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
current = current.def;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const newSchema = zod.object(newShape);
|
|
49
|
+
newSchema.withPreprocessing = true;
|
|
50
|
+
return newSchema;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const toPreprocess = {
|
|
54
|
+
|
|
55
|
+
string: (zString: zod.ZodString) => zod.preprocess( val => {
|
|
56
|
+
return val === '' ? undefined : val;
|
|
57
|
+
}, zString),
|
|
58
|
+
|
|
59
|
+
int: (zInt: zod.ZodInt) => zod.preprocess( val => {
|
|
60
|
+
return typeof val === 'string' ? Number.parseInt(val) : val;
|
|
61
|
+
}, zInt),
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const schema = {
|
|
66
|
+
...zod,
|
|
67
|
+
|
|
68
|
+
file: () => {
|
|
69
|
+
|
|
70
|
+
// Chaine = url ancien fichier = exclusion de la valeur pour conserver l'ancien fichier
|
|
71
|
+
// NOTE: Si la valeur est présente mais undefined, alors on supprimera le fichier
|
|
72
|
+
/*if (typeof val === 'string')
|
|
73
|
+
return true;*/
|
|
74
|
+
|
|
75
|
+
return zod.file();
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
choice: ( choices: string[] | { value: any, label: string }[] | _ZodType, options: { multiple?: boolean } = {} ) => {
|
|
79
|
+
|
|
80
|
+
const normalizeValue = (value: any) => typeof value === 'object' ? value.value : value;
|
|
81
|
+
|
|
82
|
+
const valueType: _ZodType = Array.isArray(choices)
|
|
83
|
+
? zod.enum( choices.map(normalizeValue) )
|
|
84
|
+
: zod.string();
|
|
85
|
+
|
|
86
|
+
const itemType = zod.union([
|
|
87
|
+
|
|
88
|
+
zod.object({ value: valueType, label: zod.string() }),
|
|
89
|
+
|
|
90
|
+
valueType
|
|
91
|
+
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
const type = options.multiple ? zod.array( itemType ) : itemType;
|
|
95
|
+
|
|
96
|
+
return type.transform(v => {
|
|
97
|
+
if (options.multiple) {
|
|
98
|
+
return v.map(normalizeValue);
|
|
99
|
+
} else {
|
|
100
|
+
return normalizeValue(v);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
richText: (opts: TRichTextValidatorOptions = {}) => schema.custom(val => {
|
|
106
|
+
|
|
107
|
+
if (typeof val !== 'string') {
|
|
108
|
+
console.error("Invalid rich text format.", val);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// We get a stringified json as input since the editor workds with JSON string
|
|
113
|
+
try {
|
|
114
|
+
val = JSON.parse(val);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error("Failed to parse rich text json:", error, val);
|
|
117
|
+
return false;//throw new InputError("Invalid rich text format.");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check that the root exists and has a valid type
|
|
121
|
+
if (!val || typeof val !== 'object' || typeof val.root !== 'object' || val.root.type !== 'root') {
|
|
122
|
+
console.error("Invalid rich text value (1).", val);
|
|
123
|
+
return false;//throw new InputError("Invalid rich text value (1).");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Check if root has children array
|
|
127
|
+
if (!Array.isArray(val.root.children)) {
|
|
128
|
+
console.error("Invalid rich text value (2).", val);
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Validate each child node in root
|
|
133
|
+
for (const child of val.root.children) {
|
|
134
|
+
if (!validateLexicalNode(child, opts))
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return true;
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Recursive function to validate each node
|
|
143
|
+
function validateLexicalNode(node: any, opts: TRichTextValidatorOptions ) {
|
|
144
|
+
|
|
145
|
+
// Each node should be an object with a `type` property
|
|
146
|
+
if (typeof node !== 'object' || !node.type || typeof node.type !== 'string')
|
|
147
|
+
throw new InputError("Invalid rich text value (3).");
|
|
148
|
+
|
|
149
|
+
// Validate text nodes
|
|
150
|
+
if (node.type === 'text') {
|
|
151
|
+
|
|
152
|
+
if (typeof node.text !== 'string')
|
|
153
|
+
throw new InputError("Invalid rich text value (4).");
|
|
154
|
+
|
|
155
|
+
// Validate paragraph, heading, or other structural nodes that may contain children
|
|
156
|
+
} else if (['paragraph', 'heading', 'list', 'listitem'].includes(node.type)) {
|
|
157
|
+
|
|
158
|
+
if (!Array.isArray(node.children) || !node.children.every(children => validateLexicalNode(children, opts))) {
|
|
159
|
+
throw new InputError("Invalid rich text value (5).");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Files upload
|
|
163
|
+
} else if (node.type === 'image') {
|
|
164
|
+
|
|
165
|
+
// Check if allowed
|
|
166
|
+
/*if (opts.attachements === undefined)
|
|
167
|
+
throw new InputError("Image attachments not allowed in this rich text field.");*/
|
|
168
|
+
|
|
169
|
+
// TODO: check mime
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
// Upload file
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export type { default as z } from 'zod';
|
|
@@ -12,7 +12,7 @@ import express from 'express';
|
|
|
12
12
|
|
|
13
13
|
// Core
|
|
14
14
|
import { Application } from '@server/app';
|
|
15
|
-
import type ServerRouter from '@server/services/router';
|
|
15
|
+
import type { RouterService, default as ServerRouter, TServerRouter } from '@server/services/router';
|
|
16
16
|
import ServerRequest from '@server/services/router/request';
|
|
17
17
|
import { TRoute, TAnyRoute, TDomainsList } from '@common/router';
|
|
18
18
|
import { NotFound, Forbidden, Anomaly } from '@common/errors';
|
|
@@ -38,10 +38,10 @@ export type TBasicSSrData = {
|
|
|
38
38
|
domains: TDomainsList
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export type TRouterContext<TRouter extends
|
|
41
|
+
export type TRouterContext<TRouter extends TServerRouter> = (
|
|
42
42
|
// Request context
|
|
43
43
|
{
|
|
44
|
-
app:
|
|
44
|
+
app: TRouter["app"],
|
|
45
45
|
context: TRouterContext<TRouter>, // = this
|
|
46
46
|
request: ServerRequest<TRouter>,
|
|
47
47
|
api: ServerRequest<TRouter>["api"],
|
|
@@ -52,14 +52,19 @@ export type TRouterContext<TRouter extends ServerRouter = ServerRouter> = (
|
|
|
52
52
|
|
|
53
53
|
Router: TRouter,
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
& TRouterContextServices<TRouter>
|
|
56
56
|
)
|
|
57
57
|
|
|
58
|
-
export type TRouterContextServices<
|
|
58
|
+
export type TRouterContextServices<
|
|
59
|
+
TRouter extends TServerRouter,
|
|
60
|
+
TPlugins = TRouter["config"]["plugins"]
|
|
61
|
+
> = (
|
|
59
62
|
// Custom context via servuces
|
|
60
63
|
// For each roiuter service, return the request service (returned by roiuterService.requestService() )
|
|
61
64
|
{
|
|
62
|
-
[serviceName in keyof
|
|
65
|
+
[serviceName in keyof TPlugins]: TPlugins[serviceName] extends RouterService
|
|
66
|
+
? ReturnType<TPlugins[serviceName]["requestService"]>
|
|
67
|
+
: TPlugins[serviceName]
|
|
63
68
|
}
|
|
64
69
|
)
|
|
65
70
|
|
|
@@ -212,7 +217,7 @@ export default class ServerResponse<
|
|
|
212
217
|
return this;
|
|
213
218
|
}
|
|
214
219
|
|
|
215
|
-
public async render( page: Page, context: TRouterContext
|
|
220
|
+
public async render( page: Page, context: TRouterContext<TRouter>, additionnalData: {} ) {
|
|
216
221
|
|
|
217
222
|
// Set page in context for the client side
|
|
218
223
|
context.page = page;
|
|
@@ -308,11 +313,11 @@ export default class ServerResponse<
|
|
|
308
313
|
return this.end();
|
|
309
314
|
}
|
|
310
315
|
|
|
311
|
-
public redirect(url: string, code: number = 302) {
|
|
316
|
+
public redirect(url: string, code: number = 302, absolute: boolean = false) {
|
|
312
317
|
|
|
313
318
|
debug && console.log("[routeur][response] Redirect", url);
|
|
314
319
|
this.statusCode = code;
|
|
315
|
-
this.headers['Location'] = this.router.url( url );
|
|
320
|
+
this.headers['Location'] = this.router.url( url, {}, absolute );
|
|
316
321
|
return this.end();
|
|
317
322
|
}
|
|
318
323
|
|
|
@@ -39,8 +39,9 @@ export default class DocumentRenderer<TRouter extends Router> {
|
|
|
39
39
|
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1" />
|
|
40
40
|
|
|
41
41
|
{/* CSS */}
|
|
42
|
-
<link rel="stylesheet" type="text/css" href="/public/icons.css" />
|
|
43
42
|
<link rel="preload" href="/public/client.css" as="style" />
|
|
43
|
+
<link rel="preload" as="font" href={"/public/icons.woff2?v=" + BUILD_ID} type="font/woff2" />
|
|
44
|
+
<link rel="stylesheet" type="text/css" href="/public/icons.css" />
|
|
44
45
|
<link rel="stylesheet" type="text/css" href="/public/client.css" />
|
|
45
46
|
<ColorSchemeScript />
|
|
46
47
|
|
|
@@ -120,6 +121,7 @@ export default class DocumentRenderer<TRouter extends Router> {
|
|
|
120
121
|
|
|
121
122
|
private styles( page: Page ) {
|
|
122
123
|
return <>
|
|
124
|
+
<link rel="preload" as="font" href={"/public/icons.woff2?v=" + BUILD_ID} type="font/woff2" />
|
|
123
125
|
<link rel="stylesheet" type="text/css" href={"/public/icons.css?" + BUILD_ID} />
|
|
124
126
|
<link rel="preload" href="/public/client.css" as="style" />
|
|
125
127
|
<link rel="stylesheet" type="text/css" href="/public/client.css" />
|
|
@@ -135,8 +137,8 @@ export default class DocumentRenderer<TRouter extends Router> {
|
|
|
135
137
|
|
|
136
138
|
private async scripts( response: ServerResponse<TRouter>, page: Page ) {
|
|
137
139
|
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
+
const ssrData = response.forSsr(page);
|
|
141
|
+
const context = safeStringify( ssrData );
|
|
140
142
|
const routesForClient = JSON.stringify( this.router.ssrRoutes );
|
|
141
143
|
|
|
142
144
|
return <>
|
|
@@ -11,7 +11,10 @@ import type { default as Router } from '.';
|
|
|
11
11
|
import type ServerRequest from './request';
|
|
12
12
|
import type RequestService from './request/service';
|
|
13
13
|
|
|
14
|
-
export type TRouterServiceArgs =
|
|
14
|
+
export type TRouterServiceArgs = [
|
|
15
|
+
getConfig: TServiceArgs<RouterService>[1],
|
|
16
|
+
app: Application,
|
|
17
|
+
];
|
|
15
18
|
|
|
16
19
|
/*----------------------------------
|
|
17
20
|
- SERVICE
|
|
@@ -20,10 +23,10 @@ export default abstract class RouterService<
|
|
|
20
23
|
TConfig extends {} = {}
|
|
21
24
|
> extends Service<TConfig, {}, Application> {
|
|
22
25
|
|
|
23
|
-
public constructor( ...
|
|
24
|
-
super(
|
|
26
|
+
public constructor( ...[config, app]: TRouterServiceArgs) {
|
|
27
|
+
super(app, config, app);
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
public abstract requestService( request: ServerRequest<
|
|
30
|
+
public abstract requestService( request: ServerRequest<RouterService> ): RequestService | {} | null;
|
|
28
31
|
|
|
29
32
|
}
|
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
- DEPENDANCES
|
|
3
3
|
----------------------------------*/
|
|
4
4
|
|
|
5
|
+
// Npm
|
|
6
|
+
import zod from 'zod';
|
|
7
|
+
import { SomeType } from 'zod/v4/core';
|
|
8
|
+
|
|
5
9
|
// Core
|
|
6
10
|
import {
|
|
7
|
-
default as Router,
|
|
11
|
+
default as Router, TServerRouter, Request as ServerRequest
|
|
8
12
|
} from '@server/services/router';
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// Specific
|
|
13
|
-
import ServerSchemaValidator from '.';
|
|
14
|
+
// Ap
|
|
15
|
+
import { preprocessSchema, schema } from '@server/services/router/request/validation/zod';
|
|
14
16
|
|
|
15
17
|
/*----------------------------------
|
|
16
18
|
- SERVICE CONFIG
|
|
@@ -25,38 +27,23 @@ export type TConfig = {
|
|
|
25
27
|
/*----------------------------------
|
|
26
28
|
- SERVICE
|
|
27
29
|
----------------------------------*/
|
|
28
|
-
export default
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
public app = router.app
|
|
35
|
-
) {
|
|
36
|
-
|
|
37
|
-
super(app);
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
public validate<TSchemaFieldsA extends TSchemaFields>(
|
|
42
|
-
fields: TSchemaFieldsA | Schema<TSchemaFieldsA>
|
|
43
|
-
): TValidatedData<TSchemaFieldsA> {
|
|
30
|
+
export default(
|
|
31
|
+
request: ServerRequest< TServerRouter >,
|
|
32
|
+
config: TConfig,
|
|
33
|
+
router = request.router,
|
|
34
|
+
app = router.app
|
|
35
|
+
) => ({
|
|
44
36
|
|
|
45
|
-
|
|
37
|
+
...schema,
|
|
46
38
|
|
|
47
|
-
|
|
39
|
+
validate( fields: zod.ZodSchema | { [key: string]: zod.ZodSchema } ) {
|
|
48
40
|
|
|
49
|
-
|
|
50
|
-
const values = schema.validate( this.request.data, {
|
|
51
|
-
debug: this.config.debug,
|
|
52
|
-
validateDeps: false,
|
|
53
|
-
validators: this
|
|
54
|
-
}, []);
|
|
41
|
+
config.debug && console.log(LogPrefix, "Validate request data:", request.data);
|
|
55
42
|
|
|
56
|
-
|
|
57
|
-
this.request.validatedData = values;
|
|
43
|
+
const schema = typeof fields === 'object' ? zod.object(fields) : fields;
|
|
58
44
|
|
|
59
|
-
|
|
60
|
-
}
|
|
45
|
+
const preprocessedSchema = preprocessSchema(schema);
|
|
61
46
|
|
|
62
|
-
|
|
47
|
+
return preprocessedSchema.parse(request.data);
|
|
48
|
+
},
|
|
49
|
+
})
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
RouterService
|
|
9
9
|
} from '@server/services/router';
|
|
10
10
|
|
|
11
|
-
import
|
|
11
|
+
import makeRequestValidators from '../request';
|
|
12
12
|
|
|
13
13
|
/*----------------------------------
|
|
14
14
|
- TYPES
|
|
@@ -22,7 +22,7 @@ export default class SchemaRouterService<
|
|
|
22
22
|
TUser extends {} = {}
|
|
23
23
|
> extends RouterService {
|
|
24
24
|
|
|
25
|
-
public requestService( request: ServerRequest )
|
|
26
|
-
return
|
|
25
|
+
public requestService( request: ServerRequest ) {
|
|
26
|
+
return makeRequestValidators( request, this.config );
|
|
27
27
|
}
|
|
28
28
|
}
|
package/types/icons.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export type TIcones = "solid/spinner-third"|"
|
|
1
|
+
export type TIcones = "solid/spinner-third"|"rocket"|"user-circle"|"brands/linkedin"|"play"|"stop"|"trash"|"times"|"at"|"star"|"plus"|"minus"|"magnet"|"paper-plane"|"search"|"check"|"plus-circle"|"regular/shield-check"|"angle-down"|"clock"|"cog"|"ellipsis-h"|"long-arrow-right"|"lightbulb"|"long-arrow-left"|"phone"|"arrow-right"|"plane-departure"|"comments-alt"|"user-shield"|"shield-alt"|"chart-line"|"money-bill-wave"|"link"|"file-alt"|"solid/crown"|"eye"|"pen"|"file"|"envelope"|"angle-up"|"user-plus"|"sack-dollar"|"info-circle"|"mouse-pointer"|"thumbs-up"|"dollar-sign"|"download"|"brands/google"|"brands/whatsapp"|"crown"|"check-circle"|"exclamation-circle"|"times-circle"|"arrow-left"|"key"|"building"|"briefcase"|"map-marker-alt"|"graduation-cap"|"solid/check-circle"|"solid/exclamation-triangle"|"solid/times-circle"|"hourglass"|"angle-left"|"angle-right"|"broom"|"question-circle"|"coin"|"coins"|"plug"|"arrow-to-bottom"|"external-link"|"magic"|"minus-circle"|"user"|"meh-rolling-eyes"|"bold"|"italic"|"underline"|"strikethrough"|"subscript"|"superscript"|"code"|"unlink"|"font"|"empty-set"|"horizontal-rule"|"page-break"|"image"|"table"|"poll"|"columns"|"sticky-note"|"caret-right"|"align-left"|"align-center"|"align-right"|"align-justify"|"indent"|"outdent"|"list-ul"|"check-square"|"h1"|"h2"|"h3"|"h4"|"list-ol"|"paragraph"|"quote-left"
|