@hed-hog/core 0.0.349 → 0.0.350
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/dist/auth/auth.controller.d.ts +3 -3
- package/dist/auth/auth.service.d.ts +8 -8
- package/dist/core.module.d.ts.map +1 -1
- package/dist/core.module.js +3 -0
- package/dist/core.module.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/guards/mcp-auth.guard.d.ts +6 -1
- package/dist/mcp/guards/mcp-auth.guard.d.ts.map +1 -1
- package/dist/mcp/guards/mcp-auth.guard.js +29 -2
- package/dist/mcp/guards/mcp-auth.guard.js.map +1 -1
- package/dist/mcp/mcp-api-key.controller.d.ts +32 -0
- package/dist/mcp/mcp-api-key.controller.d.ts.map +1 -0
- package/dist/mcp/mcp-api-key.controller.js +64 -0
- package/dist/mcp/mcp-api-key.controller.js.map +1 -0
- package/dist/mcp/mcp-api-key.service.d.ts +27 -0
- package/dist/mcp/mcp-api-key.service.d.ts.map +1 -0
- package/dist/mcp/mcp-api-key.service.js +74 -0
- package/dist/mcp/mcp-api-key.service.js.map +1 -0
- package/dist/mcp/mcp-registry.service.d.ts +1 -2
- package/dist/mcp/mcp-registry.service.d.ts.map +1 -1
- package/dist/mcp/mcp-registry.service.js +6 -2
- package/dist/mcp/mcp-registry.service.js.map +1 -1
- package/dist/mcp/mcp.module.d.ts.map +1 -1
- package/dist/mcp/mcp.module.js +8 -1
- package/dist/mcp/mcp.module.js.map +1 -1
- package/dist/mcp-chat/mcp-chat.controller.d.ts +4 -0
- package/dist/mcp-chat/mcp-chat.controller.d.ts.map +1 -1
- package/dist/mcp-chat/mcp-chat.controller.js +46 -0
- package/dist/mcp-chat/mcp-chat.controller.js.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.d.ts +6 -1
- package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.js +125 -17
- package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
- package/dist/notification/dto/create-notification.dto.d.ts +26 -0
- package/dist/notification/dto/create-notification.dto.d.ts.map +1 -0
- package/dist/notification/dto/create-notification.dto.js +86 -0
- package/dist/notification/dto/create-notification.dto.js.map +1 -0
- package/dist/notification/dto/update-progress.dto.d.ts +6 -0
- package/dist/notification/dto/update-progress.dto.d.ts.map +1 -0
- package/dist/notification/dto/update-progress.dto.js +33 -0
- package/dist/notification/dto/update-progress.dto.js.map +1 -0
- package/dist/notification/notification.controller.d.ts +108 -0
- package/dist/notification/notification.controller.d.ts.map +1 -0
- package/dist/notification/notification.controller.js +118 -0
- package/dist/notification/notification.controller.js.map +1 -0
- package/dist/notification/notification.module.d.ts +3 -0
- package/dist/notification/notification.module.d.ts.map +1 -0
- package/dist/notification/notification.module.js +29 -0
- package/dist/notification/notification.module.js.map +1 -0
- package/dist/notification/notification.service.d.ts +98 -0
- package/dist/notification/notification.service.d.ts.map +1 -0
- package/dist/notification/notification.service.js +123 -0
- package/dist/notification/notification.service.js.map +1 -0
- package/dist/profile/profile.controller.d.ts +1 -1
- package/dist/profile/profile.service.d.ts +1 -1
- package/dist/session/session.controller.d.ts +1 -1
- package/dist/session/session.service.d.ts +3 -3
- package/dist/setting/dto/update-setting-list.dto.d.ts +9 -0
- package/dist/setting/dto/update-setting-list.dto.d.ts.map +1 -0
- package/dist/setting/dto/update-setting-list.dto.js +44 -0
- package/dist/setting/dto/update-setting-list.dto.js.map +1 -0
- package/dist/setting/setting.controller.d.ts +19 -0
- package/dist/setting/setting.controller.d.ts.map +1 -1
- package/dist/setting/setting.controller.js +12 -0
- package/dist/setting/setting.controller.js.map +1 -1
- package/dist/setting/setting.service.d.ts +19 -0
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +79 -0
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/task/task.service.d.ts +12 -0
- package/dist/task/task.service.d.ts.map +1 -1
- package/dist/task/task.service.js +60 -0
- package/dist/task/task.service.js.map +1 -1
- package/dist/user/user.controller.d.ts +2 -2
- package/dist/user/user.service.d.ts +6 -6
- package/dist/webhook-integration/webhook-integration.service.d.ts +4 -0
- package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.js +184 -34
- package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
- package/dist/webhook-integration/webhook-utils.d.ts +1 -0
- package/dist/webhook-integration/webhook-utils.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-utils.js +38 -0
- package/dist/webhook-integration/webhook-utils.js.map +1 -1
- package/hedhog/data/menu.yaml +48 -4
- package/hedhog/data/route.yaml +133 -0
- package/hedhog/data/setting_group.yaml +45 -0
- package/hedhog/data/setting_subgroup.yaml +16 -0
- package/hedhog/frontend/app/account/components/mcp-tokens.tsx.ejs +285 -0
- package/hedhog/frontend/app/account/layout.tsx.ejs +32 -55
- package/hedhog/frontend/app/account/mcp-tokens/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +2098 -1835
- package/hedhog/frontend/app/mcp_chat/chat-components.tsx.ejs +595 -0
- package/hedhog/frontend/app/mcp_chat/chat-types.ts.ejs +16 -0
- package/hedhog/frontend/app/mcp_chat/page.tsx.ejs +554 -383
- package/hedhog/frontend/app/setting-list/page.tsx.ejs +649 -0
- package/hedhog/frontend/app/users/page.tsx.ejs +445 -203
- package/hedhog/frontend/app/users/user-sheet.tsx.ejs +16 -37
- package/hedhog/frontend/messages/en.json +241 -2
- package/hedhog/frontend/messages/pt.json +241 -2
- package/hedhog/table/mcp_api_key.yaml +30 -0
- package/hedhog/table/notification.yaml +72 -0
- package/hedhog/table/webhook_action_log.yaml +49 -0
- package/package.json +4 -4
- package/src/core.module.ts +3 -0
- package/src/index.ts +5 -0
- package/src/mcp/guards/mcp-auth.guard.ts +64 -32
- package/src/mcp/mcp-api-key.controller.ts +42 -0
- package/src/mcp/mcp-api-key.service.ts +66 -0
- package/src/mcp/mcp-registry.service.ts +75 -69
- package/src/mcp/mcp.module.ts +43 -36
- package/src/mcp-chat/mcp-chat.controller.ts +113 -60
- package/src/mcp-chat/mcp-chat.service.ts +494 -332
- package/src/notification/dto/create-notification.dto.ts +71 -0
- package/src/notification/dto/update-progress.dto.ts +16 -0
- package/src/notification/notification.controller.ts +71 -0
- package/src/notification/notification.module.ts +16 -0
- package/src/notification/notification.service.ts +124 -0
- package/src/setting/dto/update-setting-list.dto.ts +32 -0
- package/src/setting/setting.controller.ts +9 -0
- package/src/setting/setting.service.ts +91 -0
- package/src/task/task.service.ts +72 -0
- package/src/webhook-integration/webhook-integration.service.ts +934 -735
- package/src/webhook-integration/webhook-utils.ts +136 -79
|
@@ -1,735 +1,934 @@
|
|
|
1
|
-
import { DeleteDTO } from '@hed-hog/api';
|
|
2
|
-
import { MailService as MailMainService } from '@hed-hog/api-mail';
|
|
3
|
-
import { PaginationDTO, PaginationService } from '@hed-hog/api-pagination';
|
|
4
|
-
import { PrismaService } from '@hed-hog/api-prisma';
|
|
5
|
-
import { HttpService } from '@nestjs/axios';
|
|
6
|
-
import {
|
|
7
|
-
BadRequestException,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
},
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
)
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
status:
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
if (!
|
|
599
|
-
throw new
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1
|
+
import { DeleteDTO } from '@hed-hog/api';
|
|
2
|
+
import { MailService as MailMainService } from '@hed-hog/api-mail';
|
|
3
|
+
import { PaginationDTO, PaginationService } from '@hed-hog/api-pagination';
|
|
4
|
+
import { PrismaService } from '@hed-hog/api-prisma';
|
|
5
|
+
import { HttpService } from '@nestjs/axios';
|
|
6
|
+
import {
|
|
7
|
+
BadRequestException,
|
|
8
|
+
HttpException,
|
|
9
|
+
Injectable,
|
|
10
|
+
NotFoundException,
|
|
11
|
+
} from '@nestjs/common';
|
|
12
|
+
import { Request } from 'express';
|
|
13
|
+
import { firstValueFrom } from 'rxjs';
|
|
14
|
+
import { MailService } from '../mail/mail.service';
|
|
15
|
+
import { SettingService } from '../setting/setting.service';
|
|
16
|
+
import {
|
|
17
|
+
createPlainToken,
|
|
18
|
+
createPublicUuid,
|
|
19
|
+
hashToken,
|
|
20
|
+
normalizeJsonObject,
|
|
21
|
+
renderTemplate,
|
|
22
|
+
sanitizeSensitiveData,
|
|
23
|
+
summarizePayload,
|
|
24
|
+
} from './webhook-utils';
|
|
25
|
+
|
|
26
|
+
type ActionExecutionLog = {
|
|
27
|
+
webhookActionId: number;
|
|
28
|
+
actionName: string;
|
|
29
|
+
actionType: 'email' | 'whatsapp_evolution';
|
|
30
|
+
status: 'success' | 'failed';
|
|
31
|
+
durationMs: number;
|
|
32
|
+
requestPayload?: unknown;
|
|
33
|
+
responsePayload?: unknown;
|
|
34
|
+
errorMessage?: string | null;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
@Injectable()
|
|
38
|
+
export class WebhookIntegrationService {
|
|
39
|
+
constructor(
|
|
40
|
+
private readonly prisma: PrismaService,
|
|
41
|
+
private readonly pagination: PaginationService,
|
|
42
|
+
private readonly http: HttpService,
|
|
43
|
+
private readonly mailMainService: MailMainService,
|
|
44
|
+
private readonly coreMailService: MailService,
|
|
45
|
+
private readonly settingService: SettingService,
|
|
46
|
+
) {}
|
|
47
|
+
|
|
48
|
+
private get integrations() {
|
|
49
|
+
return (this.prisma as any).webhook_integration;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private get actions() {
|
|
53
|
+
return (this.prisma as any).webhook_action;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private get logs() {
|
|
57
|
+
return (this.prisma as any).webhook_call_log;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private get actionLogs() {
|
|
61
|
+
return (this.prisma as any).webhook_action_log;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async list(paginationParams: PaginationDTO) {
|
|
65
|
+
const fields = ['slug', 'name', 'description', 'public_uuid', 'status'];
|
|
66
|
+
const OR = this.prisma.createInsensitiveSearch(fields, paginationParams);
|
|
67
|
+
|
|
68
|
+
const result = await this.pagination.paginate(
|
|
69
|
+
this.integrations,
|
|
70
|
+
paginationParams,
|
|
71
|
+
{
|
|
72
|
+
where: OR.length > 0 ? { OR } : {},
|
|
73
|
+
include: {
|
|
74
|
+
webhook_action: {
|
|
75
|
+
orderBy: [{ order: 'asc' }, { id: 'asc' }],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
orderBy: [{ updated_at: 'desc' }, { id: 'desc' }],
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
...result,
|
|
84
|
+
data: await this.withPublicUrls(result.data || []),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async get(id: number) {
|
|
89
|
+
const record = await this.integrations.findUnique({
|
|
90
|
+
where: { id },
|
|
91
|
+
include: {
|
|
92
|
+
webhook_action: {
|
|
93
|
+
orderBy: [{ order: 'asc' }, { id: 'asc' }],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (!record) {
|
|
99
|
+
throw new NotFoundException(
|
|
100
|
+
`Webhook integration with id ${id} not found.`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return this.withPublicUrl(record);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async create(data: any) {
|
|
108
|
+
await this.ensureSlugAvailable(data.slug);
|
|
109
|
+
|
|
110
|
+
let plainToken: string | null = null;
|
|
111
|
+
if (data.require_token) {
|
|
112
|
+
plainToken = createPlainToken();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const record = await this.integrations.create({
|
|
116
|
+
data: {
|
|
117
|
+
...this.normalizeIntegration(data),
|
|
118
|
+
public_uuid: createPublicUuid(),
|
|
119
|
+
token_hash: plainToken ? hashToken(plainToken) : null,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
...(await this.withPublicUrl(record)),
|
|
125
|
+
plainToken,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async update(id: number, data: any) {
|
|
130
|
+
const existing = await this.get(id);
|
|
131
|
+
if (data.slug && data.slug !== existing.slug) {
|
|
132
|
+
await this.ensureSlugAvailable(data.slug);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let plainToken: string | null = null;
|
|
136
|
+
const updateData = this.normalizeIntegration(data, true);
|
|
137
|
+
if (data.require_token === false) {
|
|
138
|
+
updateData.token_hash = null;
|
|
139
|
+
} else if (data.require_token === true && !existing.token_hash) {
|
|
140
|
+
plainToken = createPlainToken();
|
|
141
|
+
updateData.token_hash = hashToken(plainToken);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const record = await this.integrations.update({
|
|
145
|
+
where: { id },
|
|
146
|
+
data: updateData,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
...(await this.withPublicUrl(record)),
|
|
151
|
+
plainToken,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async delete(data: DeleteDTO) {
|
|
156
|
+
if (!data.ids?.length) {
|
|
157
|
+
throw new BadRequestException(
|
|
158
|
+
'You must select at least one item to delete.',
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return this.integrations.deleteMany({
|
|
163
|
+
where: { id: { in: data.ids } },
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async regenerateUuid(id: number) {
|
|
168
|
+
await this.get(id);
|
|
169
|
+
const record = await this.integrations.update({
|
|
170
|
+
where: { id },
|
|
171
|
+
data: { public_uuid: createPublicUuid() },
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return this.withPublicUrl(record);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async regenerateToken(id: number) {
|
|
178
|
+
await this.get(id);
|
|
179
|
+
const plainToken = createPlainToken();
|
|
180
|
+
const record = await this.integrations.update({
|
|
181
|
+
where: { id },
|
|
182
|
+
data: {
|
|
183
|
+
require_token: true,
|
|
184
|
+
token_hash: hashToken(plainToken),
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
...(await this.withPublicUrl(record)),
|
|
190
|
+
plainToken,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private async getApplicationBaseUrl() {
|
|
195
|
+
const settings = await this.settingService.getSettingValues('url');
|
|
196
|
+
const value = settings?.url;
|
|
197
|
+
return typeof value === 'string' ? value.replace(/\/+$/, '') : '';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private async withPublicUrl<T extends { public_uuid?: string | null }>(
|
|
201
|
+
record: T,
|
|
202
|
+
) {
|
|
203
|
+
const baseUrl = await this.getApplicationBaseUrl();
|
|
204
|
+
return {
|
|
205
|
+
...record,
|
|
206
|
+
public_url:
|
|
207
|
+
baseUrl && record.public_uuid
|
|
208
|
+
? `${baseUrl}/webhook/${record.public_uuid}`
|
|
209
|
+
: null,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private async withPublicUrls<T extends { public_uuid?: string | null }>(
|
|
214
|
+
records: T[],
|
|
215
|
+
) {
|
|
216
|
+
const baseUrl = await this.getApplicationBaseUrl();
|
|
217
|
+
return records.map((record) => ({
|
|
218
|
+
...record,
|
|
219
|
+
public_url:
|
|
220
|
+
baseUrl && record.public_uuid
|
|
221
|
+
? `${baseUrl}/webhook/${record.public_uuid}`
|
|
222
|
+
: null,
|
|
223
|
+
}));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async listActions(integrationId: number) {
|
|
227
|
+
await this.get(integrationId);
|
|
228
|
+
return this.actions.findMany({
|
|
229
|
+
where: { webhook_integration_id: integrationId },
|
|
230
|
+
orderBy: [{ order: 'asc' }, { id: 'asc' }],
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async createAction(integrationId: number, data: any) {
|
|
235
|
+
await this.get(integrationId);
|
|
236
|
+
return this.actions.create({
|
|
237
|
+
data: {
|
|
238
|
+
...this.normalizeAction(data),
|
|
239
|
+
webhook_integration_id: integrationId,
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async updateAction(integrationId: number, actionId: number, data: any) {
|
|
245
|
+
await this.ensureAction(integrationId, actionId);
|
|
246
|
+
return this.actions.update({
|
|
247
|
+
where: { id: actionId },
|
|
248
|
+
data: this.normalizeAction(data, true),
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async deleteActions(integrationId: number, data: DeleteDTO) {
|
|
253
|
+
await this.get(integrationId);
|
|
254
|
+
if (!data.ids?.length) {
|
|
255
|
+
throw new BadRequestException(
|
|
256
|
+
'You must select at least one item to delete.',
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return this.actions.deleteMany({
|
|
261
|
+
where: {
|
|
262
|
+
webhook_integration_id: integrationId,
|
|
263
|
+
id: { in: data.ids },
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async listLogs(integrationId: number, paginationParams: PaginationDTO) {
|
|
269
|
+
await this.get(integrationId);
|
|
270
|
+
const result = await this.pagination.paginate(this.logs, paginationParams, {
|
|
271
|
+
where: { webhook_integration_id: integrationId },
|
|
272
|
+
orderBy: [{ created_at: 'desc' }, { id: 'desc' }],
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const logIds = (result.data || [])
|
|
276
|
+
.map((item: any) => item.id)
|
|
277
|
+
.filter((id: unknown) => typeof id === 'number');
|
|
278
|
+
|
|
279
|
+
if (!logIds.length || !this.actionLogs) {
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const actionLogs = await this.actionLogs.findMany({
|
|
284
|
+
where: {
|
|
285
|
+
webhook_call_log_id: { in: logIds },
|
|
286
|
+
},
|
|
287
|
+
orderBy: [{ created_at: 'asc' }, { id: 'asc' }],
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const actionLogsByCallId = new Map<number, any[]>();
|
|
291
|
+
for (const actionLog of actionLogs || []) {
|
|
292
|
+
const list = actionLogsByCallId.get(actionLog.webhook_call_log_id) || [];
|
|
293
|
+
list.push(actionLog);
|
|
294
|
+
actionLogsByCallId.set(actionLog.webhook_call_log_id, list);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
...result,
|
|
299
|
+
data: (result.data || []).map((item: any) => ({
|
|
300
|
+
...item,
|
|
301
|
+
webhook_action_log: actionLogsByCallId.get(item.id) || [],
|
|
302
|
+
})),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async executePublic(uuid: string, body: any, headers: any, req: Request) {
|
|
307
|
+
const startedAt = Date.now();
|
|
308
|
+
const remoteIp = this.getRemoteIp(req);
|
|
309
|
+
const actionLogs: ActionExecutionLog[] = [];
|
|
310
|
+
let integration: any | null = null;
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
integration = await this.integrations.findUnique({
|
|
314
|
+
where: { public_uuid: uuid },
|
|
315
|
+
include: {
|
|
316
|
+
webhook_action: {
|
|
317
|
+
where: { status: 'active' },
|
|
318
|
+
orderBy: [{ order: 'asc' }, { id: 'asc' }],
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
if (!integration || integration.status !== 'active') {
|
|
324
|
+
throw new NotFoundException('Webhook not found.');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
this.validateToken(integration, headers);
|
|
328
|
+
this.validateIp(integration, remoteIp);
|
|
329
|
+
this.validateBodySchema(integration, body);
|
|
330
|
+
|
|
331
|
+
const actionResults = [];
|
|
332
|
+
for (const action of integration.webhook_action || []) {
|
|
333
|
+
const actionStartedAt = Date.now();
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
const result = await this.executeAction(action, body);
|
|
337
|
+
const durationMs = Date.now() - actionStartedAt;
|
|
338
|
+
|
|
339
|
+
actionResults.push({
|
|
340
|
+
id: action.id,
|
|
341
|
+
type: action.type,
|
|
342
|
+
success: true,
|
|
343
|
+
duration_ms: durationMs,
|
|
344
|
+
response: summarizePayload(result.responsePayload),
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
actionLogs.push({
|
|
348
|
+
webhookActionId: action.id,
|
|
349
|
+
actionName: action.name,
|
|
350
|
+
actionType: action.type,
|
|
351
|
+
status: 'success',
|
|
352
|
+
durationMs,
|
|
353
|
+
requestPayload: result.requestPayload,
|
|
354
|
+
responsePayload: result.responsePayload,
|
|
355
|
+
});
|
|
356
|
+
} catch (error) {
|
|
357
|
+
const durationMs = Date.now() - actionStartedAt;
|
|
358
|
+
const errorMessage = this.extractErrorMessage(error);
|
|
359
|
+
|
|
360
|
+
actionResults.push({
|
|
361
|
+
id: action.id,
|
|
362
|
+
type: action.type,
|
|
363
|
+
success: false,
|
|
364
|
+
duration_ms: durationMs,
|
|
365
|
+
error: errorMessage,
|
|
366
|
+
response: summarizePayload(this.extractErrorResponse(error)),
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
actionLogs.push({
|
|
370
|
+
webhookActionId: action.id,
|
|
371
|
+
actionName: action.name,
|
|
372
|
+
actionType: action.type,
|
|
373
|
+
status: 'failed',
|
|
374
|
+
durationMs,
|
|
375
|
+
errorMessage,
|
|
376
|
+
responsePayload: this.extractErrorResponse(error),
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
throw error;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const response = {
|
|
384
|
+
success: true,
|
|
385
|
+
actions: actionResults,
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const callLog = await this.createCallLog({
|
|
389
|
+
integration,
|
|
390
|
+
uuid,
|
|
391
|
+
status: 'success',
|
|
392
|
+
remoteIp,
|
|
393
|
+
durationMs: Date.now() - startedAt,
|
|
394
|
+
payload: body,
|
|
395
|
+
response,
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
await this.createActionLogs(callLog?.id, actionLogs);
|
|
399
|
+
|
|
400
|
+
return response;
|
|
401
|
+
} catch (error) {
|
|
402
|
+
const callLog = await this.createCallLog({
|
|
403
|
+
integration,
|
|
404
|
+
uuid,
|
|
405
|
+
status: 'failed',
|
|
406
|
+
remoteIp,
|
|
407
|
+
durationMs: Date.now() - startedAt,
|
|
408
|
+
payload: body,
|
|
409
|
+
response: {
|
|
410
|
+
success: false,
|
|
411
|
+
actions: actionLogs.map((item) => ({
|
|
412
|
+
id: item.webhookActionId,
|
|
413
|
+
type: item.actionType,
|
|
414
|
+
success: item.status === 'success',
|
|
415
|
+
duration_ms: item.durationMs,
|
|
416
|
+
error: item.errorMessage || null,
|
|
417
|
+
})),
|
|
418
|
+
},
|
|
419
|
+
error,
|
|
420
|
+
}).catch(() => null);
|
|
421
|
+
|
|
422
|
+
await this.createActionLogs(callLog?.id, actionLogs).catch(() => undefined);
|
|
423
|
+
|
|
424
|
+
if (error instanceof HttpException) {
|
|
425
|
+
throw error;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
success: false,
|
|
430
|
+
actions: actionLogs.map((item) => ({
|
|
431
|
+
id: item.webhookActionId,
|
|
432
|
+
type: item.actionType,
|
|
433
|
+
success: item.status === 'success',
|
|
434
|
+
duration_ms: item.durationMs,
|
|
435
|
+
error: item.errorMessage || null,
|
|
436
|
+
})),
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private async executeAction(action: any, body: any) {
|
|
442
|
+
if (action.type === 'email') {
|
|
443
|
+
return this.executeEmailAction(action, body);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (action.type === 'whatsapp_evolution') {
|
|
447
|
+
return this.executeWhatsappAction(action, body);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
throw new BadRequestException(`Unsupported action type: ${action.type}`);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
private async executeEmailAction(action: any, body: any) {
|
|
454
|
+
if (!action.mail_profile_id || !action.mail_id || !action.email_to) {
|
|
455
|
+
throw new BadRequestException(
|
|
456
|
+
'Email action is missing required configuration.',
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const profile = await (this.prisma as any).mail_profile.findUnique({
|
|
461
|
+
where: { id: Number(action.mail_profile_id) },
|
|
462
|
+
});
|
|
463
|
+
const mail = await (this.prisma as any).mail.findUnique({
|
|
464
|
+
where: { id: Number(action.mail_id) },
|
|
465
|
+
include: {
|
|
466
|
+
mail_locale: {
|
|
467
|
+
orderBy: [{ locale_id: 'asc' }],
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
if (!profile || !mail?.mail_locale?.length) {
|
|
473
|
+
throw new BadRequestException(
|
|
474
|
+
'Email action references an invalid profile or template.',
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const template = mail.mail_locale[0];
|
|
479
|
+
const config = this.buildMailConfig(profile);
|
|
480
|
+
const sender = profile.from_name
|
|
481
|
+
? `${profile.from_name} <${profile.from_email}>`
|
|
482
|
+
: profile.from_email;
|
|
483
|
+
const replyTo = profile.reply_to_email
|
|
484
|
+
? profile.reply_to_name
|
|
485
|
+
? `${profile.reply_to_name} <${profile.reply_to_email}>`
|
|
486
|
+
: profile.reply_to_email
|
|
487
|
+
: undefined;
|
|
488
|
+
|
|
489
|
+
const requestPayload = {
|
|
490
|
+
to: renderTemplate(action.email_to, body) as string,
|
|
491
|
+
cc: action.email_cc
|
|
492
|
+
? (renderTemplate(action.email_cc, body) as string)
|
|
493
|
+
: undefined,
|
|
494
|
+
bcc: action.email_bcc
|
|
495
|
+
? (renderTemplate(action.email_bcc, body) as string)
|
|
496
|
+
: undefined,
|
|
497
|
+
from: sender,
|
|
498
|
+
replyTo,
|
|
499
|
+
subject: renderTemplate(template.subject, body) as string,
|
|
500
|
+
body: renderTemplate(template.body, body) as string,
|
|
501
|
+
mail_id: mail.id,
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
this.mailMainService.setConfig(config as any);
|
|
506
|
+
const providerResponse = await this.mailMainService.send(
|
|
507
|
+
requestPayload as any,
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
requestPayload,
|
|
512
|
+
responsePayload: {
|
|
513
|
+
provider: 'email',
|
|
514
|
+
response: providerResponse || null,
|
|
515
|
+
},
|
|
516
|
+
};
|
|
517
|
+
} finally {
|
|
518
|
+
await this.coreMailService.reloadConfig().catch(() => undefined);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
private async executeWhatsappAction(action: any, body: any) {
|
|
523
|
+
const required = [
|
|
524
|
+
action.whatsapp_target,
|
|
525
|
+
action.whatsapp_template,
|
|
526
|
+
action.whatsapp_instance,
|
|
527
|
+
action.whatsapp_base_url,
|
|
528
|
+
action.whatsapp_token,
|
|
529
|
+
];
|
|
530
|
+
|
|
531
|
+
if (required.some((value) => !String(value || '').trim())) {
|
|
532
|
+
throw new BadRequestException(
|
|
533
|
+
'WhatsApp action is missing required configuration.',
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const baseUrl = String(action.whatsapp_base_url).replace(/\/+$/, '');
|
|
538
|
+
const instance = encodeURIComponent(action.whatsapp_instance);
|
|
539
|
+
const url = `${baseUrl}/message/sendText/${instance}`;
|
|
540
|
+
const message = renderTemplate(action.whatsapp_template, body);
|
|
541
|
+
const target = renderTemplate(action.whatsapp_target, body);
|
|
542
|
+
|
|
543
|
+
const response = await firstValueFrom(
|
|
544
|
+
this.http.post(
|
|
545
|
+
url,
|
|
546
|
+
{
|
|
547
|
+
number: target,
|
|
548
|
+
text: message,
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
headers: {
|
|
552
|
+
apikey: action.whatsapp_token,
|
|
553
|
+
Authorization: `Bearer ${action.whatsapp_token}`,
|
|
554
|
+
},
|
|
555
|
+
timeout: 15000,
|
|
556
|
+
},
|
|
557
|
+
),
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
return {
|
|
561
|
+
requestPayload: {
|
|
562
|
+
url,
|
|
563
|
+
number: target,
|
|
564
|
+
text: message,
|
|
565
|
+
},
|
|
566
|
+
responsePayload: {
|
|
567
|
+
status: response?.status || null,
|
|
568
|
+
statusText: response?.statusText || null,
|
|
569
|
+
data: response?.data || null,
|
|
570
|
+
},
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
private validateToken(integration: any, headers: any) {
|
|
575
|
+
if (!integration.require_token) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const provided =
|
|
580
|
+
headers['x-webhook-token'] ||
|
|
581
|
+
headers['X-Webhook-Token'] ||
|
|
582
|
+
headers.authorization?.replace(/^Bearer\s+/i, '');
|
|
583
|
+
|
|
584
|
+
if (!provided || hashToken(String(provided)) !== integration.token_hash) {
|
|
585
|
+
throw new BadRequestException('Invalid webhook token.');
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
private validateIp(integration: any, remoteIp: string | null) {
|
|
590
|
+
const allowedIps = Array.isArray(integration.allowed_ips)
|
|
591
|
+
? integration.allowed_ips
|
|
592
|
+
: [];
|
|
593
|
+
|
|
594
|
+
if (!allowedIps.length) {
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (!remoteIp || !allowedIps.includes(remoteIp)) {
|
|
599
|
+
throw new BadRequestException('Remote IP is not allowed.');
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
private validateBodySchema(integration: any, body: any) {
|
|
604
|
+
if (!integration.body_schema) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const errors = this.validateJsonSchema(integration.body_schema, body);
|
|
609
|
+
if (errors.length) {
|
|
610
|
+
throw new BadRequestException({
|
|
611
|
+
message: 'Webhook body does not match the configured schema.',
|
|
612
|
+
errors,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
private validateJsonSchema(schema: any, value: any, path = '$'): string[] {
|
|
618
|
+
if (!schema || typeof schema !== 'object') {
|
|
619
|
+
return [];
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const errors: string[] = [];
|
|
623
|
+
const type = schema.type;
|
|
624
|
+
|
|
625
|
+
if (type && !this.matchesJsonSchemaType(type, value)) {
|
|
626
|
+
errors.push(`${path} must be ${type}`);
|
|
627
|
+
return errors;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (type === 'object' || schema.properties || schema.required) {
|
|
631
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
632
|
+
errors.push(`${path} must be object`);
|
|
633
|
+
return errors;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
for (const requiredKey of schema.required || []) {
|
|
637
|
+
if (value[requiredKey] === undefined || value[requiredKey] === null) {
|
|
638
|
+
errors.push(`${path}.${requiredKey} is required`);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
for (const [key, childSchema] of Object.entries(
|
|
643
|
+
schema.properties || {},
|
|
644
|
+
)) {
|
|
645
|
+
if (value[key] !== undefined) {
|
|
646
|
+
errors.push(
|
|
647
|
+
...this.validateJsonSchema(
|
|
648
|
+
childSchema,
|
|
649
|
+
value[key],
|
|
650
|
+
`${path}.${key}`,
|
|
651
|
+
),
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (type === 'array' && schema.items && Array.isArray(value)) {
|
|
658
|
+
value.forEach((item, index) => {
|
|
659
|
+
errors.push(
|
|
660
|
+
...this.validateJsonSchema(schema.items, item, `${path}[${index}]`),
|
|
661
|
+
);
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return errors;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
private matchesJsonSchemaType(type: string | string[], value: any) {
|
|
669
|
+
const types = Array.isArray(type) ? type : [type];
|
|
670
|
+
return types.some((item) => {
|
|
671
|
+
switch (item) {
|
|
672
|
+
case 'object':
|
|
673
|
+
return (
|
|
674
|
+
value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
675
|
+
);
|
|
676
|
+
case 'array':
|
|
677
|
+
return Array.isArray(value);
|
|
678
|
+
case 'string':
|
|
679
|
+
return typeof value === 'string';
|
|
680
|
+
case 'number':
|
|
681
|
+
return typeof value === 'number';
|
|
682
|
+
case 'integer':
|
|
683
|
+
return Number.isInteger(value);
|
|
684
|
+
case 'boolean':
|
|
685
|
+
return typeof value === 'boolean';
|
|
686
|
+
case 'null':
|
|
687
|
+
return value === null;
|
|
688
|
+
default:
|
|
689
|
+
return true;
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
private async createCallLog(input: {
|
|
695
|
+
integration: any | null;
|
|
696
|
+
uuid: string;
|
|
697
|
+
status: 'success' | 'failed';
|
|
698
|
+
remoteIp: string | null;
|
|
699
|
+
durationMs: number;
|
|
700
|
+
payload: any;
|
|
701
|
+
response?: any;
|
|
702
|
+
error?: any;
|
|
703
|
+
}) {
|
|
704
|
+
if (!this.logs) {
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return this.logs.create({
|
|
709
|
+
data: {
|
|
710
|
+
webhook_integration_id: input.integration?.id ?? null,
|
|
711
|
+
public_uuid: input.uuid,
|
|
712
|
+
status: input.status,
|
|
713
|
+
remote_ip: input.remoteIp,
|
|
714
|
+
duration_ms: input.durationMs,
|
|
715
|
+
error_message: input.error ? this.extractErrorMessage(input.error) : null,
|
|
716
|
+
payload_summary: summarizePayload(sanitizeSensitiveData(input.payload)),
|
|
717
|
+
response_summary: summarizePayload(sanitizeSensitiveData(input.response)),
|
|
718
|
+
},
|
|
719
|
+
select: {
|
|
720
|
+
id: true,
|
|
721
|
+
},
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
private async createActionLogs(callLogId: number | undefined, logs: ActionExecutionLog[]) {
|
|
726
|
+
if (!callLogId || !logs.length || !this.actionLogs) {
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
await this.actionLogs.createMany({
|
|
731
|
+
data: logs.map((item) => ({
|
|
732
|
+
webhook_call_log_id: callLogId,
|
|
733
|
+
webhook_action_id: item.webhookActionId,
|
|
734
|
+
action_name: item.actionName,
|
|
735
|
+
action_type: item.actionType,
|
|
736
|
+
status: item.status,
|
|
737
|
+
duration_ms: item.durationMs,
|
|
738
|
+
request_payload: summarizePayload(sanitizeSensitiveData(item.requestPayload)),
|
|
739
|
+
response_payload: summarizePayload(
|
|
740
|
+
sanitizeSensitiveData(item.responsePayload),
|
|
741
|
+
),
|
|
742
|
+
error_message: item.errorMessage || null,
|
|
743
|
+
})),
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
private extractErrorMessage(error: unknown) {
|
|
748
|
+
if (!error) {
|
|
749
|
+
return 'Unknown error';
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
if (error instanceof Error && error.message) {
|
|
753
|
+
return error.message;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if (typeof error === 'object' && (error as any).message) {
|
|
757
|
+
return String((error as any).message);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
return String(error);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
private extractErrorResponse(error: unknown) {
|
|
764
|
+
if (!error || typeof error !== 'object') {
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const maybeResponse = (error as any).response;
|
|
769
|
+
if (!maybeResponse) {
|
|
770
|
+
return null;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
return {
|
|
774
|
+
status: maybeResponse.status || null,
|
|
775
|
+
statusText: maybeResponse.statusText || null,
|
|
776
|
+
data: maybeResponse.data || null,
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
private getRemoteIp(req: Request) {
|
|
781
|
+
const forwardedFor = req.headers['x-forwarded-for'];
|
|
782
|
+
const forwardedIp = Array.isArray(forwardedFor)
|
|
783
|
+
? forwardedFor[0]
|
|
784
|
+
: forwardedFor?.split(',')[0];
|
|
785
|
+
|
|
786
|
+
return forwardedIp?.trim() || req.ip || req.socket.remoteAddress || null;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
private async ensureAction(integrationId: number, actionId: number) {
|
|
790
|
+
const record = await this.actions.findFirst({
|
|
791
|
+
where: {
|
|
792
|
+
id: actionId,
|
|
793
|
+
webhook_integration_id: integrationId,
|
|
794
|
+
},
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
if (!record) {
|
|
798
|
+
throw new NotFoundException(
|
|
799
|
+
`Webhook action with id ${actionId} not found.`,
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
return record;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
private async ensureSlugAvailable(slug: string) {
|
|
807
|
+
const existing = await this.integrations.findUnique({
|
|
808
|
+
where: { slug: String(slug || '').trim() },
|
|
809
|
+
select: { id: true },
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
if (existing) {
|
|
813
|
+
throw new BadRequestException(
|
|
814
|
+
`Webhook integration "${slug}" already exists.`,
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
private normalizeIntegration(data: any, partial = false) {
|
|
820
|
+
const normalized: Record<string, any> = {};
|
|
821
|
+
const assign = (key: string, value: any) => {
|
|
822
|
+
if (!partial || value !== undefined) {
|
|
823
|
+
normalized[key] = value;
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
assign('slug', String(data.slug ?? '').trim());
|
|
828
|
+
assign('name', String(data.name ?? '').trim());
|
|
829
|
+
assign('description', this.nullableString(data.description, partial));
|
|
830
|
+
assign('status', data.status === 'inactive' ? 'inactive' : 'active');
|
|
831
|
+
assign('require_token', Boolean(data.require_token));
|
|
832
|
+
assign('allowed_ips', normalizeJsonObject(data.allowed_ips));
|
|
833
|
+
assign('body_schema', normalizeJsonObject(data.body_schema));
|
|
834
|
+
|
|
835
|
+
return normalized;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
private normalizeAction(data: any, partial = false) {
|
|
839
|
+
const normalized: Record<string, any> = {};
|
|
840
|
+
const assign = (key: string, value: any) => {
|
|
841
|
+
if (!partial || value !== undefined) {
|
|
842
|
+
normalized[key] = value;
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
assign('type', data.type);
|
|
847
|
+
assign('name', String(data.name ?? '').trim());
|
|
848
|
+
assign('order', Number(data.order ?? 0));
|
|
849
|
+
assign('status', data.status === 'inactive' ? 'inactive' : 'active');
|
|
850
|
+
assign('mail_profile_id', this.nullableNumber(data.mail_profile_id));
|
|
851
|
+
assign('mail_id', this.nullableNumber(data.mail_id));
|
|
852
|
+
assign('email_to', this.nullableString(data.email_to, partial));
|
|
853
|
+
assign('email_cc', this.nullableString(data.email_cc, partial));
|
|
854
|
+
assign('email_bcc', this.nullableString(data.email_bcc, partial));
|
|
855
|
+
assign('whatsapp_target_type', data.whatsapp_target_type || null);
|
|
856
|
+
assign(
|
|
857
|
+
'whatsapp_target',
|
|
858
|
+
this.nullableString(data.whatsapp_target, partial),
|
|
859
|
+
);
|
|
860
|
+
assign(
|
|
861
|
+
'whatsapp_template',
|
|
862
|
+
this.nullableString(data.whatsapp_template, partial),
|
|
863
|
+
);
|
|
864
|
+
assign(
|
|
865
|
+
'whatsapp_instance',
|
|
866
|
+
this.nullableString(data.whatsapp_instance, partial),
|
|
867
|
+
);
|
|
868
|
+
assign(
|
|
869
|
+
'whatsapp_base_url',
|
|
870
|
+
this.nullableString(data.whatsapp_base_url, partial),
|
|
871
|
+
);
|
|
872
|
+
assign('whatsapp_token', this.nullableString(data.whatsapp_token, partial));
|
|
873
|
+
|
|
874
|
+
return normalized;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
private nullableString(value: any, partial = false) {
|
|
878
|
+
if (value === undefined) {
|
|
879
|
+
return partial ? undefined : null;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
if (value === null) {
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
const normalized = String(value).trim();
|
|
887
|
+
return normalized || null;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
private nullableNumber(value: any) {
|
|
891
|
+
if (value === undefined || value === null || value === '') {
|
|
892
|
+
return null;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
return Number(value);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
private buildMailConfig(profile: any) {
|
|
899
|
+
switch (profile.provider) {
|
|
900
|
+
case 'SMTP':
|
|
901
|
+
return {
|
|
902
|
+
global: true,
|
|
903
|
+
type: 'SMTP',
|
|
904
|
+
host: profile.smtp_host,
|
|
905
|
+
port: Number(profile.smtp_port || 587),
|
|
906
|
+
secure: Boolean(profile.smtp_secure),
|
|
907
|
+
username: profile.smtp_username || '',
|
|
908
|
+
password: profile.smtp_password || '',
|
|
909
|
+
};
|
|
910
|
+
case 'GMAIL':
|
|
911
|
+
return {
|
|
912
|
+
global: true,
|
|
913
|
+
type: 'GMAIL',
|
|
914
|
+
clientId: profile.gmail_client_id,
|
|
915
|
+
clientSecret: profile.gmail_client_secret,
|
|
916
|
+
refreshToken: profile.gmail_refresh_token,
|
|
917
|
+
from: profile.from_email,
|
|
918
|
+
};
|
|
919
|
+
case 'SES':
|
|
920
|
+
return {
|
|
921
|
+
global: true,
|
|
922
|
+
type: 'SES',
|
|
923
|
+
region: profile.ses_region,
|
|
924
|
+
accessKeyId: profile.ses_access_key_id,
|
|
925
|
+
secretAccessKey: profile.ses_secret_access_key,
|
|
926
|
+
from: profile.from_email,
|
|
927
|
+
};
|
|
928
|
+
default:
|
|
929
|
+
throw new BadRequestException(
|
|
930
|
+
`Invalid mail profile provider: ${profile.provider}`,
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|