5htp-core 0.1.2 → 0.2.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.
Files changed (148) 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 -11
  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/components.less +52 -0
  9. package/src/client/assets/css/core.less +7 -28
  10. package/src/client/assets/css/theme.less +1 -1
  11. package/src/client/assets/css/{borders.less → utils/borders.less} +0 -0
  12. package/src/client/assets/css/{layouts.less → utils/layouts.less} +0 -0
  13. package/src/client/assets/css/{medias.less → utils/medias.less} +14 -1
  14. package/src/client/assets/css/{sizing.less → utils/sizing.less} +0 -0
  15. package/src/client/assets/css/{spacing.less → utils/spacing.less} +0 -0
  16. package/src/client/components/Card/index.tsx +13 -7
  17. package/src/client/components/Dialog/Manager.tsx +41 -14
  18. package/src/client/components/Dialog/index.less +2 -4
  19. package/src/client/components/Form/index.tsx +1 -1
  20. package/src/client/components/Row/index.less +0 -2
  21. package/src/client/components/Table/index.tsx +3 -2
  22. package/src/client/components/button.tsx +2 -2
  23. package/src/client/components/containers/Popover/index.tsx +1 -1
  24. package/src/client/components/containers/champs.less +0 -2
  25. package/src/client/components/data/spintext/index.tsx +1 -1
  26. package/src/client/components/dropdown/index.tsx +1 -1
  27. package/src/client/components/index.ts +23 -0
  28. package/src/client/components/input/BaseV2/index.less +0 -2
  29. package/src/client/components/input/BaseV2/index.tsx +1 -1
  30. package/src/client/components/input/Date/index.less +0 -2
  31. package/src/client/components/input/Periode/index.less +0 -2
  32. package/src/client/components/input/Radio/index.less +0 -2
  33. package/src/client/components/input/UploadImage/index.less +0 -2
  34. package/src/client/components/input/UploadImage/index.tsx +1 -1
  35. package/src/client/hooks/index.ts +5 -0
  36. package/src/client/hooks/useState/index.tsx +2 -2
  37. package/src/client/hooks.ts +22 -0
  38. package/src/client/index.ts +5 -0
  39. package/src/client/pages/_layout/landing/index.tsx +0 -2
  40. package/src/client/pages/_messages/400.tsx +2 -2
  41. package/src/client/pages/_messages/401.tsx +2 -2
  42. package/src/client/pages/_messages/403.tsx +2 -2
  43. package/src/client/pages/_messages/404.tsx +2 -2
  44. package/src/client/pages/_messages/500.tsx +2 -2
  45. package/src/client/pages/bug.tsx +1 -1
  46. package/src/client/pages/useHeader.tsx +1 -1
  47. package/src/client/{context/captcha.ts → services/captcha/index.ts} +0 -0
  48. package/src/client/services/metrics/index.ts +37 -0
  49. package/src/client/{router → services/router/components}/Link.tsx +1 -1
  50. package/src/client/services/router/components/Page.tsx +59 -0
  51. package/src/client/{router/component.tsx → services/router/components/router.tsx} +52 -74
  52. package/src/client/services/router/index.tsx +453 -0
  53. package/src/client/services/router/request/api.ts +227 -0
  54. package/src/client/{router → services/router}/request/history.ts +0 -0
  55. package/src/client/services/router/request/index.ts +52 -0
  56. package/src/client/services/router/response/index.tsx +107 -0
  57. package/src/client/services/router/response/page.ts +90 -0
  58. package/src/client/{context/socket.ts → services/socket/index.ts} +2 -2
  59. package/src/client/utils/dom.ts +1 -1
  60. package/src/common/app/index.ts +9 -0
  61. package/src/common/data/chaines/index.ts +9 -6
  62. package/src/common/data/input/validate.ts +3 -166
  63. package/src/common/data/objets.ts +25 -0
  64. package/src/common/data/tableaux.ts +8 -0
  65. package/src/common/errors/index.ts +3 -1
  66. package/src/common/router/index.ts +67 -88
  67. package/src/common/router/layouts.ts +50 -0
  68. package/src/common/router/register.ts +62 -0
  69. package/src/common/router/request/api.ts +72 -0
  70. package/src/common/router/request/index.ts +31 -0
  71. package/src/common/router/{response.ts → response/index.ts} +9 -13
  72. package/src/common/router/response/page.ts +46 -54
  73. package/src/common/validation/index.ts +3 -0
  74. package/src/common/validation/schema.ts +185 -0
  75. package/src/common/validation/validator.ts +95 -0
  76. package/src/common/validation/validators.ts +313 -0
  77. package/src/server/app/config.ts +9 -27
  78. package/src/server/app/index.ts +81 -124
  79. package/src/server/app/service.ts +98 -0
  80. package/src/server/app.tsconfig.json +0 -8
  81. package/src/server/index.ts +5 -0
  82. package/src/server/patch.ts +0 -6
  83. package/src/server/{data/Cache.ts → services/cache/index.ts} +79 -47
  84. package/src/server/services/console/bugReporter.ts +26 -16
  85. package/src/server/services/console/index.ts +59 -51
  86. package/src/server/services/cron/index.ts +12 -26
  87. package/src/server/services/database/bucket.ts +40 -0
  88. package/src/server/services/database/connection.ts +213 -80
  89. package/src/server/services/database/datatypes.ts +63 -40
  90. package/src/server/services/database/debug.ts +20 -0
  91. package/src/server/services/database/index.ts +295 -272
  92. package/src/server/services/database/metas.ts +246 -135
  93. package/src/server/services/database/stats.ts +151 -126
  94. package/src/server/services/email/index.ts +30 -62
  95. package/src/server/services/email/transporter.ts +38 -0
  96. package/src/server/services/{router/request/services → metrics}/detect.ts +8 -10
  97. package/src/server/services/{router/request/services/tracking.ts → metrics/index.ts} +68 -45
  98. package/src/server/services/{http → router/http}/index.ts +28 -70
  99. package/src/server/services/{http → router/http}/multipart.ts +0 -0
  100. package/src/server/services/{http → router/http}/session.ts.old +0 -0
  101. package/src/server/services/router/index.ts +273 -202
  102. package/src/server/services/router/request/api.ts +76 -0
  103. package/src/server/services/router/request/index.ts +16 -97
  104. package/src/server/services/router/request/service.ts +21 -0
  105. package/src/server/services/router/response/index.ts +131 -65
  106. package/src/server/services/router/response/{filter → mask}/Filter.ts +0 -0
  107. package/src/server/services/router/response/{filter → mask}/index.ts +0 -2
  108. package/src/server/services/router/response/{filter → mask}/selecteurs.ts +0 -0
  109. package/src/server/services/router/response/page/document.tsx +194 -0
  110. package/src/server/services/router/response/page/index.tsx +157 -0
  111. package/src/server/{libs/pages → services/router/response/page}/schemaGenerator.ts +0 -0
  112. package/src/server/services/router/service.ts +48 -0
  113. package/src/server/services/schema/index.ts +47 -0
  114. package/src/server/services/schema/request.ts +55 -0
  115. package/src/server/services/schema/router.ts +33 -0
  116. package/src/server/services/socket/index.ts +38 -43
  117. package/src/server/services/socket/scope.ts +6 -4
  118. package/src/server/services/users/index.ts +203 -0
  119. package/src/server/services/{auth/base.ts → users/old.ts} +28 -112
  120. package/src/server/services/users/router/index.ts +72 -0
  121. package/src/server/services/users/router/request.ts +49 -0
  122. package/src/server/{data → services_old}/SocketClient.ts +0 -0
  123. package/src/server/{data/Token.olg.ts → services_old/Token.old.ts} +0 -0
  124. package/src/server/{data → services_old}/aes.ts +0 -0
  125. package/src/types/aliases.d.ts +43 -2
  126. package/templates/composant.tsx +1 -1
  127. package/templates/modal.tsx +1 -1
  128. package/templates/page.tsx +1 -1
  129. package/tsconfig.common.json +0 -4
  130. package/src/client/assets/css/components/components.less +0 -31
  131. package/src/client/context/api.ts +0 -92
  132. package/src/client/context/index.ts +0 -246
  133. package/src/client/index.tsx +0 -129
  134. package/src/client/router/index.ts +0 -286
  135. package/src/client/router/request/index.ts +0 -106
  136. package/src/client/router/response/index.ts +0 -38
  137. package/src/client/router/route.ts +0 -75
  138. package/src/common/data/input/validators/basic.ts +0 -299
  139. package/src/common/data/input/validators/build.ts +0 -63
  140. package/src/common/router/request.ts +0 -83
  141. package/src/server/data/ApiClient.ts +0 -119
  142. package/src/server/data/input.ts +0 -41
  143. package/src/server/libs/pages/document.static.tsx +0 -41
  144. package/src/server/libs/pages/document.tsx +0 -203
  145. package/src/server/libs/pages/render.tsx +0 -90
  146. package/src/server/routes/auth.ts +0 -151
  147. package/src/server/services/redis/index.ts +0 -71
  148. package/src/server/services/router/request/services/auth.ts +0 -177
