5htp-core 0.1.2 → 0.2.0

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.
Files changed (127) hide show
  1. package/changelog.md +5 -0
  2. package/doc/TODO.md +71 -0
  3. package/package.json +5 -4
  4. package/src/client/{App.tsx → app/component.tsx} +15 -8
  5. package/src/client/app/index.ts +128 -0
  6. package/src/client/app/service.ts +34 -0
  7. package/src/client/app.tsconfig.json +0 -4
  8. package/src/client/assets/css/medias.less +14 -0
  9. package/src/client/components/Card/index.tsx +2 -2
  10. package/src/client/components/Dialog/Manager.tsx +39 -12
  11. package/src/client/components/Form/index.tsx +1 -1
  12. package/src/client/components/button.tsx +2 -2
  13. package/src/client/components/containers/Popover/index.tsx +1 -1
  14. package/src/client/components/data/spintext/index.tsx +1 -1
  15. package/src/client/components/dropdown/index.tsx +1 -1
  16. package/src/client/components/index.ts +8 -0
  17. package/src/client/components/input/BaseV2/index.tsx +1 -1
  18. package/src/client/components/input/UploadImage/index.tsx +1 -1
  19. package/src/client/hooks/index.ts +5 -0
  20. package/src/client/hooks/useState/index.tsx +2 -2
  21. package/src/client/hooks.ts +22 -0
  22. package/src/client/index.ts +5 -0
  23. package/src/client/pages/_layout/landing/index.tsx +0 -2
  24. package/src/client/pages/_messages/400.tsx +2 -2
  25. package/src/client/pages/_messages/401.tsx +2 -2
  26. package/src/client/pages/_messages/403.tsx +2 -2
  27. package/src/client/pages/_messages/404.tsx +2 -2
  28. package/src/client/pages/_messages/500.tsx +2 -2
  29. package/src/client/pages/bug.tsx +1 -1
  30. package/src/client/pages/useHeader.tsx +1 -1
  31. package/src/client/{context/captcha.ts → services/captcha/index.ts} +0 -0
  32. package/src/client/services/metrics/index.ts +37 -0
  33. package/src/client/{router → services/router/components}/Link.tsx +1 -1
  34. package/src/client/services/router/components/Page.tsx +59 -0
  35. package/src/client/{router/component.tsx → services/router/components/router.tsx} +43 -74
  36. package/src/client/services/router/index.tsx +448 -0
  37. package/src/client/services/router/request/api.ts +229 -0
  38. package/src/client/{router → services/router}/request/history.ts +0 -0
  39. package/src/client/services/router/request/index.ts +52 -0
  40. package/src/client/services/router/response/index.tsx +107 -0
  41. package/src/client/services/router/response/page.ts +95 -0
  42. package/src/client/{context/socket.ts → services/socket/index.ts} +2 -2
  43. package/src/client/utils/dom.ts +1 -1
  44. package/src/common/app/index.ts +9 -0
  45. package/src/common/data/chaines/index.ts +9 -6
  46. package/src/common/data/input/validate.ts +3 -166
  47. package/src/common/data/objets.ts +25 -0
  48. package/src/common/data/tableaux.ts +8 -0
  49. package/src/common/errors/index.ts +3 -1
  50. package/src/common/router/index.ts +67 -88
  51. package/src/common/router/layouts.ts +50 -0
  52. package/src/common/router/register.ts +62 -0
  53. package/src/common/router/request/api.ts +72 -0
  54. package/src/common/router/request/index.ts +31 -0
  55. package/src/common/router/{response.ts → response/index.ts} +9 -13
  56. package/src/common/router/response/page.ts +40 -56
  57. package/src/common/validation/index.ts +3 -0
  58. package/src/common/validation/schema.ts +184 -0
  59. package/src/common/validation/validator.ts +88 -0
  60. package/src/common/validation/validators.ts +313 -0
  61. package/src/server/app/config.ts +9 -27
  62. package/src/server/app/index.ts +81 -124
  63. package/src/server/app/service.ts +98 -0
  64. package/src/server/app.tsconfig.json +0 -8
  65. package/src/server/error/index.ts +13 -0
  66. package/src/server/index.ts +5 -0
  67. package/src/server/patch.ts +0 -6
  68. package/src/server/{data/Cache.ts → services/cache/index.ts} +79 -47
  69. package/src/server/services/console/bugReporter.ts +26 -16
  70. package/src/server/services/console/index.ts +59 -51
  71. package/src/server/services/cron/index.ts +12 -26
  72. package/src/server/services/database/bucket.ts +40 -0
  73. package/src/server/services/database/connection.ts +206 -75
  74. package/src/server/services/database/datatypes.ts +63 -40
  75. package/src/server/services/database/index.ts +295 -272
  76. package/src/server/services/database/metas.ts +246 -135
  77. package/src/server/services/database/stats.ts +151 -126
  78. package/src/server/services/email/index.ts +28 -52
  79. package/src/server/services/{router/request/services → metrics}/detect.ts +8 -10
  80. package/src/server/services/{router/request/services/tracking.ts → metrics/index.ts} +68 -45
  81. package/src/server/services/{http → router/http}/index.ts +28 -70
  82. package/src/server/services/{http → router/http}/multipart.ts +0 -0
  83. package/src/server/services/{http → router/http}/session.ts.old +0 -0
  84. package/src/server/services/router/index.ts +273 -203
  85. package/src/server/services/router/request/api.ts +73 -0
  86. package/src/server/services/router/request/index.ts +16 -97
  87. package/src/server/services/router/request/service.ts +21 -0
  88. package/src/server/services/router/response/index.ts +125 -64
  89. package/src/server/services/router/response/{filter → mask}/Filter.ts +0 -0
  90. package/src/server/services/router/response/{filter → mask}/index.ts +0 -2
  91. package/src/server/services/router/response/{filter → mask}/selecteurs.ts +0 -0
  92. package/src/server/services/router/response/page/document.tsx +194 -0
  93. package/src/server/services/router/response/page/index.tsx +157 -0
  94. package/src/server/{libs/pages → services/router/response/page}/schemaGenerator.ts +0 -0
  95. package/src/server/services/router/service.ts +48 -0
  96. package/src/server/services/schema/index.ts +47 -0
  97. package/src/server/services/schema/request.ts +55 -0
  98. package/src/server/services/schema/router.ts +33 -0
  99. package/src/server/services/socket/index.ts +38 -43
  100. package/src/server/services/socket/scope.ts +6 -4
  101. package/src/server/services/users/index.ts +203 -0
  102. package/src/server/services/{auth/base.ts → users/old.ts} +28 -112
  103. package/src/server/services/users/router/index.ts +72 -0
  104. package/src/server/services/users/router/request.ts +49 -0
  105. package/src/types/aliases.d.ts +43 -2
  106. package/templates/composant.tsx +1 -1
  107. package/templates/modal.tsx +1 -1
  108. package/templates/page.tsx +1 -1
  109. package/tsconfig.common.json +0 -4
  110. package/src/client/context/api.ts +0 -92
  111. package/src/client/context/index.ts +0 -246
  112. package/src/client/index.tsx +0 -129
  113. package/src/client/router/index.ts +0 -286
  114. package/src/client/router/request/index.ts +0 -106
  115. package/src/client/router/response/index.ts +0 -38
  116. package/src/client/router/route.ts +0 -75
  117. package/src/common/data/input/validators/basic.ts +0 -299
  118. package/src/common/data/input/validators/build.ts +0 -63
  119. package/src/common/router/request.ts +0 -83
  120. package/src/server/data/ApiClient.ts +0 -119
  121. package/src/server/data/input.ts +0 -41
  122. package/src/server/libs/pages/document.static.tsx +0 -41
  123. package/src/server/libs/pages/document.tsx +0 -203
  124. package/src/server/libs/pages/render.tsx +0 -90
  125. package/src/server/routes/auth.ts +0 -151
  126. package/src/server/services/redis/index.ts +0 -71
  127. package/src/server/services/router/request/services/auth.ts +0 -177
