@hed-hog/core 0.0.354 → 0.0.356
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/ai/ai.service.d.ts +3 -12
- package/dist/ai/ai.service.d.ts.map +1 -1
- package/dist/ai/ai.service.js +34 -125
- package/dist/ai/ai.service.js.map +1 -1
- package/dist/core.module.d.ts.map +1 -1
- package/dist/core.module.js +3 -3
- package/dist/core.module.js.map +1 -1
- package/dist/file/file.service.d.ts +1 -1
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +50 -64
- package/dist/file/file.service.js.map +1 -1
- package/dist/file/provider/provider.enum.d.ts +2 -1
- package/dist/file/provider/provider.enum.d.ts.map +1 -1
- package/dist/file/provider/provider.enum.js +1 -0
- package/dist/file/provider/provider.enum.js.map +1 -1
- package/dist/file/provider/provider.factory.d.ts.map +1 -1
- package/dist/file/provider/provider.factory.js +3 -0
- package/dist/file/provider/provider.factory.js.map +1 -1
- package/dist/file/provider/spaces.provider.d.ts +27 -0
- package/dist/file/provider/spaces.provider.d.ts.map +1 -0
- package/dist/file/provider/spaces.provider.js +218 -0
- package/dist/file/provider/spaces.provider.js.map +1 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/integration-profile/dto/create.dto.d.ts +9 -0
- package/dist/integration-profile/dto/create.dto.d.ts.map +1 -0
- package/dist/integration-profile/dto/create.dto.js +54 -0
- package/dist/integration-profile/dto/create.dto.js.map +1 -0
- package/dist/integration-profile/dto/export.dto.d.ts +5 -0
- package/dist/integration-profile/dto/export.dto.d.ts.map +1 -0
- package/dist/integration-profile/dto/export.dto.js +29 -0
- package/dist/integration-profile/dto/export.dto.js.map +1 -0
- package/dist/integration-profile/dto/import-confirm.dto.d.ts +17 -0
- package/dist/integration-profile/dto/import-confirm.dto.d.ts.map +1 -0
- package/dist/integration-profile/dto/import-confirm.dto.js +77 -0
- package/dist/integration-profile/dto/import-confirm.dto.js.map +1 -0
- package/dist/integration-profile/dto/update.dto.d.ts +6 -0
- package/dist/integration-profile/dto/update.dto.d.ts.map +1 -0
- package/dist/integration-profile/dto/update.dto.js +9 -0
- package/dist/integration-profile/dto/update.dto.js.map +1 -0
- package/dist/integration-profile/integration-profile.controller.d.ts +142 -0
- package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -0
- package/dist/integration-profile/integration-profile.controller.js +165 -0
- package/dist/integration-profile/integration-profile.controller.js.map +1 -0
- package/dist/integration-profile/integration-profile.module.d.ts +3 -0
- package/dist/integration-profile/integration-profile.module.d.ts.map +1 -0
- package/dist/integration-profile/integration-profile.module.js +29 -0
- package/dist/integration-profile/integration-profile.module.js.map +1 -0
- package/dist/integration-profile/integration-profile.service.d.ts +160 -0
- package/dist/integration-profile/integration-profile.service.d.ts.map +1 -0
- package/dist/integration-profile/integration-profile.service.js +606 -0
- package/dist/integration-profile/integration-profile.service.js.map +1 -0
- package/dist/integration-profile/integration-profile.utils.d.ts +24 -0
- package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -0
- package/dist/integration-profile/integration-profile.utils.js +147 -0
- package/dist/integration-profile/integration-profile.utils.js.map +1 -0
- package/dist/mail/mail.module.d.ts.map +1 -1
- package/dist/mail/mail.module.js +27 -19
- package/dist/mail/mail.module.js.map +1 -1
- package/dist/mail/mail.service.d.ts +3 -2
- package/dist/mail/mail.service.d.ts.map +1 -1
- package/dist/mail/mail.service.js +25 -47
- package/dist/mail/mail.service.js.map +1 -1
- package/dist/mcp/mcp.module.js +2 -2
- package/dist/mcp/mcp.module.js.map +1 -1
- package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts +3 -15
- package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts.map +1 -1
- package/dist/mcp/tools/core-mail-profile.mcp-tools.js +12 -94
- package/dist/mcp/tools/core-mail-profile.mcp-tools.js.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.js +17 -16
- package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +0 -2
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/webhook-integration/event-webhook.controller.d.ts +1 -0
- package/dist/webhook-integration/event-webhook.controller.d.ts.map +1 -1
- package/dist/webhook-integration/event-webhook.controller.js +10 -0
- package/dist/webhook-integration/event-webhook.controller.js.map +1 -1
- package/dist/webhook-integration/event-webhook.service.d.ts +2 -0
- package/dist/webhook-integration/event-webhook.service.d.ts.map +1 -1
- package/dist/webhook-integration/event-webhook.service.js +30 -0
- package/dist/webhook-integration/event-webhook.service.js.map +1 -1
- package/dist/webhook-integration/webhook-integration.controller.d.ts +1 -0
- package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.controller.js +10 -0
- package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.d.ts +2 -1
- package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.js +62 -44
- package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
- package/dist/whatsapp-profile/dto/create.dto.d.ts +11 -0
- package/dist/whatsapp-profile/dto/create.dto.d.ts.map +1 -0
- package/dist/whatsapp-profile/dto/create.dto.js +55 -0
- package/dist/whatsapp-profile/dto/create.dto.js.map +1 -0
- package/dist/whatsapp-profile/dto/update.dto.d.ts +6 -0
- package/dist/whatsapp-profile/dto/update.dto.d.ts.map +1 -0
- package/dist/whatsapp-profile/dto/update.dto.js +9 -0
- package/dist/whatsapp-profile/dto/update.dto.js.map +1 -0
- package/dist/whatsapp-profile/whatsapp-profile.controller.d.ts +54 -0
- package/dist/whatsapp-profile/whatsapp-profile.controller.d.ts.map +1 -0
- package/dist/whatsapp-profile/whatsapp-profile.controller.js +85 -0
- package/dist/whatsapp-profile/whatsapp-profile.controller.js.map +1 -0
- package/dist/whatsapp-profile/whatsapp-profile.module.d.ts +3 -0
- package/dist/whatsapp-profile/whatsapp-profile.module.d.ts.map +1 -0
- package/dist/whatsapp-profile/whatsapp-profile.module.js +26 -0
- package/dist/whatsapp-profile/whatsapp-profile.module.js.map +1 -0
- package/dist/whatsapp-profile/whatsapp-profile.service.d.ts +63 -0
- package/dist/whatsapp-profile/whatsapp-profile.service.d.ts.map +1 -0
- package/dist/whatsapp-profile/whatsapp-profile.service.js +138 -0
- package/dist/whatsapp-profile/whatsapp-profile.service.js.map +1 -0
- package/hedhog/data/file_provider.yaml +9 -1
- package/hedhog/data/integration_provider.yaml +132 -0
- package/hedhog/data/integration_type.yaml +29 -0
- package/hedhog/data/menu.yaml +15 -14
- package/hedhog/data/role.yaml +8 -0
- package/hedhog/data/route.yaml +66 -15
- package/hedhog/data/setting_group.yaml +30 -191
- package/hedhog/data/setting_subgroup.yaml +5 -13
- package/hedhog/data/whatsapp_profile.yaml +6 -0
- package/hedhog/frontend/app/account/components/connected-accounts.tsx.ejs +25 -20
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +579 -204
- package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +1662 -0
- package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +233 -108
- package/hedhog/frontend/app/mail/profiles/page.tsx.ejs +3 -822
- package/hedhog/frontend/app/users/permissions.tsx.ejs +79 -2
- package/hedhog/frontend/app/users/user-sheet.tsx.ejs +7 -2
- package/hedhog/frontend/messages/en.json +172 -0
- package/hedhog/frontend/messages/pt.json +172 -0
- package/hedhog/table/integration_profile.yaml +34 -0
- package/hedhog/table/integration_provider.yaml +23 -0
- package/hedhog/table/integration_type.yaml +19 -0
- package/hedhog/table/webhook_action.yaml +85 -85
- package/hedhog/table/whatsapp_profile.yaml +26 -0
- package/package.json +2 -2
- package/src/ai/ai.service.ts +35 -153
- package/src/core.module.ts +3 -3
- package/src/file/file.service.ts +60 -76
- package/src/file/provider/provider.enum.ts +1 -0
- package/src/file/provider/provider.factory.ts +3 -0
- package/src/file/provider/spaces.provider.ts +261 -0
- package/src/index.ts +6 -3
- package/src/integration-profile/dto/create.dto.ts +44 -0
- package/src/integration-profile/dto/export.dto.ts +15 -0
- package/src/integration-profile/dto/import-confirm.dto.ts +61 -0
- package/src/integration-profile/dto/update.dto.ts +4 -0
- package/src/integration-profile/integration-profile.controller.ts +129 -0
- package/src/integration-profile/integration-profile.module.ts +16 -0
- package/src/integration-profile/integration-profile.service.ts +788 -0
- package/src/integration-profile/integration-profile.utils.ts +156 -0
- package/src/mail/mail.module.ts +27 -18
- package/src/mail/mail.service.ts +31 -60
- package/src/mcp/mcp.module.ts +2 -2
- package/src/mcp/tools/core-mail-profile.mcp-tools.ts +10 -69
- package/src/mcp-chat/mcp-chat.service.ts +23 -20
- package/src/setting/setting.service.ts +0 -2
- package/src/webhook-integration/event-webhook.controller.ts +55 -50
- package/src/webhook-integration/event-webhook.service.ts +422 -384
- package/src/webhook-integration/webhook-integration.controller.ts +5 -0
- package/src/webhook-integration/webhook-integration.service.ts +74 -46
- package/src/whatsapp-profile/dto/create.dto.ts +36 -0
- package/src/whatsapp-profile/dto/update.dto.ts +4 -0
- package/src/whatsapp-profile/whatsapp-profile.controller.ts +51 -0
- package/src/whatsapp-profile/whatsapp-profile.module.ts +13 -0
- package/src/whatsapp-profile/whatsapp-profile.service.ts +160 -0
|
@@ -1,384 +1,422 @@
|
|
|
1
|
-
import { DeleteDTO } from '@hed-hog/api';
|
|
2
|
-
import { PaginationDTO, PaginationService } from '@hed-hog/api-pagination';
|
|
3
|
-
import { PrismaService } from '@hed-hog/api-prisma';
|
|
4
|
-
import { HttpService } from '@nestjs/axios';
|
|
5
|
-
import {
|
|
6
|
-
BadRequestException,
|
|
7
|
-
Injectable,
|
|
8
|
-
Logger,
|
|
9
|
-
NotFoundException,
|
|
10
|
-
OnModuleInit,
|
|
11
|
-
} from '@nestjs/common';
|
|
12
|
-
import { firstValueFrom } from 'rxjs';
|
|
13
|
-
import { IntegrationDeveloperApiService } from '../integration/services/integration-developer-api.service';
|
|
14
|
-
import { DomainEvent } from '../integration/types';
|
|
15
|
-
import {
|
|
16
|
-
normalizeJsonObject,
|
|
17
|
-
renderTemplate,
|
|
18
|
-
summarizePayload,
|
|
19
|
-
} from './webhook-utils';
|
|
20
|
-
|
|
21
|
-
@Injectable()
|
|
22
|
-
export class EventWebhookService implements OnModuleInit {
|
|
23
|
-
private readonly logger = new Logger(EventWebhookService.name);
|
|
24
|
-
private readonly registeredEvents = new Set<string>();
|
|
25
|
-
|
|
26
|
-
constructor(
|
|
27
|
-
private readonly prisma: PrismaService,
|
|
28
|
-
private readonly pagination: PaginationService,
|
|
29
|
-
private readonly http: HttpService,
|
|
30
|
-
private readonly integrationApi: IntegrationDeveloperApiService,
|
|
31
|
-
) {}
|
|
32
|
-
|
|
33
|
-
private get webhooks() {
|
|
34
|
-
return (this.prisma as any).event_webhook;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
private get webhookEvents() {
|
|
38
|
-
return (this.prisma as any).event_webhook_event;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
private get catalog() {
|
|
42
|
-
return (this.prisma as any).integration_event_catalog;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
private get logs() {
|
|
46
|
-
return (this.prisma as any).event_webhook_delivery_log;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async onModuleInit() {
|
|
50
|
-
await this.registerConfiguredEvents();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async list(paginationParams: PaginationDTO) {
|
|
54
|
-
const fields = ['slug', 'name', 'description', 'url', 'method', 'status'];
|
|
55
|
-
const OR = this.prisma.createInsensitiveSearch(fields, paginationParams);
|
|
56
|
-
|
|
57
|
-
return this.pagination.paginate(this.webhooks, paginationParams, {
|
|
58
|
-
where: OR.length > 0 ? { OR } : {},
|
|
59
|
-
include: {
|
|
60
|
-
event_webhook_event: {
|
|
61
|
-
include: {
|
|
62
|
-
integration_event_catalog: true,
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
orderBy: [{ updated_at: 'desc' }, { id: 'desc' }],
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async get(id: number) {
|
|
71
|
-
const record = await this.webhooks.findUnique({
|
|
72
|
-
where: { id },
|
|
73
|
-
include: {
|
|
74
|
-
event_webhook_event: {
|
|
75
|
-
include: {
|
|
76
|
-
integration_event_catalog: true,
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
if (!record) {
|
|
83
|
-
throw new NotFoundException(`Event webhook with id ${id} not found.`);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return record;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async create(data: any) {
|
|
90
|
-
await this.ensureSlugAvailable(data.slug);
|
|
91
|
-
|
|
92
|
-
const record = await this.webhooks.create({
|
|
93
|
-
data: {
|
|
94
|
-
...this.normalizeWebhook(data),
|
|
95
|
-
event_webhook_event: {
|
|
96
|
-
create: this.normalizeEventIds(data.event_ids).map((eventId) => ({
|
|
97
|
-
integration_event_catalog_id: eventId,
|
|
98
|
-
})),
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
include: {
|
|
102
|
-
event_webhook_event: {
|
|
103
|
-
include: { integration_event_catalog: true },
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
await this.registerEventsFromWebhook(record);
|
|
109
|
-
return record;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async update(id: number, data: any) {
|
|
113
|
-
const existing = await this.get(id);
|
|
114
|
-
if (data.slug && data.slug !== existing.slug) {
|
|
115
|
-
await this.ensureSlugAvailable(data.slug);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const record = await this.webhooks.update({
|
|
119
|
-
where: { id },
|
|
120
|
-
data: this.normalizeWebhook(data, true),
|
|
121
|
-
include: {
|
|
122
|
-
event_webhook_event: {
|
|
123
|
-
include: { integration_event_catalog: true },
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
if (data.event_ids !== undefined) {
|
|
129
|
-
await this.webhookEvents.deleteMany({
|
|
130
|
-
where: { event_webhook_id: id },
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
const eventIds = this.normalizeEventIds(data.event_ids);
|
|
134
|
-
for (const eventId of eventIds) {
|
|
135
|
-
await this.webhookEvents.create({
|
|
136
|
-
data: {
|
|
137
|
-
event_webhook_id: id,
|
|
138
|
-
integration_event_catalog_id: eventId,
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const refreshed = await this.get(id);
|
|
145
|
-
await this.registerEventsFromWebhook(refreshed);
|
|
146
|
-
return refreshed;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async delete(data: DeleteDTO) {
|
|
150
|
-
if (!data.ids?.length) {
|
|
151
|
-
throw new BadRequestException('You must select at least one item to delete.');
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return this.webhooks.deleteMany({
|
|
155
|
-
where: { id: { in: data.ids } },
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async
|
|
160
|
-
await this.get(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
for (
|
|
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
|
-
|
|
1
|
+
import { DeleteDTO } from '@hed-hog/api';
|
|
2
|
+
import { PaginationDTO, PaginationService } from '@hed-hog/api-pagination';
|
|
3
|
+
import { PrismaService } from '@hed-hog/api-prisma';
|
|
4
|
+
import { HttpService } from '@nestjs/axios';
|
|
5
|
+
import {
|
|
6
|
+
BadRequestException,
|
|
7
|
+
Injectable,
|
|
8
|
+
Logger,
|
|
9
|
+
NotFoundException,
|
|
10
|
+
OnModuleInit,
|
|
11
|
+
} from '@nestjs/common';
|
|
12
|
+
import { firstValueFrom } from 'rxjs';
|
|
13
|
+
import { IntegrationDeveloperApiService } from '../integration/services/integration-developer-api.service';
|
|
14
|
+
import { DomainEvent } from '../integration/types';
|
|
15
|
+
import {
|
|
16
|
+
normalizeJsonObject,
|
|
17
|
+
renderTemplate,
|
|
18
|
+
summarizePayload,
|
|
19
|
+
} from './webhook-utils';
|
|
20
|
+
|
|
21
|
+
@Injectable()
|
|
22
|
+
export class EventWebhookService implements OnModuleInit {
|
|
23
|
+
private readonly logger = new Logger(EventWebhookService.name);
|
|
24
|
+
private readonly registeredEvents = new Set<string>();
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
private readonly prisma: PrismaService,
|
|
28
|
+
private readonly pagination: PaginationService,
|
|
29
|
+
private readonly http: HttpService,
|
|
30
|
+
private readonly integrationApi: IntegrationDeveloperApiService,
|
|
31
|
+
) {}
|
|
32
|
+
|
|
33
|
+
private get webhooks() {
|
|
34
|
+
return (this.prisma as any).event_webhook;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private get webhookEvents() {
|
|
38
|
+
return (this.prisma as any).event_webhook_event;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private get catalog() {
|
|
42
|
+
return (this.prisma as any).integration_event_catalog;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private get logs() {
|
|
46
|
+
return (this.prisma as any).event_webhook_delivery_log;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async onModuleInit() {
|
|
50
|
+
await this.registerConfiguredEvents();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async list(paginationParams: PaginationDTO) {
|
|
54
|
+
const fields = ['slug', 'name', 'description', 'url', 'method', 'status'];
|
|
55
|
+
const OR = this.prisma.createInsensitiveSearch(fields, paginationParams);
|
|
56
|
+
|
|
57
|
+
return this.pagination.paginate(this.webhooks, paginationParams, {
|
|
58
|
+
where: OR.length > 0 ? { OR } : {},
|
|
59
|
+
include: {
|
|
60
|
+
event_webhook_event: {
|
|
61
|
+
include: {
|
|
62
|
+
integration_event_catalog: true,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
orderBy: [{ updated_at: 'desc' }, { id: 'desc' }],
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async get(id: number) {
|
|
71
|
+
const record = await this.webhooks.findUnique({
|
|
72
|
+
where: { id },
|
|
73
|
+
include: {
|
|
74
|
+
event_webhook_event: {
|
|
75
|
+
include: {
|
|
76
|
+
integration_event_catalog: true,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!record) {
|
|
83
|
+
throw new NotFoundException(`Event webhook with id ${id} not found.`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return record;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async create(data: any) {
|
|
90
|
+
await this.ensureSlugAvailable(data.slug);
|
|
91
|
+
|
|
92
|
+
const record = await this.webhooks.create({
|
|
93
|
+
data: {
|
|
94
|
+
...this.normalizeWebhook(data),
|
|
95
|
+
event_webhook_event: {
|
|
96
|
+
create: this.normalizeEventIds(data.event_ids).map((eventId) => ({
|
|
97
|
+
integration_event_catalog_id: eventId,
|
|
98
|
+
})),
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
include: {
|
|
102
|
+
event_webhook_event: {
|
|
103
|
+
include: { integration_event_catalog: true },
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
await this.registerEventsFromWebhook(record);
|
|
109
|
+
return record;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async update(id: number, data: any) {
|
|
113
|
+
const existing = await this.get(id);
|
|
114
|
+
if (data.slug && data.slug !== existing.slug) {
|
|
115
|
+
await this.ensureSlugAvailable(data.slug);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const record = await this.webhooks.update({
|
|
119
|
+
where: { id },
|
|
120
|
+
data: this.normalizeWebhook(data, true),
|
|
121
|
+
include: {
|
|
122
|
+
event_webhook_event: {
|
|
123
|
+
include: { integration_event_catalog: true },
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (data.event_ids !== undefined) {
|
|
129
|
+
await this.webhookEvents.deleteMany({
|
|
130
|
+
where: { event_webhook_id: id },
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const eventIds = this.normalizeEventIds(data.event_ids);
|
|
134
|
+
for (const eventId of eventIds) {
|
|
135
|
+
await this.webhookEvents.create({
|
|
136
|
+
data: {
|
|
137
|
+
event_webhook_id: id,
|
|
138
|
+
integration_event_catalog_id: eventId,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const refreshed = await this.get(id);
|
|
145
|
+
await this.registerEventsFromWebhook(refreshed);
|
|
146
|
+
return refreshed;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async delete(data: DeleteDTO) {
|
|
150
|
+
if (!data.ids?.length) {
|
|
151
|
+
throw new BadRequestException('You must select at least one item to delete.');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return this.webhooks.deleteMany({
|
|
155
|
+
where: { id: { in: data.ids } },
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async duplicate(id: number) {
|
|
160
|
+
const original = await this.get(id);
|
|
161
|
+
|
|
162
|
+
const newSlug = await this.generateUniqueSlug(original.slug);
|
|
163
|
+
const eventIds = (original.event_webhook_event ?? []).map(
|
|
164
|
+
(e: any) => e.integration_event_catalog_id,
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const record = await this.webhooks.create({
|
|
168
|
+
data: {
|
|
169
|
+
...this.normalizeWebhook({ ...original, slug: newSlug, name: `${original.name} (copy)` }),
|
|
170
|
+
event_webhook_event: {
|
|
171
|
+
create: eventIds.map((eventId: number) => ({
|
|
172
|
+
integration_event_catalog_id: eventId,
|
|
173
|
+
})),
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
include: {
|
|
177
|
+
event_webhook_event: { include: { integration_event_catalog: true } },
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
await this.registerEventsFromWebhook(record);
|
|
182
|
+
return record;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private async generateUniqueSlug(base: string): Promise<string> {
|
|
186
|
+
const candidate = `${base}-copy`;
|
|
187
|
+
const existing = await this.webhooks.findUnique({ where: { slug: candidate } });
|
|
188
|
+
if (!existing) return candidate;
|
|
189
|
+
|
|
190
|
+
for (let i = 2; ; i++) {
|
|
191
|
+
const next = `${base}-copy-${i}`;
|
|
192
|
+
const taken = await this.webhooks.findUnique({ where: { slug: next } });
|
|
193
|
+
if (!taken) return next;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async listLogs(webhookId: number, paginationParams: PaginationDTO) {
|
|
198
|
+
await this.get(webhookId);
|
|
199
|
+
return this.pagination.paginate(this.logs, paginationParams, {
|
|
200
|
+
where: { event_webhook_id: webhookId },
|
|
201
|
+
orderBy: [{ created_at: 'desc' }, { id: 'desc' }],
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async handleEvent(event: DomainEvent) {
|
|
206
|
+
const webhooks = await this.findActiveWebhooksForEvent(event.eventName);
|
|
207
|
+
|
|
208
|
+
for (const webhook of webhooks) {
|
|
209
|
+
await this.deliverWebhook(webhook, event);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async registerConfiguredEvents() {
|
|
214
|
+
const rows = await this.catalog.findMany({
|
|
215
|
+
where: {
|
|
216
|
+
status: 'active',
|
|
217
|
+
event_webhook_event: {
|
|
218
|
+
some: {
|
|
219
|
+
event_webhook: {
|
|
220
|
+
status: 'active',
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
select: { slug: true },
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
for (const row of rows) {
|
|
229
|
+
this.registerEvent(row.slug);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private async findActiveWebhooksForEvent(eventName: string) {
|
|
234
|
+
return this.webhooks.findMany({
|
|
235
|
+
where: {
|
|
236
|
+
status: 'active',
|
|
237
|
+
event_webhook_event: {
|
|
238
|
+
some: {
|
|
239
|
+
integration_event_catalog: {
|
|
240
|
+
slug: eventName,
|
|
241
|
+
status: 'active',
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
include: {
|
|
247
|
+
event_webhook_event: {
|
|
248
|
+
include: {
|
|
249
|
+
integration_event_catalog: true,
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
orderBy: [{ priority: 'desc' }, { id: 'asc' }],
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private async deliverWebhook(webhook: any, event: DomainEvent) {
|
|
258
|
+
const startedAt = Date.now();
|
|
259
|
+
let attemptCount = 0;
|
|
260
|
+
let lastError: any = null;
|
|
261
|
+
let httpStatus: number | null = null;
|
|
262
|
+
let responseSummary: any = null;
|
|
263
|
+
|
|
264
|
+
const context = {
|
|
265
|
+
...event,
|
|
266
|
+
timestamp: event.timestamp?.toISOString?.() ?? event.timestamp,
|
|
267
|
+
outboxEventId: (event as any).outboxEventId ?? null,
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const url = renderTemplate(webhook.url, context) as string;
|
|
271
|
+
const method = webhook.method || 'POST';
|
|
272
|
+
const headers = renderTemplate(webhook.headers || {}, context) as Record<string, string>;
|
|
273
|
+
const query = renderTemplate(webhook.query || {}, context) as Record<string, string>;
|
|
274
|
+
const payload = renderTemplate(webhook.payload ?? event.payload ?? {}, context);
|
|
275
|
+
const maxAttempts = Math.max(1, Number(webhook.retry_count || 0) + 1);
|
|
276
|
+
|
|
277
|
+
for (attemptCount = 1; attemptCount <= maxAttempts; attemptCount++) {
|
|
278
|
+
try {
|
|
279
|
+
const result = await firstValueFrom(
|
|
280
|
+
this.http.request({
|
|
281
|
+
url,
|
|
282
|
+
method,
|
|
283
|
+
headers,
|
|
284
|
+
params: query,
|
|
285
|
+
data: ['GET', 'DELETE'].includes(method) ? undefined : payload,
|
|
286
|
+
timeout: Number(webhook.timeout_ms || 10000),
|
|
287
|
+
}),
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
httpStatus = result.status;
|
|
291
|
+
responseSummary = summarizePayload(result.data);
|
|
292
|
+
lastError = null;
|
|
293
|
+
break;
|
|
294
|
+
} catch (error: any) {
|
|
295
|
+
lastError = error;
|
|
296
|
+
httpStatus = error?.response?.status ?? null;
|
|
297
|
+
responseSummary = summarizePayload(error?.response?.data);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const success = !lastError;
|
|
302
|
+
await this.logs.create({
|
|
303
|
+
data: {
|
|
304
|
+
event_webhook_id: webhook.id,
|
|
305
|
+
integration_event_catalog_id:
|
|
306
|
+
webhook.event_webhook_event?.[0]?.integration_event_catalog?.id ?? null,
|
|
307
|
+
outbox_event_id: Number((event as any).outboxEventId || 0) || null,
|
|
308
|
+
event_name: event.eventName,
|
|
309
|
+
method,
|
|
310
|
+
url,
|
|
311
|
+
status: success ? 'success' : 'failed',
|
|
312
|
+
http_status: httpStatus,
|
|
313
|
+
duration_ms: Date.now() - startedAt,
|
|
314
|
+
attempt_count: attemptCount,
|
|
315
|
+
error_message: lastError
|
|
316
|
+
? lastError instanceof Error
|
|
317
|
+
? lastError.message
|
|
318
|
+
: String(lastError)
|
|
319
|
+
: null,
|
|
320
|
+
request_summary: summarizePayload({
|
|
321
|
+
headers,
|
|
322
|
+
query,
|
|
323
|
+
payload,
|
|
324
|
+
}),
|
|
325
|
+
response_summary: responseSummary,
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
if (!success) {
|
|
330
|
+
throw lastError;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private async registerEventsFromWebhook(webhook: any) {
|
|
335
|
+
const eventNames =
|
|
336
|
+
webhook.event_webhook_event
|
|
337
|
+
?.map((row: any) => row.integration_event_catalog?.slug)
|
|
338
|
+
.filter(Boolean) || [];
|
|
339
|
+
|
|
340
|
+
for (const eventName of eventNames) {
|
|
341
|
+
this.registerEvent(eventName);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private registerEvent(eventName: string) {
|
|
346
|
+
if (this.registeredEvents.has(eventName)) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
this.integrationApi.subscribe({
|
|
351
|
+
eventName,
|
|
352
|
+
consumerName: `core.event-webhook.${eventName}`,
|
|
353
|
+
priority: -100,
|
|
354
|
+
handler: async (event) => {
|
|
355
|
+
await this.handleEvent(event);
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
this.registeredEvents.add(eventName);
|
|
360
|
+
this.logger.log(`Registered event webhook dispatcher for ${eventName}`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private async ensureSlugAvailable(slug: string) {
|
|
364
|
+
const existing = await this.webhooks.findUnique({
|
|
365
|
+
where: { slug: String(slug || '').trim() },
|
|
366
|
+
select: { id: true },
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
if (existing) {
|
|
370
|
+
throw new BadRequestException(`Event webhook "${slug}" already exists.`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private normalizeWebhook(data: any, partial = false) {
|
|
375
|
+
const normalized: Record<string, any> = {};
|
|
376
|
+
const assign = (key: string, value: any) => {
|
|
377
|
+
if (!partial || value !== undefined) {
|
|
378
|
+
normalized[key] = value;
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
assign('slug', String(data.slug ?? '').trim());
|
|
383
|
+
assign('name', String(data.name ?? '').trim());
|
|
384
|
+
assign('description', this.nullableString(data.description, partial));
|
|
385
|
+
assign('status', data.status === 'inactive' ? 'inactive' : 'active');
|
|
386
|
+
assign('priority', Number(data.priority ?? 0));
|
|
387
|
+
assign('url', String(data.url ?? '').trim());
|
|
388
|
+
assign('method', String(data.method || 'POST').toUpperCase());
|
|
389
|
+
assign('headers', normalizeJsonObject(data.headers));
|
|
390
|
+
assign('query', normalizeJsonObject(data.query));
|
|
391
|
+
assign('payload', normalizeJsonObject(data.payload));
|
|
392
|
+
assign('timeout_ms', Number(data.timeout_ms || 10000));
|
|
393
|
+
assign('retry_count', Number(data.retry_count || 0));
|
|
394
|
+
|
|
395
|
+
return normalized;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private normalizeEventIds(value: any) {
|
|
399
|
+
if (!value) {
|
|
400
|
+
return [];
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const values = Array.isArray(value) ? value : String(value).split(',');
|
|
404
|
+
return values
|
|
405
|
+
.map((item) => Number(item))
|
|
406
|
+
.filter((item) => Number.isFinite(item) && item > 0);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
private nullableString(value: any, partial = false) {
|
|
410
|
+
if (value === undefined) {
|
|
411
|
+
return partial ? undefined : null;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (value === null) {
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const normalized = String(value).trim();
|
|
419
|
+
return normalized || null;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|