@@ -0,0 +1,313 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+ import {
7
+ trim,
8
+ isISO8601, toDate,
9
+ isEmail, normalizeEmail,
10
+ isURL
11
+ } from 'validator';
12
+
13
+ // Core
14
+ import { InputError } from '@common/errors';
15
+ import File from '@common/data/file';
16
+ import NormalizedFile from '@common/data/file';
17
+
18
+ // Speciific
19
+ import Validator, { TValidator } from './validator'
20
+
21
+ // Components
22
+ import NumberInput from '@client/components/input/Number';
23
+ import Dropdown from '@client/components/dropdown.old';
24
+
25
+ /*----------------------------------
26
+ - TYPES
27
+ ----------------------------------*/
28
+
29
+ export type TFileValidator = TValidator<NormalizedFile> & {
30
+ type?: (keyof typeof raccourcisMime) | string[], // Raccourci, ou liste de mimetype
31
+ taille?: number
32
+ }
33
+
34
+ /*----------------------------------
35
+ - CONST
36
+ ----------------------------------*/
37
+
38
+ const raccourcisMime = {
39
+ image: ['image/jpeg', 'image/png']
40
+ }
41
+
42
+ /*----------------------------------
43
+ - CLASS
44
+ ----------------------------------*/
45
+ export default class SchemaValidator {
46
+
47
+ /*----------------------------------
48
+ - CONTENEURS
49
+ ----------------------------------*/
50
+ public object = ({ ...opts }: TValidator<object> & {} = {}) =>
51
+ new Validator<object>('object', (val, input, output) => {
52
+
53
+ // TODO: executer seulement coté serveur
54
+ /*if (typeof val === 'string' && val.startsWith('{'))
55
+ try {
56
+ val = JSON.parse(val);
57
+ } catch (error) {
58
+ console.error('Unable to convert the given string into an object.');
59
+ }*/
60
+
61
+ if (typeof val !== 'object' || val.constructor !== Object)
62
+ throw new InputError("This value must be an object.");
63
+
64
+ return val;
65
+ }, opts)
66
+
67
+ public array = (subtype?: Validator<any[]>, { choice, ...opts }: TValidator<any[]> & {
68
+ choice?: any[]
69
+ } = {}) => {
70
+
71
+ if (subtype !== undefined)
72
+ subtype.options.in = choice;
73
+
74
+ return new Validator<any[]>('array', (items, input, output, corriger) => {
75
+
76
+ //console.log('VALIDER ARRAY', items, input);
77
+
78
+ if (!Array.isArray(items))
79
+ throw new InputError("This value must be an array.");
80
+
81
+ // Verif items
82
+ if (subtype !== undefined) {
83
+ if (false/*subtype instanceof Schema*/) {
84
+
85
+ console.log('TODO: VALIDER VIA SOUS SCHEMA');
86
+
87
+ } else {
88
+
89
+ for (let i = 0; i < items.length; i++)
90
+ items[i] = subtype.validate( items[i], items, items, corriger );
91
+
92
+ }
93
+ }
94
+
95
+ return items;
96
+ }, {
97
+ ...opts,
98
+ in: choice,
99
+ //multiple: true, // Sélection multiple
100
+ //subtype
101
+ })
102
+ }
103
+
104
+ public choice = (values: any[], opts: TValidator<any> & {} = {}) =>
105
+ new Validator<any>('object', (val, input, output) => {
106
+
107
+ if (!values.includes(val))
108
+ throw new InputError("Invalid value. Must be: " + values.join(', '));
109
+
110
+ return val;
111
+
112
+ }, opts)
113
+
114
+ /*----------------------------------
115
+ - CHAINES
116
+ ----------------------------------*/
117
+ public string = ({ min, max, ...opts }: TValidator<string> & { min?: number, max?: number } = {}) =>
118
+ new Validator<string>('string', (val, input, output, corriger?: boolean) => {
119
+
120
+ if (val === '')
121
+ return undefined;
122
+ else if (typeof val === 'number')
123
+ return val.toString();
124
+ else if (typeof val !== 'string')
125
+ throw new InputError("This value must be a string.");
126
+
127
+ // Espaces blancs
128
+ val = trim(val);
129
+
130
+ // Taille min
131
+ if (min !== undefined && val.length < min)
132
+ throw new InputError(`Must be at least ` + min + ' characters');
133
+
134
+ // Taille max
135
+ if (max !== undefined && val.length > max)
136
+ if (corriger)
137
+ val = val.substring(0, max);
138
+ else
139
+ throw new InputError(`Must be up to ` + max + ' characters');
140
+
141
+ return val;
142
+
143
+ }, opts)
144
+
145
+ public url = (opts: TValidator<string> & {} = {}) =>
146
+ new Validator<string>('url', (inputVal, input, output, corriger?) => {
147
+
148
+ let val = this.string(opts).validate(inputVal, input, output, corriger);
149
+
150
+ if (!isURL(val, {
151
+ // https://www.npmjs.com/package/validator
152
+ }))
153
+ throw new InputError(`Please provide a valid URL.`);
154
+
155
+ return val;
156
+ }, opts)
157
+
158
+ public email = (opts: TValidator<string> & {} = {}) =>
159
+ new Validator<string>('email', (inputVal, input, output, corriger?: boolean) => {
160
+
161
+ let val = this.string(opts).validate(inputVal, input, output, corriger);
162
+
163
+ if (!isEmail(val))
164
+ throw new InputError("Please enter a valid email address.");
165
+
166
+ const retour = normalizeEmail(val);
167
+
168
+ return retour;
169
+ }, opts)
170
+
171
+ /*----------------------------------
172
+ - NOMBRES
173
+ ----------------------------------*/
174
+ // On ne spread pas min et max afin quils soient passés dans les props du composant
175
+ public number = (withDecimals: boolean) => ({ ...opts }: TValidator<number> & {
176
+ min?: number,
177
+ max?: number,
178
+ } = {}) => new Validator<number>('number', (val, input, output, corriger?: boolean) => {
179
+
180
+ // Vérifications suivantes inutiles si des values spécifiques ont été fournies
181
+ if (opts.in === undefined) {
182
+
183
+ // Tente conversion chaine en nombre
184
+ if (typeof val === 'string')
185
+ val = withDecimals ? parseFloat(val) : parseInt(val);
186
+
187
+ if (opts.min === undefined)
188
+ opts.min = 0;
189
+
190
+ // Type de donnée
191
+ if (Number.isNaN(val) || typeof val !== 'number') {
192
+ if (corriger)
193
+ val = opts.min;
194
+ else
195
+ throw new InputError("This value must be a number.");
196
+ }
197
+
198
+ // Minimum
199
+ if (val < opts.min)
200
+ if (corriger)
201
+ val = opts.min;
202
+ else
203
+ throw new InputError(`Must be at least ` + opts.min);
204
+
205
+ // Maximum
206
+ if (opts.max !== undefined && val > opts.max)
207
+ if (corriger)
208
+ val = opts.max;
209
+ else
210
+ throw new InputError(`Must be up to ` + opts.max);
211
+
212
+ }
213
+
214
+ return val;
215
+ }, {
216
+ // Force une valeur par défaut si requis
217
+ defaut: opts.opt ? undefined : (opts.min || 0),
218
+ rendu: NumberInput,
219
+ ...opts,
220
+ })
221
+
222
+ public int = this.number(false)
223
+
224
+ public float = this.number(true)
225
+
226
+ public bool = (opts: TValidator<boolean> & {} = {}) =>
227
+ new Validator<boolean>('bool', (val, input, output) => {
228
+
229
+ if (typeof val !== 'boolean' && !['true', 'false'].includes(val))
230
+ throw new InputError("This value must be a boolean.");
231
+
232
+ val = !!val;
233
+
234
+ return val;
235
+ }, {
236
+ defaut: false,
237
+ ...opts
238
+ })
239
+
240
+ /*----------------------------------
241
+ - AUTRES
242
+ ----------------------------------*/
243
+ public date = (opts: TValidator<Date> & {
244
+
245
+ } = {}) => new Validator<Date>('date', (val, input, output) => {
246
+
247
+ const chaine = typeof val == 'string';
248
+
249
+ // Chaine = format iso
250
+ if (chaine) {
251
+
252
+ if (!isISO8601(val))
253
+ throw new InputError("This value must be a date.");
254
+
255
+ val = toDate(val);
256
+
257
+ } else if (!(val instanceof Date))
258
+ throw new InputError("This value must be a date.");
259
+
260
+ return val;
261
+
262
+ }, {
263
+ //defaut: new Date,
264
+ ...opts,
265
+ })
266
+
267
+ /*----------------------------------
268
+ - FICHIER
269
+ ----------------------------------*/
270
+ protected validateFile = (
271
+ { type, taille, ...opts }: TFileValidator = {},
272
+ val: any,
273
+ input: TObjetDonnees,
274
+ output: TObjetDonnees
275
+ ): File | undefined => {
276
+
277
+ console.log('VALIDER FICHIER', type, val);
278
+
279
+ if (!(val instanceof NormalizedFile))
280
+ throw new InputError(`Must be a File (${typeof val} received)`);
281
+
282
+ // MIME
283
+ if (type !== undefined) {
284
+
285
+ let mimetypes: string[];
286
+
287
+ // Raccourcis
288
+ if (typeof type === 'string') {
289
+ if (type in raccourcisMime)
290
+ mimetypes = raccourcisMime[type as keyof typeof raccourcisMime]
291
+ else
292
+ throw new Error(`Aucune liste de mimetype référencée pour le type de fichier « ${type} »`);
293
+ } else
294
+ mimetypes = type;
295
+
296
+ // Vérification
297
+ const mimeFichier = val.type;
298
+ if (!mimetypes.includes(mimeFichier))
299
+ throw new InputError('Only the following formats are allowed: ' + mimetypes.join(', ') + '. The file you gave is ' + mimeFichier + '.');
300
+
301
+ }
302
+
303
+ // Taille
304
+ if (taille) {
305
+ const tailleFichier = val.size / 1024 / 1024; // Mo
306
+ if (tailleFichier > taille)
307
+ throw new InputError(`Le fichier ne doit pas faire plus de ${taille} Mo (taille reçue: ${tailleFichier} Mo)`);
308
+ }
309
+
310
+ return val;
311
+ }
312
+
313
+ }
@@ -16,28 +16,20 @@ import yaml from 'yaml';
16
16
  ----------------------------------*/
