@hed-hog/core 0.0.355 → 0.0.358

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (287) hide show
  1. package/dist/ai/ai.service.d.ts +3 -12
  2. package/dist/ai/ai.service.d.ts.map +1 -1
  3. package/dist/ai/ai.service.js +34 -125
  4. package/dist/ai/ai.service.js.map +1 -1
  5. package/dist/auth/guards/auth.guard.d.ts +6 -1
  6. package/dist/auth/guards/auth.guard.d.ts.map +1 -1
  7. package/dist/auth/guards/auth.guard.js +33 -2
  8. package/dist/auth/guards/auth.guard.js.map +1 -1
  9. package/dist/core.module.d.ts.map +1 -1
  10. package/dist/core.module.js +3 -3
  11. package/dist/core.module.js.map +1 -1
  12. package/dist/file/file.controller.d.ts +4 -2
  13. package/dist/file/file.controller.d.ts.map +1 -1
  14. package/dist/file/file.controller.js +44 -38
  15. package/dist/file/file.controller.js.map +1 -1
  16. package/dist/file/file.service.d.ts +2 -2
  17. package/dist/file/file.service.d.ts.map +1 -1
  18. package/dist/file/file.service.js +58 -89
  19. package/dist/file/file.service.js.map +1 -1
  20. package/dist/file/provider/azure.provider.d.ts.map +1 -1
  21. package/dist/file/provider/azure.provider.js +10 -15
  22. package/dist/file/provider/azure.provider.js.map +1 -1
  23. package/dist/file/provider/gcs.provider.d.ts.map +1 -1
  24. package/dist/file/provider/gcs.provider.js +8 -9
  25. package/dist/file/provider/gcs.provider.js.map +1 -1
  26. package/dist/file/provider/local.provider.d.ts.map +1 -1
  27. package/dist/file/provider/local.provider.js +11 -10
  28. package/dist/file/provider/local.provider.js.map +1 -1
  29. package/dist/file/provider/provider.enum.d.ts +2 -1
  30. package/dist/file/provider/provider.enum.d.ts.map +1 -1
  31. package/dist/file/provider/provider.enum.js +1 -0
  32. package/dist/file/provider/provider.enum.js.map +1 -1
  33. package/dist/file/provider/provider.factory.d.ts +1 -1
  34. package/dist/file/provider/provider.factory.d.ts.map +1 -1
  35. package/dist/file/provider/provider.factory.js +18 -5
  36. package/dist/file/provider/provider.factory.js.map +1 -1
  37. package/dist/file/provider/s3.provider.d.ts.map +1 -1
  38. package/dist/file/provider/s3.provider.js +4 -20
  39. package/dist/file/provider/s3.provider.js.map +1 -1
  40. package/dist/file/provider/spaces.provider.d.ts +27 -0
  41. package/dist/file/provider/spaces.provider.d.ts.map +1 -0
  42. package/dist/file/provider/spaces.provider.js +203 -0
  43. package/dist/file/provider/spaces.provider.js.map +1 -0
  44. package/dist/index.d.ts +4 -3
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +4 -3
  47. package/dist/index.js.map +1 -1
  48. package/dist/integration-profile/dto/create.dto.d.ts +9 -0
  49. package/dist/integration-profile/dto/create.dto.d.ts.map +1 -0
  50. package/dist/integration-profile/dto/create.dto.js +54 -0
  51. package/dist/integration-profile/dto/create.dto.js.map +1 -0
  52. package/dist/integration-profile/dto/export.dto.d.ts +5 -0
  53. package/dist/integration-profile/dto/export.dto.d.ts.map +1 -0
  54. package/dist/integration-profile/dto/export.dto.js +29 -0
  55. package/dist/integration-profile/dto/export.dto.js.map +1 -0
  56. package/dist/integration-profile/dto/import-confirm.dto.d.ts +17 -0
  57. package/dist/integration-profile/dto/import-confirm.dto.d.ts.map +1 -0
  58. package/dist/integration-profile/dto/import-confirm.dto.js +77 -0
  59. package/dist/integration-profile/dto/import-confirm.dto.js.map +1 -0
  60. package/dist/integration-profile/dto/update.dto.d.ts.map +1 -0
  61. package/dist/integration-profile/dto/update.dto.js.map +1 -0
  62. package/dist/integration-profile/integration-profile.controller.d.ts +142 -0
  63. package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -0
  64. package/dist/integration-profile/integration-profile.controller.js +166 -0
  65. package/dist/integration-profile/integration-profile.controller.js.map +1 -0
  66. package/dist/integration-profile/integration-profile.module.d.ts +3 -0
  67. package/dist/integration-profile/integration-profile.module.d.ts.map +1 -0
  68. package/dist/{mail-profile/mail-profile.module.js → integration-profile/integration-profile.module.js} +11 -13
  69. package/dist/integration-profile/integration-profile.module.js.map +1 -0
  70. package/dist/integration-profile/integration-profile.service.d.ts +160 -0
  71. package/dist/integration-profile/integration-profile.service.d.ts.map +1 -0
  72. package/dist/integration-profile/integration-profile.service.js +609 -0
  73. package/dist/integration-profile/integration-profile.service.js.map +1 -0
  74. package/dist/integration-profile/integration-profile.utils.d.ts +30 -0
  75. package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -0
  76. package/dist/integration-profile/integration-profile.utils.js +156 -0
  77. package/dist/integration-profile/integration-profile.utils.js.map +1 -0
  78. package/dist/mail/mail.module.d.ts.map +1 -1
  79. package/dist/mail/mail.module.js +36 -23
  80. package/dist/mail/mail.module.js.map +1 -1
  81. package/dist/mail/mail.service.d.ts +4 -2
  82. package/dist/mail/mail.service.d.ts.map +1 -1
  83. package/dist/mail/mail.service.js +33 -51
  84. package/dist/mail/mail.service.js.map +1 -1
  85. package/dist/mcp/guards/mcp-auth.guard.js +1 -1
  86. package/dist/mcp/guards/mcp-auth.guard.js.map +1 -1
  87. package/dist/mcp/mcp-api-key.controller.d.ts +2 -0
  88. package/dist/mcp/mcp-api-key.controller.d.ts.map +1 -1
  89. package/dist/mcp/mcp-api-key.controller.js +1 -1
  90. package/dist/mcp/mcp-api-key.controller.js.map +1 -1
  91. package/dist/mcp/mcp-api-key.service.d.ts +2 -1
  92. package/dist/mcp/mcp-api-key.service.d.ts.map +1 -1
  93. package/dist/mcp/mcp-api-key.service.js +6 -2
  94. package/dist/mcp/mcp-api-key.service.js.map +1 -1
  95. package/dist/mcp/mcp.module.d.ts.map +1 -1
  96. package/dist/mcp/mcp.module.js +3 -5
  97. package/dist/mcp/mcp.module.js.map +1 -1
  98. package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
  99. package/dist/mcp-chat/mcp-chat.service.js +17 -16
  100. package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
  101. package/dist/oauth/interfaces/OAuthProvider.d.ts +1 -1
  102. package/dist/oauth/interfaces/OAuthProvider.d.ts.map +1 -1
  103. package/dist/oauth/oauth.controller.d.ts +4 -1
  104. package/dist/oauth/oauth.controller.d.ts.map +1 -1
  105. package/dist/oauth/oauth.controller.js +31 -5
  106. package/dist/oauth/oauth.controller.js.map +1 -1
  107. package/dist/oauth/oauth.service.d.ts +12 -1
  108. package/dist/oauth/oauth.service.d.ts.map +1 -1
  109. package/dist/oauth/oauth.service.js +125 -23
  110. package/dist/oauth/oauth.service.js.map +1 -1
  111. package/dist/oauth/providers/abstract.provider.d.ts +1 -1
  112. package/dist/oauth/providers/abstract.provider.d.ts.map +1 -1
  113. package/dist/oauth/providers/facebook.provider.d.ts +5 -2
  114. package/dist/oauth/providers/facebook.provider.d.ts.map +1 -1
  115. package/dist/oauth/providers/facebook.provider.js +34 -19
  116. package/dist/oauth/providers/facebook.provider.js.map +1 -1
  117. package/dist/oauth/providers/github.provider.d.ts +6 -2
  118. package/dist/oauth/providers/github.provider.d.ts.map +1 -1
  119. package/dist/oauth/providers/github.provider.js +43 -20
  120. package/dist/oauth/providers/github.provider.js.map +1 -1
  121. package/dist/oauth/providers/google.provider.d.ts +5 -2
  122. package/dist/oauth/providers/google.provider.d.ts.map +1 -1
  123. package/dist/oauth/providers/google.provider.js +34 -19
  124. package/dist/oauth/providers/google.provider.js.map +1 -1
  125. package/dist/oauth/providers/microsoft-entra-id.provider.d.ts +5 -2
  126. package/dist/oauth/providers/microsoft-entra-id.provider.d.ts.map +1 -1
  127. package/dist/oauth/providers/microsoft-entra-id.provider.js +43 -40
  128. package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
  129. package/dist/oauth/providers/microsoft.provider.d.ts +5 -2
  130. package/dist/oauth/providers/microsoft.provider.d.ts.map +1 -1
  131. package/dist/oauth/providers/microsoft.provider.js +34 -19
  132. package/dist/oauth/providers/microsoft.provider.js.map +1 -1
  133. package/dist/profile/profile.controller.js +1 -1
  134. package/dist/profile/profile.controller.js.map +1 -1
  135. package/dist/security/security.service.d.ts +2 -0
  136. package/dist/security/security.service.d.ts.map +1 -1
  137. package/dist/security/security.service.js +21 -0
  138. package/dist/security/security.service.js.map +1 -1
  139. package/dist/setting/setting.service.d.ts +26 -1
  140. package/dist/setting/setting.service.d.ts.map +1 -1
  141. package/dist/setting/setting.service.js +240 -7
  142. package/dist/setting/setting.service.js.map +1 -1
  143. package/dist/user/user.controller.d.ts +2 -1
  144. package/dist/user/user.controller.d.ts.map +1 -1
  145. package/dist/user/user.controller.js +5 -4
  146. package/dist/user/user.controller.js.map +1 -1
  147. package/dist/user/user.service.d.ts +4 -2
  148. package/dist/user/user.service.d.ts.map +1 -1
  149. package/dist/user/user.service.js +19 -4
  150. package/dist/user/user.service.js.map +1 -1
  151. package/dist/webhook-integration/dto/webhook-action.dto.d.ts +17 -2
  152. package/dist/webhook-integration/dto/webhook-action.dto.d.ts.map +1 -1
  153. package/dist/webhook-integration/dto/webhook-action.dto.js +93 -2
  154. package/dist/webhook-integration/dto/webhook-action.dto.js.map +1 -1
  155. package/dist/webhook-integration/event-webhook.controller.d.ts +1 -0
  156. package/dist/webhook-integration/event-webhook.controller.d.ts.map +1 -1
  157. package/dist/webhook-integration/event-webhook.controller.js +10 -0
  158. package/dist/webhook-integration/event-webhook.controller.js.map +1 -1
  159. package/dist/webhook-integration/event-webhook.service.d.ts +2 -0
  160. package/dist/webhook-integration/event-webhook.service.d.ts.map +1 -1
  161. package/dist/webhook-integration/event-webhook.service.js +30 -0
  162. package/dist/webhook-integration/event-webhook.service.js.map +1 -1
  163. package/dist/webhook-integration/webhook-command.registry.d.ts +15 -0
  164. package/dist/webhook-integration/webhook-command.registry.d.ts.map +1 -0
  165. package/dist/webhook-integration/webhook-command.registry.js +36 -0
  166. package/dist/webhook-integration/webhook-command.registry.js.map +1 -0
  167. package/dist/webhook-integration/webhook-integration.controller.d.ts +20 -0
  168. package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
  169. package/dist/webhook-integration/webhook-integration.controller.js +48 -0
  170. package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
  171. package/dist/webhook-integration/webhook-integration.module.d.ts.map +1 -1
  172. package/dist/webhook-integration/webhook-integration.module.js +5 -0
  173. package/dist/webhook-integration/webhook-integration.module.js.map +1 -1
  174. package/dist/webhook-integration/webhook-integration.service.d.ts +27 -2
  175. package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
  176. package/dist/webhook-integration/webhook-integration.service.js +377 -50
  177. package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
  178. package/hedhog/data/file_provider.yaml +9 -1
  179. package/hedhog/data/integration_provider.yaml +148 -0
  180. package/hedhog/data/integration_type.yaml +29 -0
  181. package/hedhog/data/menu.yaml +6 -21
  182. package/hedhog/data/role.yaml +8 -0
  183. package/hedhog/data/route.yaml +73 -373
  184. package/hedhog/data/setting_group.yaml +209 -365
  185. package/hedhog/data/setting_subgroup.yaml +21 -13
  186. package/hedhog/frontend/app/account/components/connected-accounts.tsx.ejs +25 -20
  187. package/hedhog/frontend/app/account/components/tokens.tsx.ejs +421 -0
  188. package/hedhog/frontend/app/account/layout.tsx.ejs +6 -3
  189. package/hedhog/frontend/app/account/mcp-tokens/page.tsx.ejs +2 -2
  190. package/hedhog/frontend/app/account/tokens/page.tsx.ejs +5 -0
  191. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +938 -225
  192. package/hedhog/frontend/app/configurations/layout.tsx.ejs +1 -1
  193. package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +3 -1
  194. package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +1830 -0
  195. package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +829 -128
  196. package/hedhog/frontend/app/mail/profiles/page.tsx.ejs +1 -820
  197. package/hedhog/frontend/app/setting-list/page.tsx.ejs +229 -395
  198. package/hedhog/frontend/app/users/permissions.tsx.ejs +79 -2
  199. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +7 -2
  200. package/hedhog/frontend/messages/en.json +239 -3
  201. package/hedhog/frontend/messages/pt.json +239 -3
  202. package/hedhog/table/file.yaml +1 -1
  203. package/hedhog/table/integration_profile.yaml +34 -0
  204. package/hedhog/table/integration_provider.yaml +23 -0
  205. package/hedhog/table/integration_type.yaml +19 -0
  206. package/hedhog/table/mcp_api_key.yaml +4 -0
  207. package/hedhog/table/webhook_action.yaml +141 -85
  208. package/package.json +4 -3
  209. package/src/ai/ai.service.ts +35 -153
  210. package/src/auth/guards/auth.guard.ts +32 -0
  211. package/src/core.module.ts +3 -3
  212. package/src/file/file.controller.ts +51 -42
  213. package/src/file/file.service.ts +67 -104
  214. package/src/file/provider/azure.provider.ts +14 -28
  215. package/src/file/provider/gcs.provider.ts +9 -9
  216. package/src/file/provider/local.provider.ts +10 -13
  217. package/src/file/provider/provider.enum.ts +1 -0
  218. package/src/file/provider/provider.factory.ts +21 -6
  219. package/src/file/provider/s3.provider.ts +4 -20
  220. package/src/file/provider/spaces.provider.ts +246 -0
  221. package/src/index.ts +6 -3
  222. package/src/integration-profile/dto/create.dto.ts +44 -0
  223. package/src/integration-profile/dto/export.dto.ts +15 -0
  224. package/src/integration-profile/dto/import-confirm.dto.ts +61 -0
  225. package/src/integration-profile/integration-profile.controller.ts +131 -0
  226. package/src/integration-profile/integration-profile.module.ts +16 -0
  227. package/src/integration-profile/integration-profile.service.ts +793 -0
  228. package/src/integration-profile/integration-profile.utils.ts +173 -0
  229. package/src/mail/mail.module.ts +41 -22
  230. package/src/mail/mail.service.ts +40 -64
  231. package/src/mcp/guards/mcp-auth.guard.ts +1 -1
  232. package/src/mcp/mcp-api-key.controller.ts +2 -2
  233. package/src/mcp/mcp-api-key.service.ts +6 -2
  234. package/src/mcp/mcp.module.ts +3 -5
  235. package/src/mcp-chat/mcp-chat.service.ts +23 -20
  236. package/src/oauth/interfaces/OAuthProvider.ts +1 -1
  237. package/src/oauth/oauth.controller.ts +30 -2
  238. package/src/oauth/oauth.service.ts +194 -35
  239. package/src/oauth/providers/abstract.provider.ts +1 -1
  240. package/src/oauth/providers/facebook.provider.ts +36 -20
  241. package/src/oauth/providers/github.provider.ts +46 -23
  242. package/src/oauth/providers/google.provider.ts +36 -20
  243. package/src/oauth/providers/microsoft-entra-id.provider.ts +42 -46
  244. package/src/oauth/providers/microsoft.provider.ts +36 -21
  245. package/src/profile/profile.controller.ts +2 -2
  246. package/src/security/security.service.ts +34 -1
  247. package/src/setting/setting.service.ts +307 -9
  248. package/src/user/user.controller.ts +4 -1
  249. package/src/user/user.service.ts +20 -2
  250. package/src/webhook-integration/dto/webhook-action.dto.ts +85 -4
  251. package/src/webhook-integration/event-webhook.controller.ts +55 -50
  252. package/src/webhook-integration/event-webhook.service.ts +422 -384
  253. package/src/webhook-integration/webhook-command.registry.ts +42 -0
  254. package/src/webhook-integration/webhook-integration.controller.ts +25 -0
  255. package/src/webhook-integration/webhook-integration.module.ts +50 -45
  256. package/src/webhook-integration/webhook-integration.service.ts +444 -53
  257. package/dist/mail-profile/dto/create.dto.d.ts +0 -23
  258. package/dist/mail-profile/dto/create.dto.d.ts.map +0 -1
  259. package/dist/mail-profile/dto/create.dto.js +0 -139
  260. package/dist/mail-profile/dto/create.dto.js.map +0 -1
  261. package/dist/mail-profile/dto/update.dto.d.ts.map +0 -1
  262. package/dist/mail-profile/dto/update.dto.js.map +0 -1
  263. package/dist/mail-profile/mail-profile.controller.d.ts +0 -94
  264. package/dist/mail-profile/mail-profile.controller.d.ts.map +0 -1
  265. package/dist/mail-profile/mail-profile.controller.js +0 -95
  266. package/dist/mail-profile/mail-profile.controller.js.map +0 -1
  267. package/dist/mail-profile/mail-profile.module.d.ts +0 -3
  268. package/dist/mail-profile/mail-profile.module.d.ts.map +0 -1
  269. package/dist/mail-profile/mail-profile.module.js.map +0 -1
  270. package/dist/mail-profile/mail-profile.service.d.ts +0 -109
  271. package/dist/mail-profile/mail-profile.service.d.ts.map +0 -1
  272. package/dist/mail-profile/mail-profile.service.js +0 -278
  273. package/dist/mail-profile/mail-profile.service.js.map +0 -1
  274. package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts +0 -32
  275. package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts.map +0 -1
  276. package/dist/mcp/tools/core-mail-profile.mcp-tools.js +0 -178
  277. package/dist/mcp/tools/core-mail-profile.mcp-tools.js.map +0 -1
  278. package/hedhog/data/mail_profile.yaml +0 -18
  279. package/hedhog/table/mail_profile.yaml +0 -75
  280. package/src/mail-profile/dto/create.dto.ts +0 -145
  281. package/src/mail-profile/mail-profile.controller.ts +0 -56
  282. package/src/mail-profile/mail-profile.module.ts +0 -18
  283. package/src/mail-profile/mail-profile.service.ts +0 -308
  284. package/src/mcp/tools/core-mail-profile.mcp-tools.ts +0 -128
  285. /package/dist/{mail-profile → integration-profile}/dto/update.dto.d.ts +0 -0
  286. /package/dist/{mail-profile → integration-profile}/dto/update.dto.js +0 -0
  287. /package/src/{mail-profile → integration-profile}/dto/update.dto.ts +0 -0
