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,87 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Core
|
|
6
|
+
|
|
7
|
+
import RequestService from './service';
|
|
8
|
+
|
|
9
|
+
import ApiClientService, {
|
|
10
|
+
TApiFetchOptions, TFetcherList, TFetcherArgs, TFetcher,
|
|
11
|
+
TDataReturnedByFetchers
|
|
12
|
+
} from '@common/router/request/api';
|
|
13
|
+
|
|
14
|
+
/*----------------------------------
|
|
15
|
+
- TYPES
|
|
16
|
+
----------------------------------*/
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/*----------------------------------
|
|
20
|
+
- SERVICE
|
|
21
|
+
----------------------------------*/
|
|
22
|
+
export default class ApiClientRequest extends RequestService implements ApiClientService {
|
|
23
|
+
|
|
24
|
+
/*----------------------------------
|
|
25
|
+
- HIGH LEVEL
|
|
26
|
+
----------------------------------*/
|
|
27
|
+
|
|
28
|
+
public fetch<TProvidedData extends TFetcherList = TFetcherList>(
|
|
29
|
+
fetchers: TFetcherList
|
|
30
|
+
): TDataReturnedByFetchers<TProvidedData> {
|
|
31
|
+
throw new Error("api.fetch shouldn't be called here.");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/*----------------------------------
|
|
35
|
+
- PLACEHOLDERS
|
|
36
|
+
----------------------------------*/
|
|
37
|
+
|
|
38
|
+
public set( newData: TObjetDonnees ) {
|
|
39
|
+
throw new Error("api.set is not available on server side.");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public reload( ids?: string | string[], params?: TObjetDonnees ) {
|
|
43
|
+
throw new Error("api.set is not available on server side.");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/*----------------------------------
|
|
47
|
+
- API CALLS FROM SERVER
|
|
48
|
+
----------------------------------*/
|
|
49
|
+
|
|
50
|
+
public createFetcher<TData extends unknown = unknown>(...[method, path, data, options]: TFetcherArgs): TFetcher<TData> {
|
|
51
|
+
return {
|
|
52
|
+
method, path, data, options,
|
|
53
|
+
// We don't put the then and catch methods so the api consumer on server side will know it's a fetcher and not a promize to wait
|
|
54
|
+
} as TFetcher<TData>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public async fetchSync(fetchers: TFetcherList, alreadyLoadedData: {}): Promise<TObjetDonnees> {
|
|
58
|
+
|
|
59
|
+
const fetchedData: TObjetDonnees = { ...alreadyLoadedData };
|
|
60
|
+
|
|
61
|
+
for (const id in fetchers) {
|
|
62
|
+
|
|
63
|
+
const fetcher = fetchers[id]
|
|
64
|
+
if (!fetcher)
|
|
65
|
+
continue;
|
|
66
|
+
|
|
67
|
+
// Promise Fetcher (direct call from service method)
|
|
68
|
+
if ('then' in fetcher) {
|
|
69
|
+
fetchedData[id] = await fetcher;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const { method, path, data, options } = fetcher;
|
|
74
|
+
//this.router.config.debug && console.log(`[api] Resolving from internal api`, method, path, data);
|
|
75
|
+
|
|
76
|
+
// We don't fetch the already given data
|
|
77
|
+
if (id in fetchedData)
|
|
78
|
+
continue;
|
|
79
|
+
|
|
80
|
+
// Create a children request to resolve the api data
|
|
81
|
+
const request = this.request.children(method, path, data);
|
|
82
|
+
fetchedData[id] = await request.router.resolve(request).then(res => res.data);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return fetchedData;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Npm
|
|
6
|
+
import type express from 'express';
|
|
7
|
+
import ISO6391 from 'iso-639-1';
|
|
8
|
+
import accepts from 'accepts';
|
|
9
|
+
import Bowser from "bowser";
|
|
10
|
+
|
|
11
|
+
// Core
|
|
12
|
+
import BaseRequest from '@common/router/request';
|
|
13
|
+
|
|
14
|
+
// Specific
|
|
15
|
+
import type {
|
|
16
|
+
default as Router, Config as RouterConfig,
|
|
17
|
+
HttpMethod, HttpHeaders
|
|
18
|
+
} from '..';
|
|
19
|
+
import ApiClient from './api';
|
|
20
|
+
import ServerResponse from '../response';
|
|
21
|
+
import type { TAnyRouter } from '..';
|
|
22
|
+
|
|
23
|
+
/*----------------------------------
|
|
24
|
+
- TYPES
|
|
25
|
+
----------------------------------*/
|
|
26
|
+
|
|
27
|
+
const localeFilter = (input: any) => {
|
|
28
|
+
|
|
29
|
+
// Data type
|
|
30
|
+
if (typeof input !== 'string')
|
|
31
|
+
return;
|
|
32
|
+
|
|
33
|
+
// Extract ISO code
|
|
34
|
+
let lang = input.trim().split(/[-_]/)[0].toLowerCase();
|
|
35
|
+
|
|
36
|
+
// Check size
|
|
37
|
+
if (!ISO6391.validate(lang))
|
|
38
|
+
return;
|
|
39
|
+
|
|
40
|
+
return lang.toUpperCase();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type UploadedFile = File
|
|
44
|
+
|
|
45
|
+
/*----------------------------------
|
|
46
|
+
- CONTEXTE
|
|
47
|
+
----------------------------------*/
|
|
48
|
+
export default class ServerRequest<
|
|
49
|
+
TRouter extends TAnyRouter = TAnyRouter
|
|
50
|
+
> extends BaseRequest {
|
|
51
|
+
|
|
52
|
+
/*----------------------------------
|
|
53
|
+
- PROPRIÉTÉS
|
|
54
|
+
----------------------------------*/
|
|
55
|
+
|
|
56
|
+
public id: string;
|
|
57
|
+
public isVirtual: boolean = false;
|
|
58
|
+
|
|
59
|
+
// Requete
|
|
60
|
+
public method: HttpMethod;
|
|
61
|
+
public ip?: string;
|
|
62
|
+
public locale: string;
|
|
63
|
+
public domain: string;
|
|
64
|
+
public headers: HttpHeaders = {};
|
|
65
|
+
public cookies: TObjetDonnees = {};
|
|
66
|
+
public validatedData?: TObjetDonnees; // Results from the last schema.validate
|
|
67
|
+
|
|
68
|
+
// reponse
|
|
69
|
+
public response?: ServerResponse<TRouter>;
|
|
70
|
+
public router: TRouter;
|
|
71
|
+
|
|
72
|
+
// Origin
|
|
73
|
+
public req: express.Request;
|
|
74
|
+
public res: express.Response;
|
|
75
|
+
|
|
76
|
+
// Services
|
|
77
|
+
public api: ApiClient;
|
|
78
|
+
|
|
79
|
+
/*----------------------------------
|
|
80
|
+
- INITIALISATION
|
|
81
|
+
----------------------------------*/
|
|
82
|
+
public constructor(
|
|
83
|
+
|
|
84
|
+
id: string,
|
|
85
|
+
method: HttpMethod,
|
|
86
|
+
path: string,
|
|
87
|
+
data: TObjetDonnees | undefined,
|
|
88
|
+
headers: HttpHeaders | undefined,
|
|
89
|
+
|
|
90
|
+
res: express.Response,
|
|
91
|
+
router: TRouter,
|
|
92
|
+
isVirtual: boolean = false
|
|
93
|
+
) {
|
|
94
|
+
|
|
95
|
+
super(path);
|
|
96
|
+
|
|
97
|
+
this.id = id;
|
|
98
|
+
this.isVirtual = isVirtual;
|
|
99
|
+
|
|
100
|
+
this.req = res.req;
|
|
101
|
+
this.res = res
|
|
102
|
+
this.router = router;
|
|
103
|
+
this.api = new ApiClient(this);
|
|
104
|
+
|
|
105
|
+
this.url = this.req.protocol + '://' + this.req.get('host') + this.req.originalUrl;
|
|
106
|
+
this.host = this.req.get('host') as string;
|
|
107
|
+
this.method = method;
|
|
108
|
+
this.headers = headers || {};
|
|
109
|
+
this.locale = this.getLocale();
|
|
110
|
+
this.domain = res.req.hostname;
|
|
111
|
+
this.cookies = res.req.cookies;
|
|
112
|
+
|
|
113
|
+
this.ip = res.req.ip;
|
|
114
|
+
|
|
115
|
+
this.data = data || {};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public children(method: HttpMethod, path: string, data: TObjetDonnees | undefined) {
|
|
119
|
+
const children = new ServerRequest(
|
|
120
|
+
this.id, method, path, data, { ...this.headers, accept: 'application/json' },
|
|
121
|
+
this.res, this.router, true
|
|
122
|
+
);
|
|
123
|
+
children.user = this.user;
|
|
124
|
+
return children;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private getLocale() {
|
|
128
|
+
|
|
129
|
+
const fromQuery = localeFilter(this.req.query.lang);
|
|
130
|
+
if (fromQuery) {
|
|
131
|
+
this.res.cookie('lang', fromQuery);
|
|
132
|
+
return fromQuery;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const locale = (
|
|
136
|
+
// Member settings
|
|
137
|
+
this.user?.locale
|
|
138
|
+
||
|
|
139
|
+
// URL
|
|
140
|
+
localeFilter( this.req.cookies.lang )
|
|
141
|
+
||
|
|
142
|
+
// Browser
|
|
143
|
+
localeFilter( this.req.acceptsLanguages()[0] )
|
|
144
|
+
||
|
|
145
|
+
// Default
|
|
146
|
+
'EN'
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return locale ? locale.toUpperCase() : 'EN'
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public cookie( key: string, consume: boolean = false ) {
|
|
153
|
+
|
|
154
|
+
const value = this.req.cookies[ key ];
|
|
155
|
+
|
|
156
|
+
if (consume)
|
|
157
|
+
this.res.clearCookie(key);
|
|
158
|
+
|
|
159
|
+
return value;
|
|
160
|
+
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/*----------------------------------
|
|
164
|
+
- TESTS
|
|
165
|
+
----------------------------------*/
|
|
166
|
+
|
|
167
|
+
public accepts(datatype: string | undefined) {
|
|
168
|
+
// https://github.com/jshttp/accepts
|
|
169
|
+
return datatype === undefined || datatype === '*' || accepts(this).type(datatype);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
public device(): Bowser.Parser.ParsedResult | undefined {
|
|
173
|
+
return this.headers['user-agent'] !== undefined
|
|
174
|
+
? Bowser.parse(this.headers['user-agent'])
|
|
175
|
+
: undefined;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
public deviceString(): string | undefined {
|
|
179
|
+
const info = this.device();
|
|
180
|
+
if (info === undefined) return undefined;
|
|
181
|
+
const { os, browser } = info;
|
|
182
|
+
return (os.name || 'Unknown OS') + ' ' + (os.versionName || os.version || '') + ' / ' + (browser.name || 'Unknown browser') + ' ' + (browser.version || '');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
import type Router from '..';
|
|
6
|
+
import type ServerRequest from '.';
|
|
7
|
+
|
|
8
|
+
/*----------------------------------
|
|
9
|
+
- SERVICE
|
|
10
|
+
----------------------------------*/
|
|
11
|
+
export default abstract class RequestService {
|
|
12
|
+
|
|
13
|
+
public constructor(
|
|
14
|
+
public request: ServerRequest<Router>,
|
|
15
|
+
public router = request.router,
|
|
16
|
+
public app = router.app
|
|
17
|
+
) {
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
}
|
|
@@ -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';
|