5htp-core 0.6.0 → 0.6.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 (48) hide show
  1. package/client/app/component.tsx +1 -0
  2. package/client/assets/css/colors.less +46 -25
  3. package/client/assets/css/components/button.less +14 -5
  4. package/client/assets/css/components/card.less +5 -10
  5. package/client/assets/css/components/mantine.less +6 -5
  6. package/client/assets/css/components/table.less +1 -1
  7. package/client/assets/css/text/icons.less +1 -1
  8. package/client/assets/css/text/text.less +4 -0
  9. package/client/assets/css/utils/borders.less +1 -1
  10. package/client/assets/css/utils/layouts.less +8 -5
  11. package/client/components/Button.tsx +20 -17
  12. package/client/components/Checkbox.tsx +6 -1
  13. package/client/components/ConnectedInput.tsx +34 -0
  14. package/client/components/DropDown.tsx +21 -4
  15. package/client/components/Input.tsx +2 -2
  16. package/client/components/Rte/Editor.tsx +23 -9
  17. package/client/components/Rte/ToolbarPlugin/ElementFormat.tsx +1 -1
  18. package/client/components/Rte/ToolbarPlugin/index.tsx +272 -183
  19. package/client/components/Rte/currentEditor.ts +31 -2
  20. package/client/components/Rte/index.tsx +3 -0
  21. package/client/components/Rte/plugins/FloatingTextFormatToolbarPlugin/index.tsx +4 -1
  22. package/client/components/Select.tsx +29 -16
  23. package/client/components/Table/index.tsx +27 -11
  24. package/client/components/containers/Popover/index.tsx +21 -4
  25. package/client/components/index.ts +4 -2
  26. package/client/services/router/index.tsx +7 -5
  27. package/common/errors/index.tsx +27 -3
  28. package/common/router/index.ts +4 -1
  29. package/common/utils/rte.ts +183 -0
  30. package/package.json +3 -2
  31. package/server/app/container/console/index.ts +62 -42
  32. package/server/app/container/index.ts +4 -0
  33. package/server/app/service/index.ts +4 -2
  34. package/server/services/auth/index.ts +28 -14
  35. package/server/services/auth/router/index.ts +1 -1
  36. package/server/services/auth/router/request.ts +4 -4
  37. package/server/services/email/index.ts +8 -51
  38. package/server/services/prisma/Facet.ts +118 -0
  39. package/server/services/prisma/index.ts +24 -0
  40. package/server/services/router/http/index.ts +0 -2
  41. package/server/services/router/index.ts +220 -86
  42. package/server/services/router/response/index.ts +0 -15
  43. package/server/utils/rte.ts +21 -132
  44. package/types/global/utils.d.ts +4 -22
  45. package/types/icons.d.ts +1 -1
  46. package/server/services/email/service.json +0 -6
  47. package/server/services/email/templates.ts +0 -49
  48. package/server/services/email/transporter.ts +0 -31
@@ -17,7 +17,7 @@ import Ansi2Html from 'ansi-to-html';
17
17
  // Core libs
18
18
  import type ApplicationContainer from '..';
19
19
  import context from '@server/context';
20
- import type { ServerBug, Anomaly, CoreError } from '@common/errors';
20
+ import type { ServerBug, TCatchedError, CoreError } from '@common/errors';
21
21
  import type ServerRequest from '@server/services/router/request';
22
22
  import { SqlError } from '@server/services/database/debug';
23
23
 