17
17
 
18
18
  declare global {
19
- namespace Core {
20
- namespace Config {
19
+ namespace Config {
21
20
 
22
- type EnvName = TEnvConfig["name"];
21
+ type EnvName = TEnvConfig["name"];
23
22
 
24
- type Env = TEnvConfig;
25
- type Identity = AppIdentityConfig;
26
- interface Services {}
27
- }
23
+ type Env = TEnvConfig;
24
+ type Identity = AppIdentityConfig;
25
+ interface Services {}
28
26
  }
29
27
  }
30
28
 
31
29
  export type TEnvName = TEnvConfig["name"];
32
30
  export type TEnvConfig = {
33
-
34
31
  name: 'local' | 'server',
35
32
  profile: 'dev' | 'prod',
36
- level: 'silly' | 'info' | 'warn' | 'error',
37
-
38
- localIP: string,
39
- domain: string,
40
- url: string,
41
33
  }
42
34
 
43
35
  type AppIdentityConfig = {
@@ -65,8 +57,8 @@ type AppIdentityConfig = {
65
57
  }
66
58
 
67
59
  export type AppConfig = {
68
- env: Core.Config.Env,
69
- identity: Core.Config.Identity,
60
+ env: Config.Env,
61
+ identity: Config.Identity,
70
62
  }
71
63
 
72
64
  /*----------------------------------
@@ -90,23 +82,13 @@ export default class ConfigParser {
90
82
  public env(): TEnvConfig {
91
83
  // We assume that when we run 5htp dev, we're in local
92
84
  // Otherwise, we're in production environment (docker)
93
- console.log("Using environment:", process.env.NODE_ENV);
85
+ console.log("[app] Using environment:", process.env.NODE_ENV);
94
86
  return process.env.NODE_ENV === 'development' ? {
95
87
  name: 'local',
96
- profile: 'dev',
97
- level: 'silly',
98
-
99
- localIP: '86.76.176.80',
100
- domain: 'localhost:3010',
101
- url: 'http://localhost:3010',
88
+ profile: 'dev'
102
89
  } : {
103
90
  name: 'server',
104
91
  profile: 'prod',
105
- level: 'silly',
106
-
107
- localIP: '86.76.176.80',
108
- domain: 'megacharger.io',
109
- url: 'https://megacharger.io',
110
92
  }
111
93
  }
112
94
 
@@ -9,48 +9,53 @@ import fs from 'fs-extra';
9
9
 
10
10
  // Core
11
11
  import ConfigParser, { TEnvConfig } from './config';
12
+ import { default as Service, AnyService } from './service';
13
+ import type { default as Router, Request as ServerRequest } from '@server/services/router';
12
14
 
13
15
  /*----------------------------------
14
16
  - TYPES
15
17
  ----------------------------------*/
16
18
 
17
- type THookName = 'ready' | 'cleanup' | 'error'
18
- type THook = () => Promise<void>;
19
+ export { default as Service, TPriority } from './service';
19
20
 
20
- type TServiceOptions = {
21
- instanciate: boolean
22
- }
21
+ type Config = {
23
22
 
24
- abstract class AsyncService {
25
- public abstract loading: Promise<void> | undefined;
26
- public abstract load: () => Promise<void>;
27
- }
28
- abstract class Service {
29
- public abstract load: () => void;
30
23
  }
31
24
 
32
- interface TServiceClass {
33
- new(): AsyncService | Service;
25
+ type Hooks = {
26
+ ready: {
27
+ args: [],
28
+ },
29
+ cleanup: {
30
+ args: [],
31
+ },
32
+ error: {
33
+ args: [error: Error, request?: ServerRequest<Router>],
34
+ }
34
35
  }
35
36
 
36
37
  declare global {
37
- namespace Core {
38
- interface Services { }
38
+
39
+ //interface Services { }
40
+
41
+ interface AppHooks {
42
+
39
43
  }
44
+
40
45
  interface User { }
41
46
  }
42
47
 
43
- const servicesObj = {}
44
-
45
48
  /*----------------------------------
46
49
  - FUNCTIONS
47
50
  ----------------------------------*/
48
- export class App {
51
+ export default abstract class Application extends Service<Config, Hooks, /* TODO: this ? */Application> {
49
52
 
50
53
  /*----------------------------------
51
54
  - PROPERTIES
52
55
  ----------------------------------*/
53
56
 
57
+ public side = 'server' as 'server';
58
+
54
59
  // Context
55
60
  public hmr: __WebpackModuleApi.Hot | undefined = module.hot;
56
61
 
@@ -65,143 +70,101 @@ export class App {
65
70
 
66
71
  // Status
67
72
  public launched: boolean = false;
68
- public loading: Promise<void>[] = [];
69
73
  public status = {
70
74
  services: false
71
75
  }
72
76
 
73
- public services: Core.Services = new Proxy( servicesObj, {
74
- get: (container, serviceId, receiver) => {
75
-
76
- if (!( serviceId in container ) && typeof serviceId === 'string')
77
- throw new Error(`Service not loaded: ${serviceId}`);
78
-
79
- return container[serviceId];
80
- }
81
- }) as Core.Services;
82
-
83
- public hooks: {[name in THookName]: THook[]} = {
84
- ready: [],
85
- cleanup: [],
86
- error: []
87
- }
77
+ private servicesList: AnyService[] = []
88
78
 
89
79
  /*----------------------------------
90
80
  - INIT
91
81
  ----------------------------------*/
92
82
 
83
+ public env: TEnvConfig;
84
+ public abstract identity: Config.Identity;
85
+
93
86
  public constructor() {
94
87
 
88
+ // @ts-ignore: can't pass this to super
89
+ super();
90
+
95
91
  // Gestion crash
96
92
  process.on('unhandledRejection', (error: any, promise: any) => {
97
93
 
98
94
  console.error("Unhandled promise rejection:", error);
99
-
100
- // Send email report
101
- if (this.isLoaded('console'))
102
- $.console.bugReport.server(error);
103
- else
104
- console.error(`Unable to send bug report: console service not loaded.`);
95
+ this.runHook('error', error);
105
96
 
106
97
  });
107
98
 
108
99
  // Load config files
109
100
  const configParser = new ConfigParser( this.path.root );
110
101
  this.env = configParser.env();
111
- this.identity = configParser.identity();
112
- }
113
-
114
- // Configs
115
- public config!: Core.Config.Services;
116
- public identity: Core.Config.Identity;
117
- public env: TEnvConfig;
118
- public configure( config: Core.Config.Services) {
119
- this.config = config;
120
- console.log("Configure services with", this.config);
121
- }
122
-
123
- // Register a service
124
- public register<TServiceName extends keyof Core.Services>(
125
- id: TServiceName,
126
- Service: TServiceClass,
127
- options: Partial<TServiceOptions> = {}
128
- ) {
129
-
130
- // Pas d'export default new Service pour chaque fichier de service,
131
- // dissuaded'importer ms service sn'importe où, ce qui créé des références circulaires
132
- console.log(`[services] Registering service ${id} ...`);
133
- const service = options.instanciate !== false ? new Service() : Service;
134
- this.services[id as string] = service;
135
-
136
- if ('load' in service) {
137
-
138
- console.log(`[services] Starting service ${id} ...`);
139
-
140
- // Lorsque service.load est async, une propriété loading doit etre présente
141
- // De façon à ce que les autres services puissent savoir quand ce service est prêt
142
- if ('loading' in service) {
143
-
144
- console.log(`[services] Waiting service ${id} to be fully loaded ...`);
145
- service.loading = service.load().then(() => {
146
- console.info(`[service] Service ${id} successfully started.`);
147
- }).catch(e => {
148
- // Bug report via email
149
- console.error(`[service] Error while starting the ${id} service:`, e);
150
- e.message = `Start ${id} service: ` + e.message;
151
- $.console.bugReport.server(e);
152
- });;
153
-
154
- this.loading.push(service.loading);
155
-
156
- } else
157
- service.load();
158
- }
159
102
  }
160
103
 
161
- // Test if a service was registered
162
- public isLoaded( id: keyof Core.Services ) {
163
- return id in this.services;
164
- }
165
-
166
- public on( name: THookName, callback: THook ) {
167
- this.hooks[ name ].push( callback );
168
- return this;
169
- }
104
+ /*----------------------------------
105
+ - REGISTER
106
+ ----------------------------------*/
170
107
 
171
- public runHook( hookName: THookName ) {
172
- console.info(`[hook] Run all ${hookName} hook (${this.hooks.ready.length}).`);
173
- return Promise.all(
174
- this.hooks.ready.map(
175
- cb => cb().catch(e => {
176
- console.error(`[hook] Error while executing hook ${hookName}:`, e);
177
- })
178
- )
179
- ).then(() => {
180
- console.info(`[hook] Hooks ${hookName} executed with success.`);
181
- })
108
+ // Require a service at file scope
109
+ // Only use in files where a service is strictly required
110
+ public use<ServiceType extends Service<{}, {}, this>>( serviceName: string ): ServiceType | undefined {
111
+ return this[ serviceName ];
182
112
  }
183
113
 
184
114
  /*----------------------------------
185
115
  - LAUNCH
186
116
  ----------------------------------*/
187
- public async launch() {
117
+
118
+ public async register() {
119
+
120
+ }
121
+
122
+ public async start() {
188
123
 
189
124
  console.info(`[boot] Waiting for all services to be ready ...`);
190
- await Promise.all( this.loading );
125
+ await this.startServices()
191
126
 
192
127
  console.info(`[boot] Launching application ...`);
193
128
  await this.runHook('ready');
194
129
 
195
- // NOTE: Useless ?
196
- /*if (this.hmr)
197
- this.activateHMR();*/
130
+ console.info(`[boot] Run application-specific boot instructions ...`);
131
+ await this.boot();
198
132
 
199
133
  console.info(`[boot] Application is ready.`);
200
-
201
134
  this.launched = true;
202
135
 
203
136
  }
204
137
 
138
+ public registerService( service: AnyService ) {
139
+ console.log(`[app] Register service`, service.constructor?.name);
140
+ this.servicesList.push(service);
141
+ }
142
+
143
+ public async startServices() {
144
+
145
+ console.log(`[app] Sorting ${this.servicesList.length} services by priority`);
146
+ this.servicesList.sort((s1, s2) => s2.priority - s1.priority);
147
+
148
+ console.log(`[app] Starting ${this.servicesList.length} services.`);
149
+ for (const service of this.servicesList) {
150
+ const serviceClassName = service.constructor?.name;
151
+ console.log(`[app] Start service`, serviceClassName);
152
+
153
+ if (service.register)
154
+ service.register();
155
+
156
+ if (service.start) {
157
+ service.started = service.start();
158
+ await service.started;
159
+ }
160
+ }
161
+
162
+ console.log(`[app] All ${this.servicesList.length} services were started.`);
163
+ }
164
+
165
+ public abstract boot(): Promise<void>;
166
+
167
+ // TODO: make it work
205
168
  private activateHMR() {
206
169
 
207
170
  if (!module.hot) return;
@@ -216,17 +179,16 @@ export class App {
216
179
  console.info(`Cleaning application ...`);
217
180
 
218
181
  // Services hooks
219
- for (const id in this.services) {
182
+ /*for (const id in this.services) {
220
183
  const service = this.services[id]
221
184
  if (service.cleanup) {
222
185
  console.info(`Cleaning ${id} service ...`);
223
186
  service.cleanup();
224
187
  }
225
- }
188
+ }*/
226
189
 
227
190
  // Application specific hooks
228
- for (const callback of this.hooks.cleanup)
229
- callback();
191
+ this.runHook('cleanup');
230
192
 
231
193
  /*
232
194
  console.log("[nettoyage] Arrêt serveur socket ...");
@@ -239,9 +201,4 @@ export class App {
239
201
  });
240
202
  }
241
203
 
242
- }
243
-
244
- const app = new App;
245
- export const services = app.services;
246
- export const $ = app.services;
247
- export default app;
204
+ }