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,338 @@
|
|
|
1
|
+
/* INSPIRATION:
|
|
2
|
+
https://laravel.com/docs/8.x/responses
|
|
3
|
+
https://docs.adonisjs.com/guides/response
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/*----------------------------------
|
|
7
|
+
- DEPENDANCES
|
|
8
|
+
----------------------------------*/
|
|
9
|
+
|
|
10
|
+
// Npm
|
|
11
|
+
import express from 'express';
|
|
12
|
+
|
|
13
|
+
// Core
|
|
14
|
+
import { Application } from '@server/app';
|
|
15
|
+
import type { AnyRouterService, default as ServerRouter, TServerRouter, TAnyRouter } from '@server/services/router';
|
|
16
|
+
import ServerRequest from '@server/services/router/request';
|
|
17
|
+
import { TRoute, TAnyRoute, TDomainsList } from '@common/router';
|
|
18
|
+
import { NotFound, Forbidden, Anomaly } from '@common/errors';
|
|
19
|
+
import BaseResponse, { TResponseData } from '@common/router/response';
|
|
20
|
+
import Page from './page';
|
|
21
|
+
|
|
22
|
+
// To move into a new npm module: json-mask
|
|
23
|
+
import jsonMask from './mask';
|
|
24
|
+
|
|
25
|
+
// Types
|
|
26
|
+
import type { TBasicUser } from '@server/services/auth';
|
|
27
|
+
|
|
28
|
+
/*----------------------------------
|
|
29
|
+
- TYPES
|
|
30
|
+
----------------------------------*/
|
|
31
|
+
|
|
32
|
+
const debug = true;
|
|
33
|
+
|
|
34
|
+
export type TBasicSSrData = {
|
|
35
|
+
request: { data: TObjetDonnees, id: string },
|
|
36
|
+
page: { chunkId: string, data?: TObjetDonnees },
|
|
37
|
+
user: TBasicUser | null,
|
|
38
|
+
domains: TDomainsList
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type TRouterContext<TRouter extends TServerRouter> = (
|
|
42
|
+
// Request context
|
|
43
|
+
{
|
|
44
|
+
app: TRouter["app"],
|
|
45
|
+
context: TRouterContext<TRouter>, // = this
|
|
46
|
+
request: ServerRequest<TRouter>,
|
|
47
|
+
api: ServerRequest<TRouter>["api"],
|
|
48
|
+
response: ServerResponse<TRouter>,
|
|
49
|
+
route: TRoute,
|
|
50
|
+
page?: Page,
|
|
51
|
+
|
|
52
|
+
Router: TRouter,
|
|
53
|
+
}
|
|
54
|
+
& TRouterContextServices<TRouter>
|
|
55
|
+
//& TRouterRequestContext<TRouter>
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
export type TRouterContextServices<
|
|
59
|
+
TRouter extends TServerRouter,
|
|
60
|
+
TPlugins = TRouter["config"]["plugins"]
|
|
61
|
+
> = (
|
|
62
|
+
// Custom context via servuces
|
|
63
|
+
// For each roiuter service, return the request service (returned by roiuterService.requestService() )
|
|
64
|
+
{
|
|
65
|
+
[serviceName in keyof TPlugins]: TPlugins[serviceName] extends AnyRouterService
|
|
66
|
+
? ReturnType<TPlugins[serviceName]["requestService"]>
|
|
67
|
+
: TPlugins[serviceName]
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
export type TRouterRequestContext<
|
|
72
|
+
TRouter extends TServerRouter
|
|
73
|
+
> = ReturnType<TRouter["config"]["context"]>
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
/*----------------------------------
|
|
77
|
+
- CLASSE
|
|
78
|
+
----------------------------------*/
|
|
79
|
+
export default class ServerResponse<
|
|
80
|
+
TRouter extends TAnyRouter,
|
|
81
|
+
TRequestContext = TRouterContext<TAnyRouter>,
|
|
82
|
+
TData extends TResponseData = TResponseData
|
|
83
|
+
> extends BaseResponse<TData, ServerRequest<TRouter>> {
|
|
84
|
+
|
|
85
|
+
// Services
|
|
86
|
+
public app: Application;
|
|
87
|
+
public router: TRouter;
|
|
88
|
+
|
|
89
|
+
// Response metadata
|
|
90
|
+
public statusCode: number = 200;
|
|
91
|
+
public headers: {[cle: string]: string} = {}
|
|
92
|
+
public cookie: express.Response["cookie"];
|
|
93
|
+
public clearCookie: express.Response["clearCookie"];
|
|
94
|
+
public canonicalUrl: URL;
|
|
95
|
+
|
|
96
|
+
// If data was provided by at lead one controller
|
|
97
|
+
public wasProvided = false;
|
|
98
|
+
|
|
99
|
+
public constructor( request: ServerRequest<TRouter> ) {
|
|
100
|
+
|
|
101
|
+
super(request);
|
|
102
|
+
|
|
103
|
+
this.cookie = this.request.res.cookie.bind(this.request.res);
|
|
104
|
+
this.clearCookie = this.request.res.clearCookie.bind(this.request.res);
|
|
105
|
+
|
|
106
|
+
this.router = request.router;
|
|
107
|
+
this.app = this.router.app;
|
|
108
|
+
|
|
109
|
+
this.canonicalUrl = new URL(request.url);
|
|
110
|
+
this.canonicalUrl.search = '';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public async runController( route: TAnyRoute, additionnalData: {} = {} ) {
|
|
114
|
+
|
|
115
|
+
this.route = route;
|
|
116
|
+
|
|
117
|
+
// Update canonical url
|
|
118
|
+
this.updateCanonicalUrl(route);
|
|
119
|
+
|
|
120
|
+
// Create response context for controllers
|
|
121
|
+
const context = await this.createContext(route);
|
|
122
|
+
|
|
123
|
+
// Run controller
|
|
124
|
+
const content = await this.route.controller( context );
|
|
125
|
+
if (content === undefined)
|
|
126
|
+
return;
|
|
127
|
+
|
|
128
|
+
// No need to process the content
|
|
129
|
+
if (content instanceof ServerResponse)
|
|
130
|
+
return;
|
|
131
|
+
// Render react page to html
|
|
132
|
+
else if (content instanceof Page)
|
|
133
|
+
await this.render(content, context, additionnalData);
|
|
134
|
+
// Return HTML
|
|
135
|
+
else if (typeof content === 'string' && this.route.options.accept === 'html')
|
|
136
|
+
await this.html(content);
|
|
137
|
+
// Return JSON
|
|
138
|
+
else
|
|
139
|
+
await this.json(content);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private updateCanonicalUrl( route: TAnyRoute ) {
|
|
143
|
+
|
|
144
|
+
if (!route.options.canonicalParams)
|
|
145
|
+
return;
|
|
146
|
+
|
|
147
|
+
for (const key of route.options.canonicalParams) {
|
|
148
|
+
const paramValue = this.request.data[ key ];
|
|
149
|
+
if (paramValue !== undefined)
|
|
150
|
+
this.canonicalUrl.searchParams.set(key, paramValue);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/*----------------------------------
|
|
155
|
+
- INTERNAL
|
|
156
|
+
----------------------------------*/
|
|
157
|
+
|
|
158
|
+
// Start controller services
|
|
159
|
+
private async createContext( route: TRoute ): Promise<TRequestContext> {
|
|
160
|
+
|
|
161
|
+
const contextServices = this.router.createContextServices(this.request);
|
|
162
|
+
|
|
163
|
+
const customSsrData = this.router.config.context(this.request, this.app);
|
|
164
|
+
|
|
165
|
+
// TODO: transmiss safe data (especially for Router), as Router info could be printed on client side
|
|
166
|
+
const context: TRequestContext = {
|
|
167
|
+
// Router context
|
|
168
|
+
app: this.app,
|
|
169
|
+
context: undefined as TRequestContext,
|
|
170
|
+
request: this.request,
|
|
171
|
+
response: this,
|
|
172
|
+
route: route,
|
|
173
|
+
api: this.request.api,
|
|
174
|
+
|
|
175
|
+
Router: this.router,
|
|
176
|
+
|
|
177
|
+
// Router services
|
|
178
|
+
...(contextServices as TRouterContextServices<TRouter>),
|
|
179
|
+
...customSsrData
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
context.context = context;
|
|
183
|
+
|
|
184
|
+
return context;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public forSsr( page: Page<TRouter> ): TBasicSSrData {
|
|
188
|
+
|
|
189
|
+
const customSsrData = this.router.config.context(this.request, this.app);
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
request: {
|
|
193
|
+
id: this.request.id,
|
|
194
|
+
data: this.request.data,
|
|
195
|
+
},
|
|
196
|
+
page: {
|
|
197
|
+
chunkId: page.chunkId,
|
|
198
|
+
data: page.data
|
|
199
|
+
},
|
|
200
|
+
domains: this.router.config.domains,
|
|
201
|
+
...customSsrData
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public status(code: number) {
|
|
206
|
+
this.statusCode = code;
|
|
207
|
+
return this;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
public setHeaders( headers: {[cle: string]: string} ) {
|
|
211
|
+
this.headers = { ...this.headers, ...headers };
|
|
212
|
+
return this;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/*----------------------------------
|
|
216
|
+
- DATA RESPONSE
|
|
217
|
+
----------------------------------*/
|
|
218
|
+
|
|
219
|
+
public type( mimetype: string ) {
|
|
220
|
+
this.headers['Content-Type'] = mimetype;
|
|
221
|
+
return this;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
public async render( page: Page, context: TRouterContext<TRouter>, additionnalData: {} ) {
|
|
225
|
+
|
|
226
|
+
// Set page in context for the client side
|
|
227
|
+
context.page = page;
|
|
228
|
+
|
|
229
|
+
// Prepare page & fetch data
|
|
230
|
+
page.data = await page.fetchData();
|
|
231
|
+
if (additionnalData !== undefined) // Example: error message for error pages
|
|
232
|
+
page.data = { ...page.data, ...additionnalData }
|
|
233
|
+
|
|
234
|
+
// Render page
|
|
235
|
+
await this.router.runHook('render', page);
|
|
236
|
+
const document = await page.render();
|
|
237
|
+
this.html(document);
|
|
238
|
+
|
|
239
|
+
// Never put html in the cache
|
|
240
|
+
// Because assets urls need to be updated when their hash has been changed by a release
|
|
241
|
+
this.request.res.setHeader("Expires", "0");
|
|
242
|
+
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
public async json(data?: any, mask?: string) {
|
|
246
|
+
|
|
247
|
+
// RAPPEL: On jsonMask aussi les requetes internes, car leurs données seront imprimées au SSR pour le contexte client
|
|
248
|
+
// filtreApi vérifie systèmatiquement si la donnée a été filtrée
|
|
249
|
+
// NOTE: On évite le filtrage sans masque spécifié (performances + risques erreurs)
|
|
250
|
+
if (mask !== undefined)
|
|
251
|
+
data = await jsonMask(data, mask, this.request.user);
|
|
252
|
+
|
|
253
|
+
this.headers['Content-Type'] = 'application/json';
|
|
254
|
+
this.data = this.request.isVirtual ? data : JSON.stringify(data);
|
|
255
|
+
return this.end();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
public html(html: string) {
|
|
259
|
+
|
|
260
|
+
this.headers['Content-Type'] = 'text/html';
|
|
261
|
+
this.data = html;
|
|
262
|
+
return this.end();
|
|
263
|
+
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
public xml(xml: string) {
|
|
267
|
+
|
|
268
|
+
this.headers['Content-Type'] = 'text/xml';
|
|
269
|
+
this.data = xml;
|
|
270
|
+
return this.end();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
public text(text: string, mimetype: string = 'text/plain') {
|
|
274
|
+
|
|
275
|
+
this.headers['Content-Type'] = mimetype;
|
|
276
|
+
this.data = text;
|
|
277
|
+
return this.end();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// TODO: https://github.com/adonisjs/http-server/blob/develop/src/Response/index.ts#L430
|
|
281
|
+
public async file( filename: string, mimetype?: string ) {
|
|
282
|
+
|
|
283
|
+
// Securité
|
|
284
|
+
if (filename.includes('..'))
|
|
285
|
+
throw new Forbidden("Disallowed");
|
|
286
|
+
|
|
287
|
+
// // Force absolute path
|
|
288
|
+
// if (!filename.startsWith( this.app.path.root ))
|
|
289
|
+
// filename = filename[0] === '/'
|
|
290
|
+
// ? this.app.path.root + '/bin' + filename
|
|
291
|
+
// : this.app.path.data + '/' + filename;
|
|
292
|
+
// Disk not provided = file response disabled
|
|
293
|
+
if (this.router.disks === undefined)
|
|
294
|
+
throw new Anomaly("Router: Unable to return file response in router, because no disk has been given in the router config.");
|
|
295
|
+
|
|
296
|
+
// Retirve disk driver
|
|
297
|
+
const disk = this.router.disks.get('default');
|
|
298
|
+
|
|
299
|
+
// Verif existance
|
|
300
|
+
const fileExists = await disk.exists('data', filename);
|
|
301
|
+
if (!fileExists) {
|
|
302
|
+
console.log("File " + filename + " was not found.");
|
|
303
|
+
throw new NotFound();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// envoi filename
|
|
307
|
+
const file = await disk.readFile('data', filename, {
|
|
308
|
+
encoding: 'buffer'
|
|
309
|
+
});
|
|
310
|
+
this.data = file;
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
// Mimetype
|
|
314
|
+
if (mimetype !== undefined)
|
|
315
|
+
this.headers['Content-Type'] = mimetype;
|
|
316
|
+
|
|
317
|
+
return this.end();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
public redirect(url: string, code: number = 302, absolute: boolean = false) {
|
|
321
|
+
|
|
322
|
+
debug && console.log("[routeur][response] Redirect", url);
|
|
323
|
+
this.statusCode = code;
|
|
324
|
+
this.headers['Location'] = this.router.url( url, {}, absolute );
|
|
325
|
+
return this.end();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
public end() {
|
|
329
|
+
this.wasProvided = true;
|
|
330
|
+
return this;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
public next() {
|
|
334
|
+
this.wasProvided = false;
|
|
335
|
+
return this;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Libs
|
|
6
|
+
import { TSelecteur } from './selecteurs';
|
|
7
|
+
|
|
8
|
+
/*----------------------------------
|
|
9
|
+
- TYPES
|
|
10
|
+
----------------------------------*/
|
|
11
|
+
|
|
12
|
+
type TObjet = { [cle: string]: TObjet | any };
|
|
13
|
+
|
|
14
|
+
/*----------------------------------
|
|
15
|
+
- CONFIG
|
|
16
|
+
----------------------------------*/
|
|
17
|
+
|
|
18
|
+
const debug = false;
|
|
19
|
+
|
|
20
|
+
const maxLevel = 10; // Prévention contre les références circulaires
|
|
21
|
+
|
|
22
|
+
/*----------------------------------
|
|
23
|
+
- CLASS
|
|
24
|
+
----------------------------------*/
|
|
25
|
+
export default class Filtre {
|
|
26
|
+
|
|
27
|
+
public constructor() {
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public filtrer(
|
|
33
|
+
donnee: TObjet | Array<unknown> | Date,
|
|
34
|
+
schema?: TSelecteur,
|
|
35
|
+
chemin: string[] = [],
|
|
36
|
+
schemaParent?: TObjet
|
|
37
|
+
): any {
|
|
38
|
+
|
|
39
|
+
// Prévention contre les références circulaires
|
|
40
|
+
if (chemin.length > maxLevel)
|
|
41
|
+
throw new Error(`Erreur: Niveau max (${maxLevel}) atteint via la branche ${chemin.join('.')}. Vérifier s'il n'existe pas une référence circulaire dans l'objet à filtrer.`)
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
|
|
45
|
+
// Tableau: on itère chaque élement de celui-ci
|
|
46
|
+
if (Array.isArray(donnee)) {
|
|
47
|
+
|
|
48
|
+
debug && console.log(`[requete][reponse][filtre]`, chemin.join('.'), ': Tableau');
|
|
49
|
+
|
|
50
|
+
return this.tableau(donnee, schema, chemin, schemaParent);
|
|
51
|
+
|
|
52
|
+
// Valeur: Chaque true doit être remplacé par la donnee[ nomBranche ] correspondante
|
|
53
|
+
// Si la donnée est une chaine, un nombre, etc ... On la traite comme s'il y avait un true
|
|
54
|
+
} else if (
|
|
55
|
+
// Extrémité de la branche
|
|
56
|
+
schema === true
|
|
57
|
+
||
|
|
58
|
+
// Valeur non-itérable
|
|
59
|
+
!donnee
|
|
60
|
+
||
|
|
61
|
+
typeof donnee !== 'object'
|
|
62
|
+
||
|
|
63
|
+
donnee instanceof Date
|
|
64
|
+
) {
|
|
65
|
+
|
|
66
|
+
debug && console.log(`[requete][reponse][filtre]`, chemin.join('.'), ': Valeur');
|
|
67
|
+
|
|
68
|
+
return this.valeur(
|
|
69
|
+
donnee,
|
|
70
|
+
schema,
|
|
71
|
+
chemin
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Objet
|
|
75
|
+
} else {
|
|
76
|
+
|
|
77
|
+
debug && console.log(`[requete][reponse][filtre]`, chemin.join('.'), ': Objet');
|
|
78
|
+
|
|
79
|
+
return this.objet(donnee, schema, chemin, schemaParent);
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
} catch (error) {
|
|
84
|
+
|
|
85
|
+
console.error('Erreur =', error, '|| données =', donnee, '|| chemin =', chemin, '|| schema =', schema);
|
|
86
|
+
throw new Error(`Erreur lors du filtrage. Infos ci-dessus.`);
|
|
87
|
+
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private tableau(
|
|
92
|
+
donnee: any[],
|
|
93
|
+
schema: TSelecteur | undefined,
|
|
94
|
+
|
|
95
|
+
chemin: string[],
|
|
96
|
+
schemaParent?: TObjet
|
|
97
|
+
) {
|
|
98
|
+
|
|
99
|
+
let retour: any[] = [];
|
|
100
|
+
|
|
101
|
+
for (const iElem in donnee) {
|
|
102
|
+
retour.push(
|
|
103
|
+
this.filtrer(
|
|
104
|
+
donnee[iElem],
|
|
105
|
+
schema,
|
|
106
|
+
|
|
107
|
+
[...chemin, iElem],
|
|
108
|
+
schemaParent
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return retour;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private objet(
|
|
117
|
+
donnee: TObjet,
|
|
118
|
+
schema: TSelecteur | undefined,
|
|
119
|
+
|
|
120
|
+
chemin: string[],
|
|
121
|
+
schemaParent?: TObjet
|
|
122
|
+
) {
|
|
123
|
+
|
|
124
|
+
if (typeof schema === 'object') {
|
|
125
|
+
|
|
126
|
+
// Exemple: article ( titre enfant( @ ) )
|
|
127
|
+
// Copie du schéma parent
|
|
128
|
+
if (schema['@'] === true) {
|
|
129
|
+
|
|
130
|
+
if (schemaParent === undefined)
|
|
131
|
+
throw new Error(`Référence au schéma parent trouvée, mais impossible d'accéder au schéma parent (schemaParent = undefined)`);
|
|
132
|
+
|
|
133
|
+
schema = schemaParent;
|
|
134
|
+
|
|
135
|
+
// Exemple: *
|
|
136
|
+
// Parcours et filtre toutes les entrées
|
|
137
|
+
} else if (schema['*'] === true) {
|
|
138
|
+
|
|
139
|
+
// L'itération se fera directement sur les données fournies
|
|
140
|
+
schema = undefined;
|
|
141
|
+
|
|
142
|
+
// Exemple: * ( nom symbole )
|
|
143
|
+
// Applique un selecteurs à toutes les entrées
|
|
144
|
+
} else if (schema['*'] !== undefined) {
|
|
145
|
+
|
|
146
|
+
const schemaBranches = schema['*'];
|
|
147
|
+
schema = {};
|
|
148
|
+
|
|
149
|
+
// Applique le schema du wildcard à toutes les entrées de l'objet
|
|
150
|
+
// TODO: alternative plus performante
|
|
151
|
+
for (const cle in donnee)
|
|
152
|
+
schema[cle] = schemaBranches;
|
|
153
|
+
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let retour: TObjet = {};
|
|
158
|
+
|
|
159
|
+
// Liste des clés à itérer
|
|
160
|
+
let clesAiterer: string[];
|
|
161
|
+
if (schema !== undefined)
|
|
162
|
+
clesAiterer = Object.keys(schema as object);
|
|
163
|
+
else // En dernier recours, on itère tout simplement les données de l'objet
|
|
164
|
+
clesAiterer = Object.keys(donnee);
|
|
165
|
+
|
|
166
|
+
// Objet
|
|
167
|
+
for (const nomBranche of clesAiterer) {
|
|
168
|
+
|
|
169
|
+
const cheminA = [...chemin, nomBranche];
|
|
170
|
+
let donneeBranche = undefined;
|
|
171
|
+
|
|
172
|
+
// Extraction de la valeur de la propriété
|
|
173
|
+
if (donneeBranche === undefined)
|
|
174
|
+
donneeBranche = donnee[nomBranche];
|
|
175
|
+
|
|
176
|
+
// Filtrage de la valeur de la propriété
|
|
177
|
+
retour[nomBranche] = this.filtrer(
|
|
178
|
+
donneeBranche,
|
|
179
|
+
schema !== undefined ? schema[nomBranche] : undefined,
|
|
180
|
+
|
|
181
|
+
cheminA,
|
|
182
|
+
|
|
183
|
+
schema as TObjet
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return retour;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Traitement des données aux extrémités (auxquelles font référence les true)
|
|
191
|
+
private valeur(
|
|
192
|
+
donnee: any,
|
|
193
|
+
schema: TSelecteur,
|
|
194
|
+
chemin: string[]
|
|
195
|
+
) {
|
|
196
|
+
|
|
197
|
+
// Si sélecteur wildcard
|
|
198
|
+
const wildcard = typeof schema === 'object' && schema['*'] === true
|
|
199
|
+
|
|
200
|
+
// Traitement des objets
|
|
201
|
+
if (donnee && typeof donnee === 'object') {
|
|
202
|
+
|
|
203
|
+
// Promise
|
|
204
|
+
if (typeof donnee.then === 'function')
|
|
205
|
+
throw new Error(chemin.join('.') + ": Les promises ne sont pas autorisées en retour api, sauf via un getter de modèle.");
|
|
206
|
+
|
|
207
|
+
// Sinon, wildcard obliatoire si on souhaite conserver l'objet entier
|
|
208
|
+
else if (wildcard === false) {
|
|
209
|
+
|
|
210
|
+
if (donnee instanceof Date)
|
|
211
|
+
return donnee.toISOString();
|
|
212
|
+
// Mauvaise idée: Les instances de modèle Sequelize possèdent une méthode toString()
|
|
213
|
+
/*else if (typeof donnee.toString === 'function')
|
|
214
|
+
return donnee.toString();*/
|
|
215
|
+
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return donnee;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/*private propriete<TModele extends Modele>(
|
|
224
|
+
nom: keyof TModele,
|
|
225
|
+
modele: TModele,
|
|
226
|
+
metasClasse: TModelMetas,
|
|
227
|
+
cheminA: string[]
|
|
228
|
+
): any | undefined {
|
|
229
|
+
|
|
230
|
+
const metasProp = metasClasse.attributes[nom];
|
|
231
|
+
|
|
232
|
+
if (metasProp === undefined)
|
|
233
|
+
console.warn(`ATTENTION: La propriété « ${nom} » a été demandée via le sélecteur, mais cette dernière n'a pas été référencée dans le modèle ${metasClasse.nom}.`);
|
|
234
|
+
|
|
235
|
+
// Exposé publiquement
|
|
236
|
+
// @ts-ignore: 'string' can't be used to index type '{ "Post": string[]; "Question": string[], ...
|
|
237
|
+
if (metasProp?.api === undefined) {
|
|
238
|
+
//debug && console.log(`Elimination de la donnée ${cheminA.join('.')} (propriété de classe ${metasClasse.nom}.${nom}) (non-exposé à l'API)`);
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let valeur: any;
|
|
243
|
+
// Si promise, on lui rattache un catch le plus tôt possible, avant qu'on ne tente d'acceder à sa valeur
|
|
244
|
+
// (un simple acces lançant directement la promise)
|
|
245
|
+
valeur = modele[nom];
|
|
246
|
+
|
|
247
|
+
// Permissions
|
|
248
|
+
if (!this.controleAcces(modele, metasProp.api.auth, `Elimination de la donnée ${cheminA.join('.')} (${metasClasse.nom}.${nom})`)) {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (valeur === undefined || valeur === null)
|
|
253
|
+
return undefined;
|
|
254
|
+
|
|
255
|
+
// Filtre spécifique à la propriété
|
|
256
|
+
if (metasProp.api.sortie)
|
|
257
|
+
valeur = metasProp.api.sortie(valeur);
|
|
258
|
+
|
|
259
|
+
if (valeur === undefined || valeur === null)
|
|
260
|
+
return undefined;
|
|
261
|
+
|
|
262
|
+
// Dernier traitement des valeurs
|
|
263
|
+
//valeur = filtresProps(valeur, metasProp);
|
|
264
|
+
return valeur;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private controleAcces(
|
|
268
|
+
donneesCompletes: Modele,
|
|
269
|
+
roleRequis: TControleAcces | undefined,
|
|
270
|
+
logElimination: string
|
|
271
|
+
): boolean {
|
|
272
|
+
|
|
273
|
+
if (roleRequis === undefined)
|
|
274
|
+
return true;
|
|
275
|
+
else if (!this.user) {
|
|
276
|
+
//debug && console.log(logElimination + ` (Non-connecté)`);
|
|
277
|
+
return false;
|
|
278
|
+
} else {
|
|
279
|
+
|
|
280
|
+
// L'admin peut tout voir depuis l'interface d'amdin
|
|
281
|
+
if (this.user && this.user.roles.includes('ADMIN'))
|
|
282
|
+
return true;
|
|
283
|
+
|
|
284
|
+
// Fonction personnalisée
|
|
285
|
+
if (typeof roleRequis === 'function') {
|
|
286
|
+
const retour = roleRequis(donneesCompletes, this.user);
|
|
287
|
+
//debug && console.log(logElimination + ` (Via fonction custom)`);
|
|
288
|
+
return retour;
|
|
289
|
+
} else {
|
|
290
|
+
|
|
291
|
+
// Force le format tableau
|
|
292
|
+
if (typeof roleRequis === 'string')
|
|
293
|
+
roleRequis = [roleRequis];
|
|
294
|
+
|
|
295
|
+
// Vérification si l'un des role requis correspond à l'utilisateur actuel
|
|
296
|
+
for (const role of roleRequis) {
|
|
297
|
+
|
|
298
|
+
// Role simple
|
|
299
|
+
if (this.user.roles.includes(role))
|
|
300
|
+
return true;
|
|
301
|
+
// Correspondance id utilisateur avec valeur d'une colonne (ex: auteur)
|
|
302
|
+
else {
|
|
303
|
+
const nomCol = role === 'id' || role.endsWith('_id')
|
|
304
|
+
? role
|
|
305
|
+
: role + '_id';
|
|
306
|
+
const valeurCol = donneesCompletes[nomCol];
|
|
307
|
+
|
|
308
|
+
// TODO: Vérif si nom colonne existante dans modèle
|
|
309
|
+
|
|
310
|
+
if (valeurCol === this.user.id)
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
//debug && console.log(logElimination + ` (User actuel: ${this.user.id} ${this.user.roles.join(', ')} ; Requis: ${roleRequis.join(', ')})`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
}*/
|
|
322
|
+
|
|
323
|
+
}
|