@@ -49,6 +49,11 @@ export type Services = {
49
49
  export type ChannelInfos = {
50
50
  channelType: 'cron' | 'master' | 'request' | 'socket',
51
51
  channelId?: string,
52
+
53
+ method?: string,
54
+ path?: string,
55
+
56
+ user?: string
52
57
  }
53
58
 
54
59
  export type TGuestLogs = {
@@ -99,8 +104,6 @@ export type TJsonLog = {
99
104
 
100
105
  const LogPrefix = '[console]'
101
106
 
102
- const errorMailInterval = (1 * 60 * 60 * 1000); // 1 hour
103
-
104
107
  const logLevels = {
105
108
  'log': 0,
106
109
  'info': 3,
@@ -144,13 +147,6 @@ export default class Console {
144
147
  public logger!: Logger<ILogObj>;
145
148
  // Buffers
146
149
  public logs: TJsonLog[] = [];
147
- // Bug ID => Timestamp latest send
148
- private sentBugs: {[bugId: string]: number} = {};
149
-
150
- // Old (still useful???)
151
- /*public clients: TGuestLogs[] = [];
152
- public requests: TRequestLogs[] = [];
153
- public sqlQueries: TDbQueryLog[] = [];*/
154
150
 
155
151
  /*----------------------------------
156
152
  - LIFECYCLE
@@ -283,10 +279,9 @@ export default class Console {
283
279
  this.logs = this.logs.slice(bufferOverflow);
284
280
  }
285
281
 
286
- public async createBugReport( error: Error | CoreError | Anomaly, request?: ServerRequest ) {
282
+ // We don't prevent duplicates because we want to receive all variants of the same error
283
+ public async createBugReport( error: TCatchedError, request?: ServerRequest ) {
287
284
 
288
- // Print error
289
- this.logger.error(LogPrefix, `Sending bug report for the following error:`, error);
290
285
  /*const youchRes = new Youch(error, {});
291
286
  const jsonResponse = await youchRes.toJSON()
292
287
  console.log( forTerminal(jsonResponse, {
@@ -313,28 +308,6 @@ export default class Console {
313
308
  if (application === undefined)
314
309
  return console.error(LogPrefix, "Can't send bug report because the application is not instanciated");
315
310
 
316
- // Print the error so it's accessible via logs
317
- if (error instanceof SqlError) {
318
- let printedQuery: string;
319
- try {
320
- printedQuery = this.printSql( error.query );
321
- } catch (error) {
322
- printedQuery = 'Failed to print query:' + (error || 'unknown error');
323
- }
324
- console.error(`Error caused by this query:`, printedQuery);
325
- }
326
-
327
- if (('dataForDebugging' in error) && error.dataForDebugging !== undefined)
328
- console.error(LogPrefix, `More data about the error:`, error.dataForDebugging);
329
-
330
- // Prevent spamming the mailbox if infinite loop
331
- const bugId = ['server', request?.user?.name, undefined, error.message].filter(e => !!e).join('::');
332
- const lastSending = this.sentBugs[bugId];
333
- this.sentBugs[bugId] = Date.now();
334
- const shouldSendReport = lastSending === undefined || lastSending < Date.now() - errorMailInterval;
335
- if (!shouldSendReport)
336
- return;
337
-
338
311
  // Get context
339
312
  const now = new Date();
340
313
  const hash = uuid();
@@ -342,7 +315,43 @@ export default class Console {
342
315
 
343
316
  // On envoi l'email avant l'insertion dans bla bdd
344
317
  // Car cette denrière a plus de chances de provoquer une erreur
345
- const logs = this.logs.filter(e => e.channel.channelId === channelId).slice(-100);
318
+ //const logs = this.logs.filter(e => e.channel.channelId === channelId).slice(-100);
319
+ const stacktraces: string[] = [];
320
+ const context: object[] = [];
321
+
322
+ let currentError: TCatchedError | undefined = error;
323
+ let title: string | undefined;
324
+ while (currentError !== undefined) {
325
+
326
+ if (title === undefined)
327
+ title = currentError.message;
328
+
329
+ // Stacktrace
330
+ this.logger.error(LogPrefix, `Sending bug report for the following error:`, currentError);
331
+ stacktraces.push(currentError.stack || currentError.message);
332
+
333
+ // Context
334
+ if (('dataForDebugging' in currentError) && currentError.dataForDebugging !== undefined) {
335
+ console.error(LogPrefix, `More data about the error:`, currentError.dataForDebugging);
336
+ context.push(currentError.dataForDebugging || {});
337
+ }
338
+
339
+ // Print the error so it's accessible via logs
340
+ if (currentError instanceof SqlError) {
341
+ let printedQuery: string;
342
+ try {
343
+ printedQuery = this.printSql( currentError.query );
344
+ } catch (error) {
345
+ printedQuery = 'Failed to print query:' + (error || 'unknown error');
346
+ }
347
+ console.error(`Error caused by this query:`, printedQuery);
348
+ }
349
+
350
+ // Go deeper
351
+ currentError = 'originalError' in currentError
352
+ ? currentError.originalError
353
+ : undefined
354
+ }
346
355
 
347
356
  const bugReport: ServerBug = {
348
357
 
@@ -371,9 +380,9 @@ export default class Console {
371
380
  } : {}),
372
381
 
373
382
  // Error
374
- error,
375
- stacktrace: error.stack || error.message,
376
- logs
383
+ title,
384
+ stacktraces,
385
+ context
377
386
  }
378
387
 
379
388
  await application.runHook('bug', bugReport);
@@ -391,12 +400,23 @@ export default class Console {
391
400
  ----------------------------------*/
392
401
 
393
402
  public bugToHtml( report: ServerBug ) {
403
+
394
404
  return `
395
405
  <b>Channel</b>: ${report.channelType} (${report.channelId})<br />
396
406
  <b>User</b>: ${report.user ? (report.user.name + ' (' + report.user.email + ')') : 'Unknown'}<br />
397
407
  <b>IP</b>: ${report.ip}<br />
398
- <b>Error</b>: ${report.error.message}<br />
399
- ${this.printHtml(report.stacktrace)}<br />
408
+
409
+ ${report.stacktraces.map((stacktrace, index) => `
410
+ <hr />
411
+ <b>Error ${index + 1}</b>:
412
+ ${this.printHtml(stacktrace)}<br />
413
+ `).join('')}
414
+
415
+ ${report.context.map((context, index) => `
416
+ <hr />
417
+ <b>Context ${index + 1}</b>: ${this.jsonToHTML(context)}<br />
418
+ `).join('')}
419
+
400
420
  ${report.request ? `
401
421
  <hr />
402
422
  <b>Request</b>: ${report.request.method} ${report.request.url}<br />
@@ -432,7 +452,7 @@ Logs: ${this.config.enable ? `<br/>` + this.logsToHTML(report.logs) : 'Logs coll
432
452
  });
433
453
 
434
454
  // Print args as ANSI
435
- const logArgsAndErrorsMarkup = this.logger.runtime.prettyFormatLogObj(log.args, this.logger.settings);
455
+ const logArgsAndErrorsMarkup = this.logger["runtime"].prettyFormatLogObj(log.args, this.logger.settings);
436
456
  const logErrors = logArgsAndErrorsMarkup.errors;
437
457
  const logArgs = logArgsAndErrorsMarkup.args;
438
458
  const logErrorsStr = (logErrors.length > 0 && logArgs.length > 0 ? "\n" : "") + logErrors.join("\n");
@@ -2,6 +2,10 @@
2
2
  - DEPENDANCES
3
3
  ----------------------------------*/
4
4
 
5
+ // Set timezone
6
+ process.env.TZ = 'UTC';
7
+ import 'source-map-support/register';
8
+
5
9
  // Npm
6
10
  import path from 'path';
7
11
 
@@ -42,7 +42,7 @@ export type StartedServicesIndex = {
42
42
 
43
43
  export type TServiceArgs<TService extends AnyService> = [
44
44
  parent: AnyService | 'self',
45
- getConfig: (instance: TService) => {},
45
+ getConfig: null | undefined | ((instance: TService) => {}),
46
46
  app: TService['app'] | 'self'
47
47
  ]
48
48
 
@@ -94,7 +94,8 @@ export function Route(options: Omit<TControllerDefinition, 'controller'> = {}) {
94
94
  export default abstract class Service<
95
95
  TConfig extends {},
96
96
  THooks extends THooksList,
97
- TApplication extends Application
97
+ TApplication extends Application,
98
+ TParent extends AnyService | Application = Application
98
99
  > {
99
100
 
100
101
  public started?: Promise<void>;
@@ -104,6 +105,7 @@ export default abstract class Service<
104
105
  public metas!: TServiceMetas;
105
106
  public bindings: string[] = []
106
107
 
108
+ public parent: TParent;
107
109
  public app: TApplication;
108
110
  public config: TConfig = {} as TConfig;
109
111
 
@@ -41,7 +41,7 @@ export type TConfig = {
41
41
  jwt: {
42
42
  // 2048 bits
43
43
  key: string,
44
- expiration: string,
44
+ expiration: number,
45
45
  },
46
46
  }
47
47
 
@@ -173,7 +173,9 @@ export default abstract class AuthService<
173
173
 
174
174
  this.config.debug && console.info(LogPrefix, `Generated JWT token for session:` + token);
175
175
 
176
- request.res.cookie('authorization', token);
176
+ request.res.cookie('authorization', token, {
177
+ maxAge: this.config.jwt.expiration,
178
+ });
177
179
 
178
180
  return token;
179
181
  }
@@ -187,13 +189,30 @@ export default abstract class AuthService<
187
189
  request.res.clearCookie('authorization');
188
190
  }
189
191
 
190
- public check( request: TRequest, entity: string, role: TUserRole): TUser;
191
- public check( request: TRequest, entity: string, role: false): null;
192
- public check( request: TRequest, entity: string, role: TUserRole | false = 'USER'): TUser | null {
192
+ public check(
193
+ request: TRequest,
194
+ role: TUserRole,
195
+ motivation?: string,
196
+ dataForDebug?: { [key: string]: any }
197
+ ): TUser;
198
+
199
+ public check(
200
+ request: TRequest,
201
+ role: false,
202
+ motivation?: string,
203
+ dataForDebug?: { [key: string]: any }
204
+ ): null;
205
+
206
+ public check(
207
+ request: TRequest,
208
+ role: TUserRole | false = 'USER',
209
+ motivation?: string,
210
+ dataForDebug?: { [key: string]: any }
211
+ ): TUser | null {
193
212
 
194
213
  const user = request.user;
195
214
 
196
- this.config.debug && console.warn(LogPrefix, `Check auth, role = ${role}. Current user =`, user?.name);
215
+ this.config.debug && console.warn(LogPrefix, `Check auth, role = ${role}. Current user =`, user?.name, motivation);
197
216
 
198
217
  if (user === undefined) {
199
218
 
@@ -208,23 +227,18 @@ export default abstract class AuthService<
208
227
  } else if (user === null) {
209
228
 
210
229
  this.config.debug && console.warn(LogPrefix, "Refusé pour anonyme (" + request.ip + ")");
211
- throw new AuthRequired('Please login to continue');
212
-
213
- } else if (user.type !== entity) {
214
-
215
- this.config.debug && console.warn(LogPrefix, `User type mismatch: ${user.type} (user) vs ${entity} (expected) (${request.ip})`);
216
- throw new AuthRequired("Your account type doesn't have access to the requested content.");
230
+ throw new AuthRequired('Please login to continue', motivation, dataForDebug);
217
231
 
218
232
  // Insufficient permissions
219
233
  } else if (!user.roles.includes(role)) {
220
234
 
221
- console.warn(LogPrefix, "Refusé: " + role + " pour " + user.name + " (" + (user.roles || 'role inconnu') + ")");
235
+ this.config.debug && console.warn(LogPrefix, "Refusé: " + role + " pour " + user.name + " (" + (user.roles || 'role inconnu') + ")");
222
236
 
223
237
  throw new Forbidden("You do not have sufficient permissions to access this resource.");
224
238
 
225
239
  } else {
226
240
 
227
- console.warn(LogPrefix, "Autorisé " + role + " pour " + user.name + " (" + user.roles + ")");
241
+ this.config.debug && console.warn(LogPrefix, "Autorisé " + role + " pour " + user.name + " (" + user.roles + ")");
228
242
 
229
243
  }
230
244
 
@@ -66,7 +66,7 @@ export default class AuthenticationRouterService<
66
66
  if (route.options.auth !== undefined) {
67
67
 
68
68
  // Basic auth check
69
- this.users.check(request, 'User', route.options.auth);
69
+ this.users.check(request, route.options.auth);
70
70
 
71
71
  // Redirect to logged page
72
72
  if (route.options.auth === false && request.user && route.options.redirectLogged)
@@ -45,9 +45,9 @@ export default class UsersRequestService<
45
45
  }
46
46
 
47
47
  // TODO: return user type according to entity
48
- public check( entity: string, role: TUserRole, motivation?: string): TUser;
49
- public check( entity: string, role: false, motivation?: string): null;
50
- public check( entity: string, role: TUserRole | boolean = 'USER', motivation?: string): TUser | null {
51
- return this.users.check( this.request, entity, role, motivation );
48
+ public check(role: TUserRole, motivation?: string, dataForDebug?: {}): TUser;
49
+ public check(role: false, motivation?: string, dataForDebug?: {}): null;
50
+ public check(role: TUserRole | boolean = 'USER', motivation?: string, dataForDebug?: {}): TUser | null {
51
+ return this.users.check( this.request, role, motivation, dataForDebug );
52
52
  }
53
53
  }
@@ -2,10 +2,6 @@
2
2
  - DEPENDANCES
3
3
  ----------------------------------*/
4
4
 
5
- /* NOTE: On évite d'utiliser les alias ici,
6
- Afin que l'envoi des rapports de bug fonctionne même en cas d'erreur avec les alias
7
- */
8
-
9
5
  // Core
10
6
  import type { Application } from '@server/app';
11
7
  import Service from '@server/app/service';
@@ -13,7 +9,6 @@ import markdown from '@common/data/markdown';
13
9
 
14
10
  // Speciic
15
11
  import { jsonToHtml } from './utils';
16
- import type { Transporter } from './transporter';
17
12
 
18
13
  /*----------------------------------
19
14
  - SERVICE CONFIG
@@ -25,16 +20,12 @@ export type Config = {
25
20
  debug: boolean,
26
21
  simulateWhenLocal: boolean,
27
22
  default: {
28
- transporter: string,
29
23
  from: TPerson
30
24
  },
31
25
  bugReport: {
32
26
  from: TPerson,
33
27
  to: TPerson
34
28
  },
35
- transporters: {
36
- [transporterId: string]: Transporter
37
- }
38
29
  }
39
30
 
40
31
  export type Hooks = {
@@ -49,9 +40,7 @@ export type Services = {
49
40
  - TYPES: EMAILS
50
41
  ----------------------------------*/
51
42
 
52
- export { Transporter } from './transporter';
53
-
54
- export type TEmail = THtmlEmail | TMarkdownEmail// | TTemplateEmail;
43
+ export type TEmail = THtmlEmail | TMarkdownEmail;
55
44
 
56
45
  type TPerson = {
57
46
  name?: string,
@@ -74,11 +63,6 @@ export type TMarkdownEmail = TBaseEmail & {
74
63
  markdown: string,
75
64
  }
76
65
 
77
- /*export type TTemplateEmail = TBaseEmail & {
78
- template: keyof typeof templates,
79
- data?: TObjetDonnees
80
- }*/
81
-
82
66
  export type TCompleteEmail = With<THtmlEmail, {
83
67
  to: TPerson[],
84
68
  from: TPerson,
@@ -109,14 +93,15 @@ type TOptions = {
109
93
  /*----------------------------------
110
94
  - FONCTIONS
111
95
  ----------------------------------*/
112
- export default class Email extends Service<Config, Hooks, Application> {
113
-
114
- private transporters = this.config.transporters;
96
+ export default abstract class Email<TConfig extends Config>
97
+ extends Service<TConfig, Hooks, Application> {
115
98
 
116
99
  /*----------------------------------
117
100
  - ACTIONS
118
101
  ----------------------------------*/
119
102
 
103
+ protected abstract sendNow( emails: TCompleteEmail[] ): Promise<void>;
104
+
120
105
  public async send( to: string, subject: string, markdown: string, options?: TOptions );
121
106
  public async send( emails: TEmail | TEmail[], options?: TOptions ): Promise<void>;
122
107
  public async send( ...args: TEmailSendArgs ): Promise<void> {
@@ -162,30 +147,7 @@ export default class Email extends Service<Config, Hooks, Application> {
162
147
  ? email.to
163
148
  : [email.to];
164
149
 
165
- // Via template
166
- // TODO: Restore templates feature
167
- /*if ('template' in email) {
168
-
169
- const template = templates[email.template];
170
-
171
- if (template === undefined)
172
- throw new Error(`Impossible de charger la template email ${email.template} depuis le cache (NotFound).`);
173
-
174
- const txt = template(email.data || {})
175
-
176
- const delimTitre = txt.indexOf('\n\n');
177
-
178
- return {
179
- ...email,
180
- // Vire le "> " au début
181
- subject: txt.substring(2, delimTitre),
182
- html: htmlWarning + txt.substring(delimTitre + 2),
183
- from,
184
- to,
185
- cc
186
- }
187
-
188
- } else */if ('markdown' in email) {
150
+ if ('markdown' in email) {
189
151
 
190
152
  return {
191
153
  ...email,
@@ -209,11 +171,7 @@ export default class Email extends Service<Config, Hooks, Application> {
209
171
 
210
172
  });
211
173
 
212
- const transporterName = options.transporter || this.config.default.transporter;
213
- if (transporterName === undefined)
214
- throw new Error(`Please define at least one mail transporter.`);
215
-
216
- console.info(LogPrefix, `Sending ${emailsToSend.length} emails via transporter "${transporterName}"`, emailsToSend[0].subject);
174
+ console.info(LogPrefix, `Sending ${emailsToSend.length} emails via transporter`, emailsToSend[0].subject);
217
175
 
218
176
  // Pas d'envoi d'email quand local
219
177
  if (this.app.env.name === 'local' && this.config.simulateWhenLocal === true) {
@@ -224,8 +182,7 @@ export default class Email extends Service<Config, Hooks, Application> {
224
182
  return;
225
183
  }
226
184
 
227
- const transporter = this.transporters[ transporterName ];
228
- await transporter.send(emailsToSend);
185
+ await this.sendNow(emailsToSend);
229
186
 
230
187
  }
231
188
  }
@@ -0,0 +1,118 @@
1
+ import type { Prisma, PrismaClient } from '@models/types';
2
+ import * as runtime from '@/var/prisma/runtime/library.js';
3
+
4
+ export type TWithStats = {
5
+ $table: string,
6
+ $key: string
7
+ } & {
8
+ [key: string]: string // key => SQL
9
+ }
10
+
11
+ export type TSubset = (...a: any[]) => Prisma.ProspectContactLeadFindFirstArgs & {
12
+ withStats?: TWithStats
13
+ }
14
+
15
+ export default class Facet<
16
+ D extends {
17
+ findMany(args?: any): Promise<any>
18
+ findFirst(args?: any): Promise<any>
19
+ },
20
+ S extends TSubset,
21
+ R
22
+ > {
23
+ constructor(
24
+
25
+ private readonly prisma: PrismaClient,
26
+
27
+ private readonly delegate: D,
28
+ private readonly subset: S,
29
+
30
+ /* the **ONLY** line that changed ↓↓↓ */
31
+ private readonly transform?: (
32
+ row: runtime.Types.Result.GetResult<
33
+ Prisma.$ProspectContactLeadPayload,
34
+ ReturnType<S>,
35
+ 'findMany'
36
+ >[number]
37
+ ) => R,
38
+ ) { }
39
+
40
+ public async findMany(
41
+ ...args: Parameters<S>
42
+ ): Promise<R[]> {
43
+
44
+ const { withStats, ...subset } = this.subset(...args);
45
+
46
+ const results = await this.delegate.findMany(subset);
47
+
48
+ // Load stats
49
+ const stats = withStats
50
+ ? await this.fetchStats( withStats, results )
51
+ : [];
52
+
53
+ return results.map(row => this.transformResult(row, stats, withStats));
54
+ }
55
+
56
+ public async findFirst(
57
+ ...args: Parameters<S>
58
+ ): Promise<R | null> {
59
+
60
+ const { withStats, ...subset } = this.subset(...args);
61
+
62
+ const result = await this.delegate.findFirst(subset);
63
+
64
+ const stats = withStats
65
+ ? await this.fetchStats( withStats, [result] )
66
+ : [];
67
+
68
+ return result ? this.transformResult(result, stats, withStats) : null;
69
+ }
70
+
71
+ private async fetchStats(
72
+ { $table, $key, ...withStats }: TWithStats,
73
+ results: any[]
74
+ ): Promise<any[]> {
75
+
76
+ const select = Object.entries(withStats).map(([key, sql]) =>
77
+ `(COALESCE((
78
+ ${sql}
79
+ ), 0)) as ${key}`
80
+ );
81
+
82
+ const stats = await this.prisma.$queryRawUnsafe(`
83
+ SELECT ${$key}, ${select.join(', ')}
84
+ FROM ${$table}
85
+ WHERE ${$key} IN (
86
+ ${results.map(r => "'" + r[ $key ] + "'").join(',')}
87
+ )
88
+ `);
89
+
90
+ for (const stat of stats) {
91
+ for (const key in stat) {
92
+
93
+ if (key === $key)
94
+ continue;
95
+
96
+ stat[key] = stat[key] ? parseInt(stat[key]) : 0;
97
+ }
98
+ }
99
+
100
+ return stats;
101
+ }
102
+
103
+ private transformResult( result: any, stats: any[], withStats?: TWithStats ) {
104
+
105
+ // Transform stats
106
+ const resultStats = withStats
107
+ ? stats.find(stat => stat[withStats.$key] === result[withStats.$key]) || {}
108
+ : {};
109
+
110
+ if (this.transform)
111
+ result = this.transform(result);
112
+
113
+ return {
114
+ ...result,
115
+ ...resultStats
116
+ }
117
+ }
118
+ }
@@ -9,6 +9,9 @@ import { PrismaClient } from '@/var/prisma';
9
9
  import type { Application } from '@server/app';
10
10
  import Service from '@server/app/service';
11
11
 
12
+ // Specific
13
+ import Facet, { TSubset } from './Facet';
14
+
12
15
  /*----------------------------------
13
16
  - TYPES
14
17
  ----------------------------------*/
@@ -37,10 +40,31 @@ export type Services = {
37
40
  export default class ModelsManager extends Service<Config, Hooks, Application> {
38
41
 
39
42
  public client = new PrismaClient();
43
+
44
+ public async ready() {
45
+
46
+ await this.client.$executeRaw`SET time_zone = '+00:00'`;
47
+
48
+ }
40
49
 
41
50
  public async shutdown() {
42
51
  await this.client.$disconnect()
43
52
  }
44
53
 
54
+ public Facet<
55
+ D extends {
56
+ findMany(args?: any): Promise<any>
57
+ findFirst(args?: any): Promise<any>
58
+ },
59
+ S extends TSubset,
60
+ R
61
+ >(...args: [D, S, R]) {
62
+
63
+ return new Facet(
64
+ this.client,
65
+ ...args
66
+ );
67
+ }
68
+
45
69
 
46
70
  }
@@ -144,8 +144,6 @@ export default class HttpServer {
144
144
 
145
145
  routes.get("/ping", (req, res) => res.send("pong"));
146
146
 
147
- routes.all('*', morgan('short'));
148
-
149
147
  /*----------------------------------
150
148
  - SESSION & SECURITE
151
149
  ----------------------------------*/