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.
- package/client/app/component.tsx +1 -0
- package/client/assets/css/colors.less +46 -25
- package/client/assets/css/components/button.less +14 -5
- package/client/assets/css/components/card.less +5 -10
- package/client/assets/css/components/mantine.less +6 -5
- package/client/assets/css/components/table.less +1 -1
- package/client/assets/css/text/icons.less +1 -1
- package/client/assets/css/text/text.less +4 -0
- package/client/assets/css/utils/borders.less +1 -1
- package/client/assets/css/utils/layouts.less +8 -5
- package/client/components/Button.tsx +20 -17
- package/client/components/Checkbox.tsx +6 -1
- package/client/components/ConnectedInput.tsx +34 -0
- package/client/components/DropDown.tsx +21 -4
- package/client/components/Input.tsx +2 -2
- package/client/components/Rte/Editor.tsx +23 -9
- package/client/components/Rte/ToolbarPlugin/ElementFormat.tsx +1 -1
- package/client/components/Rte/ToolbarPlugin/index.tsx +272 -183
- package/client/components/Rte/currentEditor.ts +31 -2
- package/client/components/Rte/index.tsx +3 -0
- package/client/components/Rte/plugins/FloatingTextFormatToolbarPlugin/index.tsx +4 -1
- package/client/components/Select.tsx +29 -16
- package/client/components/Table/index.tsx +27 -11
- package/client/components/containers/Popover/index.tsx +21 -4
- package/client/components/index.ts +4 -2
- package/client/services/router/index.tsx +7 -5
- package/common/errors/index.tsx +27 -3
- package/common/router/index.ts +4 -1
- package/common/utils/rte.ts +183 -0
- package/package.json +3 -2
- package/server/app/container/console/index.ts +62 -42
- package/server/app/container/index.ts +4 -0
- package/server/app/service/index.ts +4 -2
- package/server/services/auth/index.ts +28 -14
- package/server/services/auth/router/index.ts +1 -1
- package/server/services/auth/router/request.ts +4 -4
- package/server/services/email/index.ts +8 -51
- package/server/services/prisma/Facet.ts +118 -0
- package/server/services/prisma/index.ts +24 -0
- package/server/services/router/http/index.ts +0 -2
- package/server/services/router/index.ts +220 -86
- package/server/services/router/response/index.ts +0 -15
- package/server/utils/rte.ts +21 -132
- package/types/global/utils.d.ts +4 -22
- package/types/icons.d.ts +1 -1
- package/server/services/email/service.json +0 -6
- package/server/services/email/templates.ts +0 -49
- 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,
|
|
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
|
-
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
399
|
-
${
|
|
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
|
|
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");
|
|
@@ -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:
|
|
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(
|
|
191
|
-
|
|
192
|
-
|
|
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,
|
|
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(
|
|
49
|
-
public check(
|
|
50
|
-
public check(
|
|
51
|
-
return this.users.check( this.request,
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|