5htp-core 0.6.2-97 → 0.6.2-99
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/package.json +1 -1
- package/server/app/index.ts +15 -5
- package/server/services/disks/index.ts +0 -2
- package/server/services/router/index.ts +28 -18
- package/server/services/router/request/validation/zod.ts +81 -33
- package/server/services/router/response/index.ts +2 -2
- package/server/services/schema/request.ts +4 -2
- package/types/icons.d.ts +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "5htp-core",
|
|
3
3
|
"description": "Convenient TypeScript framework designed for Performance and Productivity.",
|
|
4
|
-
"version": "0.6.2-
|
|
4
|
+
"version": "0.6.2-99",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
package/server/app/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import ServicesContainer, {
|
|
|
17
17
|
// Built-in
|
|
18
18
|
import type { default as Router, Request as ServerRequest, TRoute } from '@server/services/router';
|
|
19
19
|
import { Anomaly } from '@common/errors';
|
|
20
|
+
import { preprocessSchema } from '@server/services/router/request/validation/zod';
|
|
20
21
|
|
|
21
22
|
export { default as Services } from './service/container';
|
|
22
23
|
export type { TEnvConfig as Environment } from './container/config';
|
|
@@ -150,7 +151,9 @@ export abstract class Application<
|
|
|
150
151
|
console.log('----------------------------------');
|
|
151
152
|
console.log('- SERVICES');
|
|
152
153
|
console.log('----------------------------------');
|
|
153
|
-
await this.ready();
|
|
154
|
+
const startingServices = await this.ready();
|
|
155
|
+
await Promise.all(startingServices);
|
|
156
|
+
console.log('All services are ready');
|
|
154
157
|
await this.runHook('ready');
|
|
155
158
|
|
|
156
159
|
const startedTime = (Date.now() - startTime) / 1000;
|
|
@@ -179,12 +182,14 @@ export abstract class Application<
|
|
|
179
182
|
|
|
180
183
|
public register( service: AnyService ) {
|
|
181
184
|
|
|
182
|
-
service.ready();
|
|
185
|
+
return service.ready();
|
|
183
186
|
|
|
184
187
|
}
|
|
185
188
|
|
|
186
189
|
protected async ready() {
|
|
187
190
|
|
|
191
|
+
const startingServices: Promise<any>[] = [];
|
|
192
|
+
|
|
188
193
|
// Print services
|
|
189
194
|
const processService = async (propKey: string, service: AnyService, level: number = 0) => {
|
|
190
195
|
|
|
@@ -193,7 +198,8 @@ export abstract class Application<
|
|
|
193
198
|
|
|
194
199
|
// Services start shouldn't block app boot
|
|
195
200
|
// use await ServiceName.started to make services depends on each other
|
|
196
|
-
|
|
201
|
+
service.starting = service.ready();
|
|
202
|
+
startingServices.push(service.starting);
|
|
197
203
|
service.status = 'running';
|
|
198
204
|
console.log('-' + '-'.repeat(level * 1), propKey + ': ' + service.constructor.name);
|
|
199
205
|
|
|
@@ -203,12 +209,14 @@ export abstract class Application<
|
|
|
203
209
|
|
|
204
210
|
console.log('Attached service', service.constructor.name, 'to route', route.path);
|
|
205
211
|
|
|
212
|
+
const preprocessedSchema = route.schema ? preprocessSchema(route.schema) : undefined;
|
|
213
|
+
|
|
206
214
|
const origController = route.controller;
|
|
207
215
|
route.controller = (context: RouterContext) => {
|
|
208
216
|
|
|
209
217
|
// Filter data
|
|
210
|
-
const data =
|
|
211
|
-
?
|
|
218
|
+
const data = preprocessedSchema
|
|
219
|
+
? preprocessedSchema.parse( context.request.data )
|
|
212
220
|
: {};
|
|
213
221
|
|
|
214
222
|
// Run controller
|
|
@@ -253,6 +261,8 @@ export abstract class Application<
|
|
|
253
261
|
// Services start shouldn't block app boot
|
|
254
262
|
processService(serviceId, service);
|
|
255
263
|
}
|
|
264
|
+
|
|
265
|
+
return startingServices;
|
|
256
266
|
}
|
|
257
267
|
|
|
258
268
|
}
|
|
@@ -168,11 +168,6 @@ export default class ServerRouter<
|
|
|
168
168
|
|
|
169
169
|
public async ready() {
|
|
170
170
|
|
|
171
|
-
// Every hours
|
|
172
|
-
setInterval(() => {
|
|
173
|
-
this.refreshStaticPages();
|
|
174
|
-
}, 1000 * 60 * 60);
|
|
175
|
-
|
|
176
171
|
// Detect router services
|
|
177
172
|
for (const serviceName in this.config.plugins) {
|
|
178
173
|
const service = this.config.plugins[serviceName];
|
|
@@ -216,6 +211,12 @@ export default class ServerRouter<
|
|
|
216
211
|
);
|
|
217
212
|
};
|
|
218
213
|
|
|
214
|
+
|
|
215
|
+
// When all the services are ready, initialize static routes
|
|
216
|
+
this.app.on('ready', () => {
|
|
217
|
+
this.initStaticRoutes();
|
|
218
|
+
});
|
|
219
|
+
|
|
219
220
|
}
|
|
220
221
|
|
|
221
222
|
public async shutdown() {
|
|
@@ -238,6 +239,8 @@ export default class ServerRouter<
|
|
|
238
239
|
|
|
239
240
|
if (!rendered) {
|
|
240
241
|
|
|
242
|
+
console.log('[router] renderStatic: url', url);
|
|
243
|
+
|
|
241
244
|
const fullUrl = this.url(url, {}, true);
|
|
242
245
|
const response = await got( fullUrl, {
|
|
243
246
|
method: 'GET',
|
|
@@ -266,6 +269,26 @@ export default class ServerRouter<
|
|
|
266
269
|
|
|
267
270
|
}
|
|
268
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
|
+
|
|
269
292
|
private refreshStaticPages() {
|
|
270
293
|
|
|
271
294
|
console.log('[router] refreshStaticPages');
|
|
@@ -275,15 +298,10 @@ export default class ServerRouter<
|
|
|
275
298
|
if (page.expire && page.expire < Date.now()) {
|
|
276
299
|
|
|
277
300
|
this.renderStatic(pageUrl, page.options);
|
|
278
|
-
|
|
279
301
|
}
|
|
280
302
|
}
|
|
281
303
|
}
|
|
282
304
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
305
|
private registerRoutes(defModules: GlobImportedWithMetas<TRouteModule>) {
|
|
288
306
|
for (const routeModule of defModules) {
|
|
289
307
|
|
|
@@ -332,14 +350,6 @@ export default class ServerRouter<
|
|
|
332
350
|
|
|
333
351
|
this.routes.push(route);
|
|
334
352
|
|
|
335
|
-
// Add to static pages
|
|
336
|
-
// Should be a GET oage that don't take any parameter
|
|
337
|
-
if (options.static) {
|
|
338
|
-
for (const url of options.static.urls) {
|
|
339
|
-
this.renderStatic(url, options.static);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
353
|
return this;
|
|
344
354
|
|
|
345
355
|
}
|
|
@@ -5,42 +5,61 @@ export type TRichTextValidatorOptions = {
|
|
|
5
5
|
attachements?: boolean
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
function validateLexicalNode(node: any, opts: TRichTextValidatorOptions ) {
|
|
8
|
+
export const preprocessSchema = (schema: zod.ZodObject): zod.ZodObject => {
|
|
10
9
|
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
throw new InputError("Invalid rich text value (3).");
|
|
10
|
+
// Not working, data is {}
|
|
11
|
+
return schema;
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
if (!(schema instanceof zod.ZodObject))
|
|
14
|
+
return schema;
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
if (schema.withPreprocessing)
|
|
17
|
+
return schema;
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
const shape = schema.def.shape;
|
|
20
|
+
const newShape: Record<string, zod.ZodTypeAny> = {};
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
throw new InputError("Invalid rich text value (5).");
|
|
26
|
-
}
|
|
22
|
+
for (const key in shape) {
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
if (!['newEntity', 'email'].includes(key))
|
|
25
|
+
continue;
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
let current: zod.ZodTypeAny = shape[key];
|
|
28
|
+
while (current) {
|
|
29
|
+
|
|
30
|
+
const origType = current.type;
|
|
31
|
+
const preprocessor = toPreprocess[origType];
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
if (origType === 'object') {
|
|
34
|
+
newShape[key] = preprocessSchema(current as zod.ZodObject);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
36
37
|
|
|
38
|
+
if (preprocessor) {
|
|
39
|
+
newShape[key] = preprocessor(current);
|
|
40
|
+
console.log('====newShape', key, newShape[key]);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
37
43
|
|
|
38
|
-
|
|
44
|
+
current = current.def;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
39
47
|
|
|
48
|
+
const newSchema = zod.object(newShape);
|
|
49
|
+
newSchema.withPreprocessing = true;
|
|
50
|
+
return newSchema;
|
|
51
|
+
}
|
|
40
52
|
|
|
41
|
-
|
|
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),
|
|
42
62
|
|
|
43
|
-
return true;
|
|
44
63
|
}
|
|
45
64
|
|
|
46
65
|
export const schema = {
|
|
@@ -56,15 +75,6 @@ export const schema = {
|
|
|
56
75
|
return zod.file();
|
|
57
76
|
},
|
|
58
77
|
|
|
59
|
-
int: () => zod.preprocess( val => {
|
|
60
|
-
|
|
61
|
-
if (typeof val === "string")
|
|
62
|
-
return Number.parseInt(val);
|
|
63
|
-
|
|
64
|
-
return val;
|
|
65
|
-
|
|
66
|
-
}, zod.int()),
|
|
67
|
-
|
|
68
78
|
choice: ( choices: string[] | { value: any, label: string }[] | _ZodType, options: { multiple?: boolean } = {} ) => {
|
|
69
79
|
|
|
70
80
|
const normalizeValue = (value: any) => typeof value === 'object' ? value.value : value;
|
|
@@ -129,4 +139,42 @@ export const schema = {
|
|
|
129
139
|
})
|
|
130
140
|
}
|
|
131
141
|
|
|
132
|
-
|
|
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';
|
|
@@ -313,11 +313,11 @@ export default class ServerResponse<
|
|
|
313
313
|
return this.end();
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
public redirect(url: string, code: number = 302) {
|
|
316
|
+
public redirect(url: string, code: number = 302, absolute: boolean = false) {
|
|
317
317
|
|
|
318
318
|
debug && console.log("[routeur][response] Redirect", url);
|
|
319
319
|
this.statusCode = code;
|
|
320
|
-
this.headers['Location'] = this.router.url( url );
|
|
320
|
+
this.headers['Location'] = this.router.url( url, {}, absolute );
|
|
321
321
|
return this.end();
|
|
322
322
|
}
|
|
323
323
|
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from '@server/services/router';
|
|
13
13
|
|
|
14
14
|
// Ap
|
|
15
|
-
import { schema } from '@server/services/router/request/validation/zod';
|
|
15
|
+
import { preprocessSchema, schema } from '@server/services/router/request/validation/zod';
|
|
16
16
|
|
|
17
17
|
/*----------------------------------
|
|
18
18
|
- SERVICE CONFIG
|
|
@@ -42,6 +42,8 @@ export default(
|
|
|
42
42
|
|
|
43
43
|
const schema = typeof fields === 'object' ? zod.object(fields) : fields;
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
const preprocessedSchema = preprocessSchema(schema);
|
|
46
|
+
|
|
47
|
+
return preprocessedSchema.parse(request.data);
|
|
46
48
|
},
|
|
47
49
|
})
|
package/types/icons.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export type TIcones = "solid/spinner-third"|"rocket"|"user-circle"|"times"|"at"|"star"|"plus"|"minus"|"
|
|
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"
|