@@ -0,0 +1,448 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+ import React from 'react';
7
+ import ReactDOM from 'react-dom';
8
+
9
+ // Core
10
+ import type {
11
+ default as ServerRouter,
12
+ Request as ServerRequest,
13
+ Response as ServerResponse
14
+ } from '@server/services/router';
15
+ import type { TBasicSSrData } from '@server/services/router/response';
16
+
17
+ import { Erreur } from '@common/errors';
18
+ import BaseRouter, {
19
+ defaultOptions, TRoute, TErrorRoute, TClientOrServerContext, TRouteModule
20
+ } from '@common/router'
21
+ import { getRegisterPageArgs, buildRegex } from '@common/router/register';
22
+ import { TFetcherList } from '@common/router/request/api';
23
+ import type { TFrontRenderer, TDataProvider } from '@common/router/response/page';
24
+
25
+ import App from '@client/app/component';
26
+ import type ClientApplication from '@client/app';
27
+ import Service from '@client/app/service';
28
+
29
+ // Specific
30
+ import ClientRequest from './request';
31
+ import { location, history } from './request/history';
32
+ import ClientResponse from './response';
33
+ import ClientPage from './response/page';
34
+
35
+ // Routes (import __register)
36
+ import * as coreRoutes from '@client/pages/**/*.tsx';
37
+ import * as appRoutes from '@/client/pages/**/*.tsx';
38
+
39
+ /*----------------------------------
40
+ - CONFIG
41
+ ----------------------------------*/
42
+
43
+ const debug = true;
44
+ const LogPrefix = '[router]'
45
+
46
+ /*----------------------------------
47
+ - TYPES
48
+ ----------------------------------*/
49
+
50
+ // Client router can handle Client requests AND Server requests (for pages only)
51
+ export type { default as ClientResponse, TRouterContext } from "./response";
52
+ export { Link } from './components/Link';
53
+
54
+ export type Router = ClientRouter | ServerRouter;
55
+
56
+ export type Request = ClientRequest<ClientRouter> | ServerRequest<ServerRouter>;
57
+
58
+ export type Response = ClientResponse<ClientRouter> | ServerResponse<ServerRouter>;
59
+
60
+ /*----------------------------------
61
+ - TYPES: ROUTES LOADING
62
+ ----------------------------------*/
63
+
64
+ export type TRegisterPageArgs<TProvidedData extends TFetcherList = {}, TRouter extends Router = Router> = ([
65
+ path: string,
66
+ controller: TDataProvider<TProvidedData> | null,
67
+ renderer: TFrontRenderer<TProvidedData>
68
+ ] | [
69
+ path: string,
70
+ options: Partial<TRoute["options"]>,
71
+ controller: TDataProvider<TProvidedData> | null,
72
+ renderer: TFrontRenderer<TProvidedData>
73
+ ])
74
+
75
+ // Route definition passed by the server
76
+ export type TSsrUnresolvedRoute = {
77
+ chunk: string,
78
+ } & ({
79
+ // Normal route
80
+ regex: string,
81
+ keys: TRoute["keys"]
82
+ } | {
83
+ // Error
84
+ code: number
85
+ })
86
+
87
+ // Route definition without having loaded the controller
88
+ type TUnresolvedRoute = TUnresolvedErrorRoute | TUnresolvedNormalRoute;
89
+
90
+ export type TUnresolvedErrorRoute = {
91
+ index: number,
92
+ chunk: string,
93
+ code: number,
94
+ load: TRouteLoader<TErrorRoute>,
95
+ }
96
+
97
+ export type TUnresolvedNormalRoute = {
98
+ index: number,
99
+ chunk: string,
100
+ code: number,
101
+ load: TRouteLoader<TErrorRoute>,
102
+ }
103
+
104
+ type TRouteLoader<Route extends TRoute | TErrorRoute = TRoute | TErrorRoute> = () => Promise<TRouteModule<Route>>;
105
+
106
+ export type TFetchedRoute = Pick<TRoute, 'path' | 'options' | 'controller' | 'method'>
107
+
108
+ export type TRoutesLoaders = {
109
+ [chunkId: string]: () => Promise</* Preloaded via require() */TFetchedRoute | /* Loader via import() */TRouteLoader/* | undefined*/>
110
+ }
111
+
112
+ /*----------------------------------
113
+ - SERVICE TYPES
114
+ ----------------------------------*/
115
+
116
+ export type THookCallback<TRouter extends ClientRouter> = (request: ClientRequest<TRouter>) => void;
117
+
118
+ type THookName = 'location.change' | 'page.changed'
119
+
120
+ type Config = {
121
+ preload: string[], // List of globs
122
+ }
123
+
124
+ /*----------------------------------
125
+ - ROUTER
126
+ ----------------------------------*/
127
+ export default class ClientRouter<
128
+ TApplication extends ClientApplication = ClientApplication
129
+ > extends Service<Config, ClientApplication> implements BaseRouter {
130
+
131
+ public ssrData = window["ssr"] as (TBasicSSrData | undefined);
132
+ public ssrRoutes = window["routes"] as TSsrUnresolvedRoute[];
133
+
134
+ public constructor(app: TApplication, config: Config) {
135
+
136
+ super(app, config);
137
+ }
138
+
139
+ public async start() {
140
+
141
+ const currentRoute = await this.registerRoutes();
142
+
143
+ this.initialRender(currentRoute);
144
+ }
145
+
146
+ public go( url: string ) {
147
+ history?.replace(url);
148
+ }
149
+
150
+ /*----------------------------------
151
+ - REGISTRATION
152
+ ----------------------------------*/
153
+
154
+ public routes: (TRoute | TUnresolvedNormalRoute)[] = [];
155
+ public errors: { [code: number]: TErrorRoute | TUnresolvedErrorRoute } = {};
156
+
157
+ public async registerRoutes() {
158
+
159
+ const loaders: TRoutesLoaders = { ...coreRoutes, ...appRoutes }
160
+ let currentRoute: TUnresolvedRoute | undefined;
161
+ debug && console.log(LogPrefix, `Indexing routes and finding the current route from ssr data:`, this.ssrData);
162
+
163
+ // Associe la liste des routes (obtenue via ssr) à leur loader
164
+ for (let routeIndex = 0; routeIndex < this.ssrRoutes.length; routeIndex++) {
165
+
166
+ const ssrRoute = this.ssrRoutes[routeIndex];
167
+
168
+ if (loaders[ssrRoute.chunk] === undefined) {
169
+ console.error("Chunk id not found for ssr route:", ssrRoute, "Searched in:", loaders);
170
+ throw new Error(`Loader not found for chunk id ${ssrRoute.chunk}`);
171
+ }
172
+
173
+ // TODO: Fix types
174
+ const loader = loaders[ssrRoute.chunk];
175
+
176
+ // Register the route
177
+ let route: TUnresolvedRoute;
178
+ if ('code' in ssrRoute)
179
+ route = this.errors[ssrRoute.code] = {
180
+ code: ssrRoute.code,
181
+ chunk: ssrRoute.chunk,
182
+ load: loader,
183
+ }
184
+ else
185
+ route = this.routes[routeIndex] = {
186
+ index: routeIndex,
187
+ chunk: ssrRoute.chunk,
188
+ regex: new RegExp(ssrRoute.regex),
189
+ keys: ssrRoute.keys,
190
+ load: loader,
191
+ }
192
+
193
+ debug && console.log(LogPrefix, `${route.chunk}`, route);
194
+
195
+ // Detect if it's the current route
196
+ if (currentRoute === undefined) {
197
+
198
+ const isCurrentRoute = (
199
+ this.ssrData !== undefined
200
+ &&
201
+ route.chunk === this.ssrData.page.chunkId
202
+ );
203
+
204
+ if (isCurrentRoute) {
205
+ currentRoute = route;
206
+ continue;
207
+ }
208
+ }
209
+ }
210
+
211
+ return currentRoute;
212
+ }
213
+
214
+ public page(...args: TRegisterPageArgs): TRoute {
215
+
216
+ const { path, options, controller, renderer } = getRegisterPageArgs(...args);
217
+
218
+ // S'il s'agit d'une page, son id doit avoir été injecté via le plugin babel
219
+ const id = options["id"];
220
+ if (id === undefined)
221
+ throw new Error(`ID had not been injected into page options via the routes babel plugin for route ${path}.`);
222
+
223
+ const { regex, keys } = buildRegex(path);
224
+
225
+ const route: TRoute = {
226
+ method: 'GET',
227
+ path,
228
+ regex,
229
+ keys,
230
+ options: {
231
+ ...defaultOptions,
232
+ ...options
233
+ },
234
+ controller: (context: TClientOrServerContext) => new ClientPage(controller, renderer, context)
235
+ };
236
+
237
+ this.routes.push(route);
238
+
239
+ return route;
240
+ }
241
+
242
+ public error(code: number, options: TRoute["options"], renderer: TFrontRenderer<{}, { message: string }>) {
243
+
244
+ const route: TErrorRoute = {
245
+ code,
246
+ controller: (context: TClientOrServerContext) => new ClientPage(null, renderer, context),
247
+ options
248
+ };
249
+
250
+ this.errors[code] = route;
251
+
252
+ return route;
253
+ }
254
+
255
+
256
+ /*----------------------------------
257
+ - RESOLUTION
258
+ ----------------------------------*/
259
+ public async resolve(request: ClientRequest<this>): Promise<ClientPage | undefined | null> {
260
+
261
+ debug && console.log(LogPrefix, 'Resolving request', request.path, Object.keys(request.data));
262
+ this.runHook('location.change', request);
263
+
264
+ for (let iRoute = 0; iRoute < this.routes.length; iRoute++) {
265
+
266
+ let route = this.routes[iRoute];
267
+ if (!('regex' in route))
268
+ continue;
269
+
270
+ const match = route.regex.exec(request.path);
271
+ if (!match)
272
+ continue;
273
+
274
+ // URL data
275
+ for (let iKey = 0; iKey < route.keys.length; iKey++) {
276
+ const nomParam = route.keys[iKey];
277
+ if (typeof nomParam === 'string') // number = sans nom
278
+ request.data[nomParam] = match[iKey + 1]
279
+ }
280
+
281
+ // Create response
282
+ debug && console.log(LogPrefix, 'Resolved request', request.path, '| Route:', route);
283
+ const page = await this.createResponse(route, request);
284
+
285
+ return page;
286
+
287
+ };
288
+
289
+ return undefined;
290
+ }
291
+
292
+ private async load(route: TUnresolvedNormalRoute): Promise<TRoute>;
293
+ private async load(route: TUnresolvedErrorRoute): Promise<TErrorRoute>;
294
+ private async load(route: TUnresolvedNormalRoute | TUnresolvedErrorRoute): Promise<TRoute | TErrorRoute> {
295
+
296
+ //throw new Error(`Failed to load route: ${route.chunk}`);
297
+
298
+ let fetched: TFetchedRoute;
299
+ if (typeof route.load === 'function') {
300
+
301
+ debug && console.log(`Fetching route ${route.chunk} ...`, route);
302
+ try {
303
+
304
+ const loaded = await route.load();
305
+
306
+ fetched = loaded.__register(this.app);
307
+
308
+ } catch (e) {
309
+ console.error(`Failed to fetch the route ${route.chunk}`, e);
310
+ this.app.handleError(new Error("Failed to load content. Please make sure you're connected to Internet."));
311
+ throw e;
312
+ }
313
+
314
+ } else {
315
+
316
+ debug && console.log(`Route already fetched: ${route.chunk}`, route.load);
317
+ fetched = route.load;
318
+
319
+ }
320
+
321
+ debug && console.log(`Route fetched: ${route.chunk}`, fetched);
322
+ return {
323
+ ...fetched,
324
+ regex: route.regex,
325
+ keys: route.keys
326
+ }
327
+ }
328
+
329
+ public set(data: TObjetDonnees) {
330
+ throw new Error(`router.set was not attached to the router component.`);
331
+ }
332
+
333
+ private async initialRender(route: TUnresolvedRoute | undefined) {
334
+
335
+ debug && console.log(LogPrefix, `Initial render route`, route);
336
+
337
+ if (!location)
338
+ throw new Error(`Unable to retrieve current location.`);
339
+
340
+ if (!route)
341
+ throw new Error(`Unable to resolve route.`);
342
+
343
+ const request = new ClientRequest(location, this);
344
+
345
+ // Restituate SSR response
346
+ if (this.ssrData) {
347
+
348
+ console.log("SSR Response restitution ...");
349
+
350
+ request.user = this.ssrData.user || null;
351
+
352
+ request.data = this.ssrData.request.data;
353
+ }
354
+
355
+ const response = await this.createResponse(route, request)
356
+
357
+ ReactDOM.hydrate(<App context={response.context} />, document.body, () => {
358
+
359
+ console.log(`Render complete`);
360
+
361
+ });
362
+ }
363
+
364
+ private async createResponse(
365
+ route: TUnresolvedRoute | TRoute,
366
+ request: ClientRequest<this>,
367
+ additionnalData: {} = {}
368
+ ): Promise<ClientPage> {
369
+
370
+ // Load if not done before
371
+ if ('load' in route)
372
+ route = this.routes[route.index] = await this.load(route);
373
+
374
+ // Run controller
375
+ // TODO: tell that ruController on the client side always returns pages
376
+ try {
377
+
378
+ const response = new ClientResponse<this, ClientPage>(request, route);
379
+ return await response.runController(additionnalData);
380
+
381
+ } catch (error) {
382
+
383
+ return await this.createErrorResponse(error, request);
384
+ }
385
+ }
386
+
387
+ private async createErrorResponse(
388
+ e: any,
389
+ request: ClientRequest<this>,
390
+ additionnalData: {} = {}
391
+ ): Promise<ClientPage> {
392
+
393
+ const code = 'http' in e ? e.http : 500;
394
+ console.log(`Loading error page ` + code);
395
+ let route = this.errors[code];
396
+
397
+ // Nor page configurated for this error
398
+ if (route === undefined) {
399
+ console.error(`Error page for http error code ${code} not found.`, this.errors, this.routes);
400
+ this.app.handleError(e, 404);
401
+ throw new Error(`Error page for http error code ${code} not found.`);
402
+ }
403
+
404
+ // Load if not done before
405
+ if ('load' in route)
406
+ route = this.errors[code] = await this.load(route);
407
+
408
+ const response = new ClientResponse<this, ClientPage>(request, route);
409
+ return await response.runController(additionnalData);
410
+ }
411
+
412
+ /*----------------------------------
413
+ - HOOKS
414
+ ----------------------------------*/
415
+ private hooks: {
416
+ [hookname in THookName]?: (THookCallback<this> | null)[]
417
+ } = {}
418
+
419
+ public on(hookName: THookName, callback: THookCallback<this>) {
420
+
421
+ debug && console.info(LogPrefix, `Register hook ${hookName}`);
422
+
423
+ let cbIndex: number;
424
+ let callbacks = this.hooks[hookName];
425
+ if (!callbacks) {
426
+ cbIndex = 0;
427
+ callbacks = this.hooks[hookName] = [callback]
428
+ } else {
429
+ cbIndex = callbacks.length;
430
+ callbacks.push(callback);
431
+ }
432
+
433
+ // Listener remover
434
+ return () => {
435
+ debug && console.info(LogPrefix, `De-register hook ${hookName} (index ${cbIndex})`);
436
+ delete (callbacks as THookCallback<this>[])[cbIndex];
437
+ }
438
+
439
+ }
440
+
441
+ public runHook(hookName: THookName, request: ClientRequest<this>) {
442
+ const callbacks = this.hooks[hookName];
443
+ if (callbacks)
444
+ for (const callback of callbacks)
445
+ // callback can be null since we use delete to unregister
446
+ callback && callback(request);
447
+ }
448
+ }
@@ -0,0 +1,229 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+ import axios, { AxiosResponse, AxiosError, AxiosRequestConfig } from 'axios';
7
+
8
+ // Core
9
+ import type { TApiResponseData } from '@server/services/router';
10
+ import ApiClientService, {
11
+ TApiFetchOptions, TFetcherList, TFetcherArgs, TFetcher
12
+ } from '@common/router/request/api';
13
+ import { instancierViaCode, NetworkError } from '@common/errors';
14
+ import type ClientApplication from '@client/app';
15
+
16
+ // Specific
17
+ import type { default as Router, Request } from '..';
18
+
19
+ /*----------------------------------
20
+ - TYPES
21
+ ----------------------------------*/
22
+
23
+ const debug = true;
24
+
25
+ export type Config = {
26
+
27
+ }
28
+
29
+ /*----------------------------------
30
+ - FUNCTION
31
+ ----------------------------------*/
32
+ export default class ApiClient implements ApiClientService {
33
+
34
+ // APO Client needs to know the current request so we can monitor which api request is made from which page
35
+ public constructor(
36
+ public app: ClientApplication,
37
+ public request: Request<Router>
38
+ ) {
39
+
40
+ }
41
+
42
+ /*----------------------------------
43
+ - HIGH LEVEL
44
+ ----------------------------------*/
45
+ public get = <TData extends unknown = unknown>(path: string, data?: TObjetDonnees, opts?: TApiFetchOptions) =>
46
+ this.createFetcher<TData>('GET', path, data, opts);
47
+
48
+ public post = <TData extends unknown = unknown>(path: string, data?: TObjetDonnees, opts?: TApiFetchOptions) =>
49
+ this.createFetcher<TData>('POST', path, data, opts);
50
+
51
+ public put = <TData extends unknown = unknown>(path: string, data?: TObjetDonnees, opts?: TApiFetchOptions) =>
52
+ this.createFetcher<TData>('PUT', path, data, opts);
53
+
54
+ public delete = <TData extends unknown = unknown>(path: string, data?: TObjetDonnees, opts?: TApiFetchOptions) =>
55
+ this.createFetcher<TData>('DELETE', path, data, opts);
56
+
57
+
58
+ public set( newData: TObjetDonnees ) {
59
+
60
+ console.log("[api] Update page data", newData);
61
+ if (this.app.page)
62
+ this.app.page.setAllData(curData => ({ ...curData, ...newData }));
63
+
64
+ }
65
+
66
+ public reload( ids?: string | string[], params?: TObjetDonnees ) {
67
+
68
+ if (this.app.page === undefined)
69
+ throw new Error("context.page is missing");
70
+
71
+ if (ids === undefined)
72
+ ids = Object.keys(this.app.page.fetchers);
73
+ else if (typeof ids === 'string')
74
+ ids = [ids];
75
+
76
+ console.log("[api] Reload data", ids, params, this.app.page.fetchers);
77
+
78
+ for (const id of ids) {
79
+
80
+ const fetcher = this.app.page.fetchers[id];
81
+ if (fetcher === undefined)
82
+ return console.error(`Unable to reload ${id}: Request not found in fetchers list.`);
83
+
84
+ if (params !== undefined)
85
+ fetcher.data = { ...(fetcher.data || {}), ...params };
86
+
87
+ console.log("[api][reload]", id, fetcher.method, fetcher.path, fetcher.data);
88
+ const indicator = this.toast.loading("Loading ...");
89
+
90
+ this.fetchAsync(fetcher.method, fetcher.path, fetcher.data).then((data) => {
91
+
92
+ this.set({ [id]: data });
93
+
94
+ }).finally(() => {
95
+
96
+ indicator.close(true);
97
+
98
+ })
99
+ }
100
+ }
101
+
102
+ /*----------------------------------
103
+ - LOW LEVEL
104
+ ----------------------------------*/
105
+ public createFetcher<TData extends unknown = unknown>(...args: TFetcherArgs): TFetcher<TData> {
106
+ const [method, path, data, options] = args;
107
+ return {
108
+ method, path, data, options,
109
+ // For async calls: api.post(...).then((data) => ...)
110
+ then: (callback: (data: any) => void) => this.fetchAsync<TData>(...args).then(callback),
111
+ catch: (callback: (data: any) => void) => this.fetchAsync(...args).catch(callback),
112
+ finally: (callback: () => void) => this.fetchAsync(...args).finally(callback),
113
+ run: () => this.fetchAsync(...args)
114
+ };
115
+ }
116
+
117
+ public async fetchAsync<TData extends unknown = unknown>(...[
118
+ method, path, data, options
119
+ ]: TFetcherArgs): Promise<TData> {
120
+
121
+ /*if (options?.captcha !== undefined)
122
+ await this.gui.captcha.check(options?.captcha);*/
123
+
124
+ return await this.fetch<TData>(method, path, data, options).catch((e) => {
125
+ this.app.handleError(e);
126
+ throw e;
127
+ })
128
+ }
129
+
130
+ public async fetchSync(fetchers: TFetcherList): Promise<TObjetDonnees> {
131
+
132
+ const ids = Object.keys(fetchers);
133
+ if (ids.length === 1) {
134
+ const id = ids[0];
135
+ const fetcher = fetchers[id];
136
+ return {
137
+ [id]: await this.fetch( fetcher.method, fetcher.path, fetcher.data, undefined )
138
+ };
139
+ }
140
+
141
+ const fetchersArgs: {[id: string]: TFetcherArgs} = {};
142
+ for (const id in fetchers)
143
+ fetchersArgs[id] = [
144
+ fetchers[id].method,
145
+ fetchers[id].path,
146
+ fetchers[id].data,
147
+ ]
148
+
149
+ return await this.fetch("POST", "/api", { fetchers: fetchersArgs }, undefined).then((res) => {
150
+
151
+ const data: TObjetDonnees = {};
152
+ for (const id in res)
153
+ data[id] = res[id];
154
+
155
+ return data;
156
+
157
+ });
158
+
159
+ }
160
+
161
+ public configure = (...[method, path, data, options]: TFetcherArgs): AxiosRequestConfig => {
162
+
163
+ const { onProgress, captcha } = options || {};
164
+
165
+ debug && console.log(`[api] Sending request`, method, path, data);
166
+
167
+ const config: AxiosRequestConfig = {
168
+
169
+ url: path,
170
+ method: method,
171
+ headers: {
172
+ 'Content-Type': "application/json",
173
+ 'Accept': "application/json",
174
+ },
175
+
176
+ validateStatus: function (status: number) {
177
+ return status === 200;
178
+ },
179
+
180
+ onUploadProgress: onProgress === undefined ? undefined : (e) => {
181
+ const percentCompleted = Math.round((e.loaded * 100) / e.total);
182
+ onProgress(percentCompleted);
183
+ }
184
+
185
+ };
186
+
187
+ if (data) {
188
+ if (method === "GET")
189
+ config.params = data;
190
+ else {
191
+ config.data = data;
192
+ if (data instanceof FormData)
193
+ config.headers["Content-Type"] = 'multipart/form-data';
194
+ }
195
+ }
196
+
197
+ return config;
198
+ }
199
+
200
+ public fetch<TData = unknown>(...args: TFetcherArgs): Promise<TData> {
201
+
202
+ const config = this.configure(...args);
203
+
204
+ return axios.request(config)
205
+ .then((res: AxiosResponse<TApiResponseData>) => {
206
+
207
+ debug && console.log(`[api] Success:`, res);
208
+ return res.data as TData;
209
+
210
+ })
211
+ .catch((e: AxiosError) => {
212
+
213
+ if (e.response !== undefined) {
214
+
215
+ console.warn(`[api] Failure:`, e);
216
+ throw instancierViaCode(
217
+ e.response.status || 500,
218
+ e.response.data
219
+ );
220
+
221
+ // Erreur réseau: l'utilisateur n'ets probablement plus connecté à internet
222
+ } else {
223
+ const error = new NetworkError(e.message);
224
+ this.app.handleError(error);
225
+ throw error;
226
+ }
227
+ });
228
+ }
229
+ }