@@ -0,0 +1,793 @@
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 {
5
+ BadRequestException,
6
+ Inject,
7
+ Injectable,
8
+ NotFoundException,
9
+ forwardRef,
10
+ } from '@nestjs/common';
11
+ import * as pako from 'pako';
12
+ import { buildMailConfigFromIntegration } from './integration-profile.utils';
13
+ import { CreateDTO } from './dto/create.dto';
14
+ import {
15
+ ConflictResolutionDTO,
16
+ ImportProfileItemDTO,
17
+ } from './dto/import-confirm.dto';
18
+ import { UpdateDTO } from './dto/update.dto';
19
+
20
+ type IntegrationProfileExportItem = {
21
+ slug: string;
22
+ name: string;
23
+ type_slug: string;
24
+ provider_slug: string;
25
+ config: Record<string, unknown> | null;
26
+ is_active: boolean;
27
+ };
28
+
29
+ type IntegrationProfileImportEnvelope = {
30
+ type: 'integration-profile';
31
+ version: 1;
32
+ generated_at: string;
33
+ include_secrets: boolean;
34
+ data: IntegrationProfileExportItem[];
35
+ };
36
+
37
+ type ValidatedImportItem = {
38
+ index: number;
39
+ slug: string;
40
+ name: string;
41
+ type_slug: string;
42
+ provider_slug: string;
43
+ config: Record<string, unknown> | null;
44
+ is_active: boolean;
45
+ has_conflict: boolean;
46
+ };
47
+
48
+ type InvalidImportItem = {
49
+ index: number;
50
+ slug?: string;
51
+ errors: string[];
52
+ };
53
+
54
+ @Injectable()
55
+ export class IntegrationProfileService {
56
+ constructor(
57
+ @Inject(forwardRef(() => PrismaService))
58
+ private readonly prismaService: PrismaService,
59
+ @Inject(forwardRef(() => PaginationService))
60
+ private readonly paginationService: PaginationService,
61
+ ) {}
62
+
63
+ async listTypes() {
64
+ return this.prismaService.integration_type.findMany({
65
+ orderBy: { id: 'asc' },
66
+ select: {
67
+ id: true,
68
+ slug: true,
69
+ icon: true,
70
+ integration_type_locale: {
71
+ select: { name: true, locale: { select: { code: true } } },
72
+ },
73
+ },
74
+ });
75
+ }
76
+
77
+ async listProviders(typeId?: number, typeSlug?: string) {
78
+ const normalizedTypeSlug = String(typeSlug ?? '').trim();
79
+
80
+ return this.prismaService.integration_provider.findMany({
81
+ where: {
82
+ ...(typeId ? { type_id: typeId } : {}),
83
+ ...(normalizedTypeSlug
84
+ ? { integration_type: { slug: normalizedTypeSlug } }
85
+ : {}),
86
+ },
87
+ orderBy: { id: 'asc' },
88
+ select: {
89
+ id: true,
90
+ slug: true,
91
+ type_id: true,
92
+ integration_provider_locale: {
93
+ select: { name: true, locale: { select: { code: true } } },
94
+ },
95
+ },
96
+ });
97
+ }
98
+
99
+ async list(
100
+ paginationParams: PaginationDTO,
101
+ typeId?: number,
102
+ typeSlug?: string,
103
+ providerSlug?: string,
104
+ ) {
105
+ const fields = ['slug', 'name'];
106
+ const OR = this.prismaService.createInsensitiveSearch(fields, paginationParams);
107
+ const normalizedTypeSlug = String(typeSlug ?? '').trim();
108
+ const normalizedProviderSlug = String(providerSlug ?? '').trim();
109
+
110
+ const result = await this.paginationService.paginate(
111
+ this.prismaService.integration_profile,
112
+ paginationParams,
113
+ {
114
+ where: {
115
+ ...(OR.length > 0 ? { OR } : {}),
116
+ ...(typeId ? { type_id: typeId } : {}),
117
+ ...(normalizedTypeSlug
118
+ ? { integration_type: { slug: normalizedTypeSlug } }
119
+ : {}),
120
+ ...(normalizedProviderSlug
121
+ ? { integration_provider: { slug: normalizedProviderSlug } }
122
+ : {}),
123
+ },
124
+ select: {
125
+ id: true,
126
+ slug: true,
127
+ name: true,
128
+ config: true,
129
+ type_id: true,
130
+ provider_id: true,
131
+ is_active: true,
132
+ created_at: true,
133
+ updated_at: true,
134
+ integration_type: {
135
+ select: {
136
+ slug: true,
137
+ icon: true,
138
+ integration_type_locale: {
139
+ select: { name: true, locale: { select: { code: true } } },
140
+ },
141
+ },
142
+ },
143
+ integration_provider: {
144
+ select: {
145
+ slug: true,
146
+ integration_provider_locale: {
147
+ select: { name: true, locale: { select: { code: true } } },
148
+ },
149
+ },
150
+ },
151
+ },
152
+ orderBy: [{ updated_at: 'desc' }, { id: 'desc' }],
153
+ },
154
+ );
155
+
156
+ const items = Array.isArray(result.data) ? result.data : [];
157
+ if (items.length > 0) {
158
+ const ids = items.map((p: any) => p.id as number);
159
+ const counts = await this.prismaService.$queryRaw<
160
+ { id: number; uses_count: bigint }[]
161
+ >`
162
+ SELECT
163
+ ip.id,
164
+ (
165
+ COALESCE((SELECT COUNT(*)::int FROM campaign WHERE integration_profile_id = ip.id), 0) +
166
+ COALESCE((SELECT COUNT(*)::int FROM webhook_action WHERE integration_profile_id = ip.id), 0)
167
+ ) AS uses_count
168
+ FROM integration_profile ip
169
+ WHERE ip.id = ANY(${ids}::int[])
170
+ `;
171
+
172
+ const countMap = new Map(
173
+ counts.map((r) => [r.id, Number(r.uses_count)]),
174
+ );
175
+
176
+ result.data = items.map((p: any) => ({
177
+ ...p,
178
+ uses_count: countMap.get(p.id) ?? 0,
179
+ }));
180
+ }
181
+
182
+ return result;
183
+ }
184
+
185
+ async get(id: number) {
186
+ const profile = await this.prismaService.integration_profile.findUnique({
187
+ where: { id },
188
+ include: {
189
+ integration_type: {
190
+ select: {
191
+ slug: true,
192
+ icon: true,
193
+ integration_type_locale: {
194
+ select: { name: true, locale: { select: { code: true } } },
195
+ },
196
+ },
197
+ },
198
+ integration_provider: {
199
+ select: {
200
+ slug: true,
201
+ integration_provider_locale: {
202
+ select: { name: true, locale: { select: { code: true } } },
203
+ },
204
+ },
205
+ },
206
+ },
207
+ });
208
+
209
+ if (!profile) {
210
+ throw new NotFoundException(
211
+ `Integration profile with id ${id} not found.`,
212
+ );
213
+ }
214
+
215
+ return profile;
216
+ }
217
+
218
+ async create(data: CreateDTO) {
219
+ await this.ensureSlugIsAvailable(data.slug);
220
+ await this.validateTypeAndProvider(data.type_id, data.provider_id);
221
+
222
+ return this.prismaService.integration_profile.create({
223
+ data: {
224
+ slug: data.slug.trim(),
225
+ name: data.name.trim(),
226
+ type_id: data.type_id,
227
+ provider_id: data.provider_id,
228
+ config: (data.config as object) ?? null,
229
+ is_active: data.is_active ?? true,
230
+ },
231
+ });
232
+ }
233
+
234
+ async update({ id, data }: { id: number; data: UpdateDTO }) {
235
+ const existing = await this.prismaService.integration_profile.findUnique({
236
+ where: { id },
237
+ });
238
+
239
+ if (!existing) {
240
+ throw new NotFoundException(
241
+ `Integration profile with id ${id} not found.`,
242
+ );
243
+ }
244
+
245
+ if (data.slug && data.slug !== existing.slug) {
246
+ await this.ensureSlugIsAvailable(data.slug);
247
+ }
248
+
249
+ const type_id = data.type_id ?? existing.type_id;
250
+ const provider_id = data.provider_id ?? existing.provider_id;
251
+ await this.validateTypeAndProvider(type_id, provider_id);
252
+
253
+ return this.prismaService.integration_profile.update({
254
+ where: { id },
255
+ data: {
256
+ slug: data.slug?.trim() ?? existing.slug,
257
+ name: data.name?.trim() ?? existing.name,
258
+ type_id,
259
+ provider_id,
260
+ config:
261
+ data.config !== undefined
262
+ ? ((data.config as object) ?? null)
263
+ : existing.config,
264
+ is_active: data.is_active ?? existing.is_active,
265
+ },
266
+ });
267
+ }
268
+
269
+ async delete({ ids }: DeleteDTO): Promise<{ count: number }> {
270
+ if (!ids?.length) {
271
+ throw new BadRequestException(
272
+ 'You must select at least one item to delete.',
273
+ );
274
+ }
275
+
276
+ return this.prismaService.integration_profile.deleteMany({
277
+ where: { id: { in: ids } },
278
+ });
279
+ }
280
+
281
+ async test(data: CreateDTO) {
282
+ await this.validateTypeAndProvider(data.type_id, data.provider_id);
283
+
284
+ const provider = await this.prismaService.integration_provider.findUnique({
285
+ where: { id: data.provider_id },
286
+ select: {
287
+ slug: true,
288
+ integration_type: {
289
+ select: {
290
+ slug: true,
291
+ },
292
+ },
293
+ },
294
+ });
295
+
296
+ if (!provider) {
297
+ throw new BadRequestException(
298
+ `Provider with id ${data.provider_id} not found.`,
299
+ );
300
+ }
301
+
302
+ if (provider.integration_type.slug !== 'email') {
303
+ throw new BadRequestException(
304
+ 'Integration profile test is only available for email type.',
305
+ );
306
+ }
307
+
308
+ buildMailConfigFromIntegration(provider.slug, data.config);
309
+
310
+ const destination = String(
311
+ (data.config as Record<string, unknown> | null | undefined)?.from_email ??
312
+ '',
313
+ ).trim();
314
+
315
+ if (!destination) {
316
+ throw new BadRequestException('Sender email is required.');
317
+ }
318
+
319
+ return {
320
+ success: true,
321
+ destination,
322
+ };
323
+ }
324
+
325
+ async exportProfiles({
326
+ ids,
327
+ includeSecrets = false,
328
+ }: {
329
+ ids?: number[];
330
+ includeSecrets?: boolean;
331
+ }): Promise<Buffer> {
332
+ const rows = await this.prismaService.integration_profile.findMany({
333
+ where: ids?.length ? { id: { in: ids } } : undefined,
334
+ select: {
335
+ slug: true,
336
+ name: true,
337
+ config: true,
338
+ is_active: true,
339
+ integration_type: {
340
+ select: { slug: true },
341
+ },
342
+ integration_provider: {
343
+ select: { slug: true },
344
+ },
345
+ },
346
+ orderBy: [{ slug: 'asc' }],
347
+ });
348
+
349
+ const payload: IntegrationProfileExportItem[] = rows.map((row) => ({
350
+ slug: row.slug,
351
+ name: row.name,
352
+ type_slug: row.integration_type.slug,
353
+ provider_slug: row.integration_provider.slug,
354
+ config: this.sanitizeConfigForExport(
355
+ row.integration_provider.slug,
356
+ row.config as Record<string, unknown> | null,
357
+ includeSecrets,
358
+ ),
359
+ is_active: row.is_active,
360
+ }));
361
+
362
+ const envelope: IntegrationProfileImportEnvelope = {
363
+ type: 'integration-profile',
364
+ version: 1,
365
+ generated_at: new Date().toISOString(),
366
+ include_secrets: includeSecrets,
367
+ data: payload,
368
+ };
369
+
370
+ const compressed = pako.gzip(JSON.stringify(envelope));
371
+ return Buffer.from(compressed);
372
+ }
373
+
374
+ async validateImportFile(file: MulterFile) {
375
+ const envelope = this.parseImportEnvelope(file);
376
+
377
+ const typeMap = new Map(
378
+ (
379
+ await this.prismaService.integration_type.findMany({
380
+ select: { id: true, slug: true },
381
+ })
382
+ ).map((row) => [row.slug.toLowerCase(), row]),
383
+ );
384
+
385
+ const providerMap = new Map(
386
+ (
387
+ await this.prismaService.integration_provider.findMany({
388
+ select: { id: true, slug: true, type_id: true },
389
+ })
390
+ ).map((row) => [row.slug.toLowerCase(), row]),
391
+ );
392
+
393
+ const firstSlugIndex = new Map<string, number>();
394
+ const validItems: Omit<ValidatedImportItem, 'has_conflict'>[] = [];
395
+ const invalidItems: InvalidImportItem[] = [];
396
+
397
+ envelope.data.forEach((item, index) => {
398
+ const errors: string[] = [];
399
+ const slug = String(item?.slug ?? '').trim();
400
+ const name = String(item?.name ?? '').trim();
401
+ const typeSlug = String(item?.type_slug ?? '')
402
+ .trim()
403
+ .toLowerCase();
404
+ const providerSlug = String(item?.provider_slug ?? '')
405
+ .trim()
406
+ .toLowerCase();
407
+
408
+ if (!slug) errors.push('Missing slug.');
409
+ if (!name) errors.push(`Missing name for slug "${slug || index}".`);
410
+ if (!typeSlug) errors.push(`Missing type_slug for slug "${slug || index}".`);
411
+ if (!providerSlug) {
412
+ errors.push(`Missing provider_slug for slug "${slug || index}".`);
413
+ }
414
+
415
+ if (slug) {
416
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)) {
417
+ errors.push(`Invalid slug format "${slug}".`);
418
+ }
419
+
420
+ if (firstSlugIndex.has(slug)) {
421
+ const originalIndex = firstSlugIndex.get(slug);
422
+ errors.push(
423
+ `Duplicate slug "${slug}" in file (already used at index ${originalIndex}).`,
424
+ );
425
+ } else {
426
+ firstSlugIndex.set(slug, index);
427
+ }
428
+ }
429
+
430
+ const type = typeMap.get(typeSlug);
431
+ if (!type && typeSlug) {
432
+ errors.push(`Unknown type_slug "${typeSlug}".`);
433
+ }
434
+
435
+ const provider = providerMap.get(providerSlug);
436
+ if (!provider && providerSlug) {
437
+ errors.push(`Unknown provider_slug "${providerSlug}".`);
438
+ }
439
+
440
+ if (type && provider && provider.type_id !== type.id) {
441
+ errors.push(
442
+ `Provider "${providerSlug}" does not belong to type "${typeSlug}".`,
443
+ );
444
+ }
445
+
446
+ if (
447
+ item?.config !== null &&
448
+ item?.config !== undefined &&
449
+ (typeof item.config !== 'object' || Array.isArray(item.config))
450
+ ) {
451
+ errors.push(`Invalid config for slug "${slug || index}".`);
452
+ }
453
+
454
+ if (errors.length > 0) {
455
+ invalidItems.push({ index, slug: slug || undefined, errors });
456
+ return;
457
+ }
458
+
459
+ validItems.push({
460
+ index,
461
+ slug,
462
+ name,
463
+ type_slug: typeSlug,
464
+ provider_slug: providerSlug,
465
+ config: (item.config as Record<string, unknown> | null) ?? null,
466
+ is_active: item.is_active ?? true,
467
+ });
468
+ });
469
+
470
+ const existingSlugs = new Set(
471
+ (
472
+ await this.prismaService.integration_profile.findMany({
473
+ where: {
474
+ slug: {
475
+ in: validItems.map((item) => item.slug),
476
+ },
477
+ },
478
+ select: { slug: true },
479
+ })
480
+ ).map((row) => row.slug),
481
+ );
482
+
483
+ const profiles: ValidatedImportItem[] = validItems.map((item) => ({
484
+ ...item,
485
+ has_conflict: existingSlugs.has(item.slug),
486
+ }));
487
+
488
+ const conflictSlugs = profiles
489
+ .filter((item) => item.has_conflict)
490
+ .map((item) => item.slug);
491
+
492
+ return {
493
+ format: {
494
+ type: envelope.type,
495
+ version: envelope.version,
496
+ generated_at: envelope.generated_at,
497
+ include_secrets: envelope.include_secrets,
498
+ },
499
+ total_profiles: envelope.data.length,
500
+ valid_profiles: profiles.length,
501
+ invalid_profiles: invalidItems.length,
502
+ conflict_profiles: conflictSlugs.length,
503
+ importable_profiles: profiles.length - conflictSlugs.length,
504
+ conflict_slugs: conflictSlugs,
505
+ profiles,
506
+ invalid_items: invalidItems,
507
+ };
508
+ }
509
+
510
+ async confirmImport({
511
+ profiles,
512
+ conflictResolutions,
513
+ }: {
514
+ profiles: ImportProfileItemDTO[];
515
+ conflictResolutions?: ConflictResolutionDTO[];
516
+ }) {
517
+ if (!profiles?.length) {
518
+ throw new BadRequestException('No profiles provided for import.');
519
+ }
520
+
521
+ const resolutionMap = new Map<string, ConflictResolutionDTO['action']>(
522
+ (conflictResolutions ?? []).map((resolution) => [
523
+ resolution.slug,
524
+ resolution.action,
525
+ ]),
526
+ );
527
+
528
+ const typeMap = new Map(
529
+ (
530
+ await this.prismaService.integration_type.findMany({
531
+ select: { id: true, slug: true },
532
+ })
533
+ ).map((row) => [row.slug.toLowerCase(), row]),
534
+ );
535
+
536
+ const providerMap = new Map(
537
+ (
538
+ await this.prismaService.integration_provider.findMany({
539
+ select: { id: true, slug: true, type_id: true },
540
+ })
541
+ ).map((row) => [row.slug.toLowerCase(), row]),
542
+ );
543
+
544
+ const duplicatedSlugs = new Set<string>();
545
+ const seenSlugs = new Set<string>();
546
+ profiles.forEach((item) => {
547
+ const slug = item.slug.trim();
548
+ if (seenSlugs.has(slug)) {
549
+ duplicatedSlugs.add(slug);
550
+ }
551
+ seenSlugs.add(slug);
552
+ });
553
+
554
+ if (duplicatedSlugs.size > 0) {
555
+ throw new BadRequestException(
556
+ `File has duplicated slugs: ${Array.from(duplicatedSlugs).join(', ')}`,
557
+ );
558
+ }
559
+
560
+ const existingBySlug = new Map(
561
+ (
562
+ await this.prismaService.integration_profile.findMany({
563
+ where: { slug: { in: profiles.map((profile) => profile.slug.trim()) } },
564
+ select: { id: true, slug: true },
565
+ })
566
+ ).map((row) => [row.slug, row]),
567
+ );
568
+
569
+ let created = 0;
570
+ let replaced = 0;
571
+ let ignored = 0;
572
+ let renamed = 0;
573
+
574
+ for (const item of profiles) {
575
+ const slug = item.slug.trim();
576
+ const name = item.name.trim();
577
+ const typeSlug = item.type_slug.trim().toLowerCase();
578
+ const providerSlug = item.provider_slug.trim().toLowerCase();
579
+
580
+ if (!slug || !name || !typeSlug || !providerSlug) {
581
+ throw new BadRequestException(
582
+ `Invalid profile payload for slug "${slug || '<empty>'}".`,
583
+ );
584
+ }
585
+
586
+ const type = typeMap.get(typeSlug);
587
+ if (!type) {
588
+ throw new BadRequestException(`Unknown type_slug "${typeSlug}".`);
589
+ }
590
+
591
+ const provider = providerMap.get(providerSlug);
592
+ if (!provider) {
593
+ throw new BadRequestException(`Unknown provider_slug "${providerSlug}".`);
594
+ }
595
+
596
+ if (provider.type_id !== type.id) {
597
+ throw new BadRequestException(
598
+ `Provider "${providerSlug}" does not belong to type "${typeSlug}".`,
599
+ );
600
+ }
601
+
602
+ const existing = existingBySlug.get(slug);
603
+ const data = {
604
+ slug,
605
+ name,
606
+ type_id: type.id,
607
+ provider_id: provider.id,
608
+ config: (item.config as object | null) ?? null,
609
+ is_active: item.is_active ?? true,
610
+ };
611
+
612
+ if (!existing) {
613
+ await this.prismaService.integration_profile.create({ data });
614
+ created += 1;
615
+ continue;
616
+ }
617
+
618
+ const action = resolutionMap.get(slug) ?? 'ignore';
619
+ if (action === 'ignore') {
620
+ ignored += 1;
621
+ continue;
622
+ }
623
+
624
+ if (action === 'replace') {
625
+ await this.prismaService.integration_profile.update({
626
+ where: { id: existing.id },
627
+ data: {
628
+ name: data.name,
629
+ type_id: data.type_id,
630
+ provider_id: data.provider_id,
631
+ config: data.config,
632
+ is_active: data.is_active,
633
+ },
634
+ });
635
+ replaced += 1;
636
+ continue;
637
+ }
638
+
639
+ const newSlug = await this.generateUniqueSlug(slug);
640
+ await this.prismaService.integration_profile.create({
641
+ data: {
642
+ ...data,
643
+ slug: newSlug,
644
+ },
645
+ });
646
+ renamed += 1;
647
+ }
648
+
649
+ return {
650
+ success: true,
651
+ total_received: profiles.length,
652
+ created,
653
+ replaced,
654
+ renamed,
655
+ ignored,
656
+ imported: created + replaced + renamed,
657
+ };
658
+ }
659
+
660
+ private async ensureSlugIsAvailable(slug: string) {
661
+ const existing = await this.prismaService.integration_profile.findUnique({
662
+ where: { slug },
663
+ select: { id: true },
664
+ });
665
+
666
+ if (existing) {
667
+ throw new BadRequestException(
668
+ `Integration profile with slug "${slug}" already exists.`,
669
+ );
670
+ }
671
+ }
672
+
673
+ private async validateTypeAndProvider(type_id: number, provider_id: number) {
674
+ const provider = await this.prismaService.integration_provider.findUnique({
675
+ where: { id: provider_id },
676
+ select: { id: true, type_id: true },
677
+ });
678
+
679
+ if (!provider) {
680
+ throw new BadRequestException(
681
+ `Provider with id ${provider_id} not found.`,
682
+ );
683
+ }
684
+
685
+ if (provider.type_id !== type_id) {
686
+ throw new BadRequestException(
687
+ `Provider ${provider_id} does not belong to type ${type_id}.`,
688
+ );
689
+ }
690
+ }
691
+
692
+ private parseImportEnvelope(file: MulterFile): IntegrationProfileImportEnvelope {
693
+ let parsed: unknown;
694
+ try {
695
+ const decompressed = pako.ungzip(file.buffer, { to: 'string' });
696
+ parsed = JSON.parse(decompressed);
697
+ } catch {
698
+ throw new BadRequestException(
699
+ 'File is not a valid .hedhog archive or is corrupted.',
700
+ );
701
+ }
702
+
703
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
704
+ throw new BadRequestException('Invalid .hedhog payload format.');
705
+ }
706
+
707
+ const payload = parsed as Partial<IntegrationProfileImportEnvelope>;
708
+ if (payload.type !== 'integration-profile') {
709
+ throw new BadRequestException(
710
+ `Invalid .hedhog type "${String(payload.type ?? '')}".`,
711
+ );
712
+ }
713
+
714
+ if (payload.version !== 1) {
715
+ throw new BadRequestException(
716
+ `Unsupported .hedhog version "${String(payload.version ?? '')}".`,
717
+ );
718
+ }
719
+
720
+ if (!Array.isArray(payload.data)) {
721
+ throw new BadRequestException('Invalid .hedhog data: expected an array.');
722
+ }
723
+
724
+ return {
725
+ type: 'integration-profile',
726
+ version: 1,
727
+ generated_at:
728
+ typeof payload.generated_at === 'string'
729
+ ? payload.generated_at
730
+ : new Date().toISOString(),
731
+ include_secrets: payload.include_secrets === true,
732
+ data: payload.data as IntegrationProfileExportItem[],
733
+ };
734
+ }
735
+
736
+ private sanitizeConfigForExport(
737
+ providerSlug: string,
738
+ config: Record<string, unknown> | null,
739
+ includeSecrets: boolean,
740
+ ): Record<string, unknown> | null {
741
+ if (!config || includeSecrets) return config;
742
+
743
+ const hiddenKeys = this.getSensitiveConfigKeys(providerSlug);
744
+ if (hiddenKeys.length === 0) return config;
745
+
746
+ const sanitized = { ...config };
747
+ hiddenKeys.forEach((key) => {
748
+ delete sanitized[key];
749
+ });
750
+
751
+ return sanitized;
752
+ }
753
+
754
+ private getSensitiveConfigKeys(providerSlug: string): string[] {
755
+ const map: Record<string, string[]> = {
756
+ smtp: ['password'],
757
+ gmail: ['client_secret', 'refresh_token'],
758
+ ses: ['secret_access_key'],
759
+ 'evolution-api': ['token'],
760
+ 'whatsapp-official': ['access_token'],
761
+ s3: ['secret_access_key'],
762
+ gcs: ['key_file_json'],
763
+ 'azure-blob': ['key'],
764
+ 's3-compatible': ['secret_access_key'],
765
+ openai: ['api_key'],
766
+ gemini: ['api_key'],
767
+ claude: ['api_key'],
768
+ 'google-oauth': ['client_secret'],
769
+ 'github-oauth': ['client_secret'],
770
+ 'microsoft-oauth': ['client_secret'],
771
+ };
772
+
773
+ return map[providerSlug.toLowerCase()] ?? [];
774
+ }
775
+
776
+ private async generateUniqueSlug(baseSlug: string): Promise<string> {
777
+ let candidate = `${baseSlug}-copy`;
778
+ let suffix = 1;
779
+
780
+ // Gera slug único determinístico para imports com conflito e opção de renomear.
781
+ while (
782
+ await this.prismaService.integration_profile.findUnique({
783
+ where: { slug: candidate },
784
+ select: { id: true },
785
+ })
786
+ ) {
787
+ candidate = `${baseSlug}-copy-${suffix}`;
788
+ suffix += 1;
789
+ }
790
+
791
+ return candidate;
792
+ }
793
+ }