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.
- package/changelog.md +5 -0
- package/doc/TODO.md +71 -0
- package/package.json +5 -4
- package/src/client/{App.tsx → app/component.tsx} +15 -11
- package/src/client/app/index.ts +128 -0
- package/src/client/app/service.ts +34 -0
- package/src/client/app.tsconfig.json +0 -4
- package/src/client/assets/css/components.less +52 -0
- package/src/client/assets/css/core.less +7 -28
- package/src/client/assets/css/theme.less +1 -1
- package/src/client/assets/css/{borders.less → utils/borders.less} +0 -0
- package/src/client/assets/css/{layouts.less → utils/layouts.less} +0 -0
- package/src/client/assets/css/{medias.less → utils/medias.less} +14 -1
- package/src/client/assets/css/{sizing.less → utils/sizing.less} +0 -0
- package/src/client/assets/css/{spacing.less → utils/spacing.less} +0 -0
- package/src/client/components/Card/index.tsx +13 -7
- package/src/client/components/Dialog/Manager.tsx +41 -14
- package/src/client/components/Dialog/index.less +2 -4
- package/src/client/components/Form/index.tsx +1 -1
- package/src/client/components/Row/index.less +0 -2
- package/src/client/components/Table/index.tsx +3 -2
- package/src/client/components/button.tsx +2 -2
- package/src/client/components/containers/Popover/index.tsx +1 -1
- package/src/client/components/containers/champs.less +0 -2
- package/src/client/components/data/spintext/index.tsx +1 -1
- package/src/client/components/dropdown/index.tsx +1 -1
- package/src/client/components/index.ts +23 -0
- package/src/client/components/input/BaseV2/index.less +0 -2
- package/src/client/components/input/BaseV2/index.tsx +1 -1
- package/src/client/components/input/Date/index.less +0 -2
- package/src/client/components/input/Periode/index.less +0 -2
- package/src/client/components/input/Radio/index.less +0 -2
- package/src/client/components/input/UploadImage/index.less +0 -2
- package/src/client/components/input/UploadImage/index.tsx +1 -1
- package/src/client/hooks/index.ts +5 -0
- package/src/client/hooks/useState/index.tsx +2 -2
- package/src/client/hooks.ts +22 -0
- package/src/client/index.ts +5 -0
- package/src/client/pages/_layout/landing/index.tsx +0 -2
- package/src/client/pages/_messages/400.tsx +2 -2
- package/src/client/pages/_messages/401.tsx +2 -2
- package/src/client/pages/_messages/403.tsx +2 -2
- package/src/client/pages/_messages/404.tsx +2 -2
- package/src/client/pages/_messages/500.tsx +2 -2
- package/src/client/pages/bug.tsx +1 -1
- package/src/client/pages/useHeader.tsx +1 -1
- package/src/client/{context/captcha.ts → services/captcha/index.ts} +0 -0
- package/src/client/services/metrics/index.ts +37 -0
- package/src/client/{router → services/router/components}/Link.tsx +1 -1
- package/src/client/services/router/components/Page.tsx +59 -0
- package/src/client/{router/component.tsx → services/router/components/router.tsx} +52 -74
- package/src/client/services/router/index.tsx +453 -0
- package/src/client/services/router/request/api.ts +227 -0
- package/src/client/{router → services/router}/request/history.ts +0 -0
- package/src/client/services/router/request/index.ts +52 -0
- package/src/client/services/router/response/index.tsx +107 -0
- package/src/client/services/router/response/page.ts +90 -0
- package/src/client/{context/socket.ts → services/socket/index.ts} +2 -2
- package/src/client/utils/dom.ts +1 -1
- package/src/common/app/index.ts +9 -0
- package/src/common/data/chaines/index.ts +9 -6
- package/src/common/data/input/validate.ts +3 -166
- package/src/common/data/objets.ts +25 -0
- package/src/common/data/tableaux.ts +8 -0
- package/src/common/errors/index.ts +3 -1
- package/src/common/router/index.ts +67 -88
- package/src/common/router/layouts.ts +50 -0
- package/src/common/router/register.ts +62 -0
- package/src/common/router/request/api.ts +72 -0
- package/src/common/router/request/index.ts +31 -0
- package/src/common/router/{response.ts → response/index.ts} +9 -13
- package/src/common/router/response/page.ts +46 -54
- package/src/common/validation/index.ts +3 -0
- package/src/common/validation/schema.ts +185 -0
- package/src/common/validation/validator.ts +95 -0
- package/src/common/validation/validators.ts +313 -0
- package/src/server/app/config.ts +9 -27
- package/src/server/app/index.ts +81 -124
- package/src/server/app/service.ts +98 -0
- package/src/server/app.tsconfig.json +0 -8
- package/src/server/index.ts +5 -0
- package/src/server/patch.ts +0 -6
- package/src/server/{data/Cache.ts → services/cache/index.ts} +79 -47
- package/src/server/services/console/bugReporter.ts +26 -16
- package/src/server/services/console/index.ts +59 -51
- package/src/server/services/cron/index.ts +12 -26
- package/src/server/services/database/bucket.ts +40 -0
- package/src/server/services/database/connection.ts +213 -80
- package/src/server/services/database/datatypes.ts +63 -40
- package/src/server/services/database/debug.ts +20 -0
- package/src/server/services/database/index.ts +295 -272
- package/src/server/services/database/metas.ts +246 -135
- package/src/server/services/database/stats.ts +151 -126
- package/src/server/services/email/index.ts +30 -62
- package/src/server/services/email/transporter.ts +38 -0
- package/src/server/services/{router/request/services → metrics}/detect.ts +8 -10
- package/src/server/services/{router/request/services/tracking.ts → metrics/index.ts} +68 -45
- package/src/server/services/{http → router/http}/index.ts +28 -70
- package/src/server/services/{http → router/http}/multipart.ts +0 -0
- package/src/server/services/{http → router/http}/session.ts.old +0 -0
- package/src/server/services/router/index.ts +273 -202
- package/src/server/services/router/request/api.ts +76 -0
- package/src/server/services/router/request/index.ts +16 -97
- package/src/server/services/router/request/service.ts +21 -0
- package/src/server/services/router/response/index.ts +131 -65
- package/src/server/services/router/response/{filter → mask}/Filter.ts +0 -0
- package/src/server/services/router/response/{filter → mask}/index.ts +0 -2
- package/src/server/services/router/response/{filter → mask}/selecteurs.ts +0 -0
- package/src/server/services/router/response/page/document.tsx +194 -0
- package/src/server/services/router/response/page/index.tsx +157 -0
- package/src/server/{libs/pages → services/router/response/page}/schemaGenerator.ts +0 -0
- package/src/server/services/router/service.ts +48 -0
- package/src/server/services/schema/index.ts +47 -0
- package/src/server/services/schema/request.ts +55 -0
- package/src/server/services/schema/router.ts +33 -0
- package/src/server/services/socket/index.ts +38 -43
- package/src/server/services/socket/scope.ts +6 -4
- package/src/server/services/users/index.ts +203 -0
- package/src/server/services/{auth/base.ts → users/old.ts} +28 -112
- package/src/server/services/users/router/index.ts +72 -0
- package/src/server/services/users/router/request.ts +49 -0
- package/src/server/{data → services_old}/SocketClient.ts +0 -0
- package/src/server/{data/Token.olg.ts → services_old/Token.old.ts} +0 -0
- package/src/server/{data → services_old}/aes.ts +0 -0
- package/src/types/aliases.d.ts +43 -2
- package/templates/composant.tsx +1 -1
- package/templates/modal.tsx +1 -1
- package/templates/page.tsx +1 -1
- package/tsconfig.common.json +0 -4
- package/src/client/assets/css/components/components.less +0 -31
- package/src/client/context/api.ts +0 -92
- package/src/client/context/index.ts +0 -246
- package/src/client/index.tsx +0 -129
- package/src/client/router/index.ts +0 -286
- package/src/client/router/request/index.ts +0 -106
- package/src/client/router/response/index.ts +0 -38
- package/src/client/router/route.ts +0 -75
- package/src/common/data/input/validators/basic.ts +0 -299
- package/src/common/data/input/validators/build.ts +0 -63
- package/src/common/router/request.ts +0 -83
- package/src/server/data/ApiClient.ts +0 -119
- package/src/server/data/input.ts +0 -41
- package/src/server/libs/pages/document.static.tsx +0 -41
- package/src/server/libs/pages/document.tsx +0 -203
- package/src/server/libs/pages/render.tsx +0 -90
- package/src/server/routes/auth.ts +0 -151
- package/src/server/services/redis/index.ts +0 -71
- 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
|
+
}
|
package/src/server/app/config.ts
CHANGED
|
@@ -16,28 +16,20 @@ import yaml from 'yaml';
|
|
|
16
16
|
----------------------------------*/
|
|
17
17
|
|
|
18
18
|
declare global {
|
|
19
|
-
namespace
|
|
20
|
-
namespace Config {
|
|
19
|
+
namespace Config {
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
type EnvName = TEnvConfig["name"];
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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:
|
|
69
|
-
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
|
|
package/src/server/app/index.ts
CHANGED
|
@@ -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
|
-
|
|
18
|
-
type THook = () => Promise<void>;
|
|
19
|
+
export { default as Service, TPriority } from './service';
|
|
19
20
|
|
|
20
|
-
type
|
|
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
|
-
|
|
33
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
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
|
|
125
|
+
await this.startServices()
|
|
191
126
|
|
|
192
127
|
console.info(`[boot] Launching application ...`);
|
|
193
128
|
await this.runHook('ready');
|
|
194
129
|
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
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
|
+
}
|