@hed-hog/core 0.0.356 → 0.0.359

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 (250) hide show
  1. package/dist/auth/guards/auth.guard.d.ts +6 -1
  2. package/dist/auth/guards/auth.guard.d.ts.map +1 -1
  3. package/dist/auth/guards/auth.guard.js +33 -2
  4. package/dist/auth/guards/auth.guard.js.map +1 -1
  5. package/dist/file/file.controller.d.ts +4 -2
  6. package/dist/file/file.controller.d.ts.map +1 -1
  7. package/dist/file/file.controller.js +44 -38
  8. package/dist/file/file.controller.js.map +1 -1
  9. package/dist/file/file.service.d.ts +2 -2
  10. package/dist/file/file.service.d.ts.map +1 -1
  11. package/dist/file/file.service.js +25 -42
  12. package/dist/file/file.service.js.map +1 -1
  13. package/dist/file/provider/azure.provider.d.ts.map +1 -1
  14. package/dist/file/provider/azure.provider.js +10 -15
  15. package/dist/file/provider/azure.provider.js.map +1 -1
  16. package/dist/file/provider/gcs.provider.d.ts.map +1 -1
  17. package/dist/file/provider/gcs.provider.js +8 -9
  18. package/dist/file/provider/gcs.provider.js.map +1 -1
  19. package/dist/file/provider/local.provider.d.ts.map +1 -1
  20. package/dist/file/provider/local.provider.js +11 -10
  21. package/dist/file/provider/local.provider.js.map +1 -1
  22. package/dist/file/provider/provider.factory.d.ts +1 -1
  23. package/dist/file/provider/provider.factory.d.ts.map +1 -1
  24. package/dist/file/provider/provider.factory.js +16 -6
  25. package/dist/file/provider/provider.factory.js.map +1 -1
  26. package/dist/file/provider/s3.provider.d.ts.map +1 -1
  27. package/dist/file/provider/s3.provider.js +4 -20
  28. package/dist/file/provider/s3.provider.js.map +1 -1
  29. package/dist/file/provider/spaces.provider.d.ts.map +1 -1
  30. package/dist/file/provider/spaces.provider.js +4 -19
  31. package/dist/file/provider/spaces.provider.js.map +1 -1
  32. package/dist/integration-profile/integration-profile.controller.d.ts +1 -1
  33. package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
  34. package/dist/integration-profile/integration-profile.controller.js +4 -3
  35. package/dist/integration-profile/integration-profile.controller.js.map +1 -1
  36. package/dist/integration-profile/integration-profile.service.d.ts +1 -1
  37. package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
  38. package/dist/integration-profile/integration-profile.service.js +7 -4
  39. package/dist/integration-profile/integration-profile.service.js.map +1 -1
  40. package/dist/integration-profile/integration-profile.utils.d.ts +6 -0
  41. package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
  42. package/dist/integration-profile/integration-profile.utils.js +9 -0
  43. package/dist/integration-profile/integration-profile.utils.js.map +1 -1
  44. package/dist/mail/mail.module.d.ts.map +1 -1
  45. package/dist/mail/mail.module.js +9 -4
  46. package/dist/mail/mail.module.js.map +1 -1
  47. package/dist/mail/mail.service.d.ts +2 -1
  48. package/dist/mail/mail.service.d.ts.map +1 -1
  49. package/dist/mail/mail.service.js +11 -7
  50. package/dist/mail/mail.service.js.map +1 -1
  51. package/dist/mcp/guards/mcp-auth.guard.js +1 -1
  52. package/dist/mcp/guards/mcp-auth.guard.js.map +1 -1
  53. package/dist/mcp/mcp-api-key.controller.d.ts +2 -0
  54. package/dist/mcp/mcp-api-key.controller.d.ts.map +1 -1
  55. package/dist/mcp/mcp-api-key.controller.js +1 -1
  56. package/dist/mcp/mcp-api-key.controller.js.map +1 -1
  57. package/dist/mcp/mcp-api-key.service.d.ts +2 -1
  58. package/dist/mcp/mcp-api-key.service.d.ts.map +1 -1
  59. package/dist/mcp/mcp-api-key.service.js +6 -2
  60. package/dist/mcp/mcp-api-key.service.js.map +1 -1
  61. package/dist/mcp/mcp.module.d.ts.map +1 -1
  62. package/dist/mcp/mcp.module.js +1 -3
  63. package/dist/mcp/mcp.module.js.map +1 -1
  64. package/dist/oauth/interfaces/OAuthProvider.d.ts +1 -1
  65. package/dist/oauth/interfaces/OAuthProvider.d.ts.map +1 -1
  66. package/dist/oauth/oauth.controller.d.ts +4 -1
  67. package/dist/oauth/oauth.controller.d.ts.map +1 -1
  68. package/dist/oauth/oauth.controller.js +31 -5
  69. package/dist/oauth/oauth.controller.js.map +1 -1
  70. package/dist/oauth/oauth.service.d.ts +12 -1
  71. package/dist/oauth/oauth.service.d.ts.map +1 -1
  72. package/dist/oauth/oauth.service.js +125 -23
  73. package/dist/oauth/oauth.service.js.map +1 -1
  74. package/dist/oauth/providers/abstract.provider.d.ts +1 -1
  75. package/dist/oauth/providers/abstract.provider.d.ts.map +1 -1
  76. package/dist/oauth/providers/facebook.provider.d.ts +5 -2
  77. package/dist/oauth/providers/facebook.provider.d.ts.map +1 -1
  78. package/dist/oauth/providers/facebook.provider.js +34 -19
  79. package/dist/oauth/providers/facebook.provider.js.map +1 -1
  80. package/dist/oauth/providers/github.provider.d.ts +6 -2
  81. package/dist/oauth/providers/github.provider.d.ts.map +1 -1
  82. package/dist/oauth/providers/github.provider.js +43 -20
  83. package/dist/oauth/providers/github.provider.js.map +1 -1
  84. package/dist/oauth/providers/google.provider.d.ts +5 -2
  85. package/dist/oauth/providers/google.provider.d.ts.map +1 -1
  86. package/dist/oauth/providers/google.provider.js +34 -19
  87. package/dist/oauth/providers/google.provider.js.map +1 -1
  88. package/dist/oauth/providers/microsoft-entra-id.provider.d.ts +5 -2
  89. package/dist/oauth/providers/microsoft-entra-id.provider.d.ts.map +1 -1
  90. package/dist/oauth/providers/microsoft-entra-id.provider.js +43 -40
  91. package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
  92. package/dist/oauth/providers/microsoft.provider.d.ts +5 -2
  93. package/dist/oauth/providers/microsoft.provider.d.ts.map +1 -1
  94. package/dist/oauth/providers/microsoft.provider.js +34 -19
  95. package/dist/oauth/providers/microsoft.provider.js.map +1 -1
  96. package/dist/profile/profile.controller.js +1 -1
  97. package/dist/profile/profile.controller.js.map +1 -1
  98. package/dist/security/security.service.d.ts +2 -0
  99. package/dist/security/security.service.d.ts.map +1 -1
  100. package/dist/security/security.service.js +21 -0
  101. package/dist/security/security.service.js.map +1 -1
  102. package/dist/setting/setting.service.d.ts +26 -1
  103. package/dist/setting/setting.service.d.ts.map +1 -1
  104. package/dist/setting/setting.service.js +240 -5
  105. package/dist/setting/setting.service.js.map +1 -1
  106. package/dist/user/user.controller.d.ts +2 -1
  107. package/dist/user/user.controller.d.ts.map +1 -1
  108. package/dist/user/user.controller.js +5 -4
  109. package/dist/user/user.controller.js.map +1 -1
  110. package/dist/user/user.service.d.ts +4 -2
  111. package/dist/user/user.service.d.ts.map +1 -1
  112. package/dist/user/user.service.js +19 -4
  113. package/dist/user/user.service.js.map +1 -1
  114. package/dist/webhook-integration/dto/webhook-action.dto.d.ts +17 -2
  115. package/dist/webhook-integration/dto/webhook-action.dto.d.ts.map +1 -1
  116. package/dist/webhook-integration/dto/webhook-action.dto.js +93 -2
  117. package/dist/webhook-integration/dto/webhook-action.dto.js.map +1 -1
  118. package/dist/webhook-integration/webhook-command.registry.d.ts +15 -0
  119. package/dist/webhook-integration/webhook-command.registry.d.ts.map +1 -0
  120. package/dist/webhook-integration/webhook-command.registry.js +36 -0
  121. package/dist/webhook-integration/webhook-command.registry.js.map +1 -0
  122. package/dist/webhook-integration/webhook-integration.controller.d.ts +19 -0
  123. package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
  124. package/dist/webhook-integration/webhook-integration.controller.js +38 -0
  125. package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
  126. package/dist/webhook-integration/webhook-integration.module.d.ts.map +1 -1
  127. package/dist/webhook-integration/webhook-integration.module.js +5 -0
  128. package/dist/webhook-integration/webhook-integration.module.js.map +1 -1
  129. package/dist/webhook-integration/webhook-integration.service.d.ts +25 -1
  130. package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
  131. package/dist/webhook-integration/webhook-integration.service.js +332 -23
  132. package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
  133. package/hedhog/data/integration_provider.yaml +16 -0
  134. package/hedhog/data/menu.yaml +0 -16
  135. package/hedhog/data/route.yaml +32 -383
  136. package/hedhog/data/setting_group.yaml +179 -174
  137. package/hedhog/data/setting_subgroup.yaml +16 -0
  138. package/hedhog/frontend/app/account/components/tokens.tsx.ejs +421 -0
  139. package/hedhog/frontend/app/account/layout.tsx.ejs +6 -3
  140. package/hedhog/frontend/app/account/mcp-tokens/page.tsx.ejs +2 -2
  141. package/hedhog/frontend/app/account/tokens/page.tsx.ejs +5 -0
  142. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +414 -76
  143. package/hedhog/frontend/app/configurations/layout.tsx.ejs +1 -1
  144. package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +3 -1
  145. package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +186 -18
  146. package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +597 -21
  147. package/hedhog/frontend/app/mail/profiles/page.tsx.ejs +2 -2
  148. package/hedhog/frontend/app/setting-list/page.tsx.ejs +229 -395
  149. package/hedhog/frontend/messages/en.json +67 -3
  150. package/hedhog/frontend/messages/pt.json +67 -3
  151. package/hedhog/table/file.yaml +1 -1
  152. package/hedhog/table/mcp_api_key.yaml +4 -0
  153. package/hedhog/table/webhook_action.yaml +57 -1
  154. package/package.json +5 -4
  155. package/src/auth/guards/auth.guard.ts +32 -0
  156. package/src/file/file.controller.ts +51 -42
  157. package/src/file/file.service.ts +31 -52
  158. package/src/file/provider/azure.provider.ts +14 -28
  159. package/src/file/provider/gcs.provider.ts +9 -9
  160. package/src/file/provider/local.provider.ts +10 -13
  161. package/src/file/provider/provider.factory.ts +19 -7
  162. package/src/file/provider/s3.provider.ts +4 -20
  163. package/src/file/provider/spaces.provider.ts +4 -19
  164. package/src/integration-profile/integration-profile.controller.ts +2 -0
  165. package/src/integration-profile/integration-profile.service.ts +7 -2
  166. package/src/integration-profile/integration-profile.utils.ts +17 -0
  167. package/src/mail/mail.module.ts +14 -4
  168. package/src/mail/mail.service.ts +12 -7
  169. package/src/mcp/guards/mcp-auth.guard.ts +1 -1
  170. package/src/mcp/mcp-api-key.controller.ts +2 -2
  171. package/src/mcp/mcp-api-key.service.ts +6 -2
  172. package/src/mcp/mcp.module.ts +1 -3
  173. package/src/oauth/interfaces/OAuthProvider.ts +1 -1
  174. package/src/oauth/oauth.controller.ts +30 -2
  175. package/src/oauth/oauth.service.ts +194 -35
  176. package/src/oauth/providers/abstract.provider.ts +1 -1
  177. package/src/oauth/providers/facebook.provider.ts +36 -20
  178. package/src/oauth/providers/github.provider.ts +46 -23
  179. package/src/oauth/providers/google.provider.ts +36 -20
  180. package/src/oauth/providers/microsoft-entra-id.provider.ts +42 -46
  181. package/src/oauth/providers/microsoft.provider.ts +36 -21
  182. package/src/profile/profile.controller.ts +2 -2
  183. package/src/security/security.service.ts +34 -1
  184. package/src/setting/setting.service.ts +307 -7
  185. package/src/user/user.controller.ts +4 -1
  186. package/src/user/user.service.ts +20 -2
  187. package/src/webhook-integration/dto/webhook-action.dto.ts +85 -4
  188. package/src/webhook-integration/webhook-command.registry.ts +42 -0
  189. package/src/webhook-integration/webhook-integration.controller.ts +20 -0
  190. package/src/webhook-integration/webhook-integration.module.ts +50 -45
  191. package/src/webhook-integration/webhook-integration.service.ts +374 -11
  192. package/dist/mail-profile/dto/create.dto.d.ts +0 -23
  193. package/dist/mail-profile/dto/create.dto.d.ts.map +0 -1
  194. package/dist/mail-profile/dto/create.dto.js +0 -139
  195. package/dist/mail-profile/dto/create.dto.js.map +0 -1
  196. package/dist/mail-profile/dto/update.dto.d.ts +0 -6
  197. package/dist/mail-profile/dto/update.dto.d.ts.map +0 -1
  198. package/dist/mail-profile/dto/update.dto.js +0 -9
  199. package/dist/mail-profile/dto/update.dto.js.map +0 -1
  200. package/dist/mail-profile/mail-profile.controller.d.ts +0 -94
  201. package/dist/mail-profile/mail-profile.controller.d.ts.map +0 -1
  202. package/dist/mail-profile/mail-profile.controller.js +0 -95
  203. package/dist/mail-profile/mail-profile.controller.js.map +0 -1
  204. package/dist/mail-profile/mail-profile.module.d.ts +0 -3
  205. package/dist/mail-profile/mail-profile.module.d.ts.map +0 -1
  206. package/dist/mail-profile/mail-profile.module.js +0 -31
  207. package/dist/mail-profile/mail-profile.module.js.map +0 -1
  208. package/dist/mail-profile/mail-profile.service.d.ts +0 -109
  209. package/dist/mail-profile/mail-profile.service.d.ts.map +0 -1
  210. package/dist/mail-profile/mail-profile.service.js +0 -278
  211. package/dist/mail-profile/mail-profile.service.js.map +0 -1
  212. package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts +0 -20
  213. package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts.map +0 -1
  214. package/dist/mcp/tools/core-mail-profile.mcp-tools.js +0 -96
  215. package/dist/mcp/tools/core-mail-profile.mcp-tools.js.map +0 -1
  216. package/dist/whatsapp-profile/dto/create.dto.d.ts +0 -11
  217. package/dist/whatsapp-profile/dto/create.dto.d.ts.map +0 -1
  218. package/dist/whatsapp-profile/dto/create.dto.js +0 -55
  219. package/dist/whatsapp-profile/dto/create.dto.js.map +0 -1
  220. package/dist/whatsapp-profile/dto/update.dto.d.ts +0 -6
  221. package/dist/whatsapp-profile/dto/update.dto.d.ts.map +0 -1
  222. package/dist/whatsapp-profile/dto/update.dto.js +0 -9
  223. package/dist/whatsapp-profile/dto/update.dto.js.map +0 -1
  224. package/dist/whatsapp-profile/whatsapp-profile.controller.d.ts +0 -54
  225. package/dist/whatsapp-profile/whatsapp-profile.controller.d.ts.map +0 -1
  226. package/dist/whatsapp-profile/whatsapp-profile.controller.js +0 -85
  227. package/dist/whatsapp-profile/whatsapp-profile.controller.js.map +0 -1
  228. package/dist/whatsapp-profile/whatsapp-profile.module.d.ts +0 -3
  229. package/dist/whatsapp-profile/whatsapp-profile.module.d.ts.map +0 -1
  230. package/dist/whatsapp-profile/whatsapp-profile.module.js +0 -26
  231. package/dist/whatsapp-profile/whatsapp-profile.module.js.map +0 -1
  232. package/dist/whatsapp-profile/whatsapp-profile.service.d.ts +0 -63
  233. package/dist/whatsapp-profile/whatsapp-profile.service.d.ts.map +0 -1
  234. package/dist/whatsapp-profile/whatsapp-profile.service.js +0 -138
  235. package/dist/whatsapp-profile/whatsapp-profile.service.js.map +0 -1
  236. package/hedhog/data/mail_profile.yaml +0 -18
  237. package/hedhog/data/whatsapp_profile.yaml +0 -6
  238. package/hedhog/table/mail_profile.yaml +0 -75
  239. package/hedhog/table/whatsapp_profile.yaml +0 -26
  240. package/src/mail-profile/dto/create.dto.ts +0 -145
  241. package/src/mail-profile/dto/update.dto.ts +0 -4
  242. package/src/mail-profile/mail-profile.controller.ts +0 -56
  243. package/src/mail-profile/mail-profile.module.ts +0 -18
  244. package/src/mail-profile/mail-profile.service.ts +0 -308
  245. package/src/mcp/tools/core-mail-profile.mcp-tools.ts +0 -69
  246. package/src/whatsapp-profile/dto/create.dto.ts +0 -36
  247. package/src/whatsapp-profile/dto/update.dto.ts +0 -4
  248. package/src/whatsapp-profile/whatsapp-profile.controller.ts +0 -51
  249. package/src/whatsapp-profile/whatsapp-profile.module.ts +0 -13
  250. package/src/whatsapp-profile/whatsapp-profile.service.ts +0 -160
@@ -6,6 +6,7 @@ import {
6
6
  forwardRef,
7
7
  Inject,
8
8
  Injectable,
9
+ Logger,
9
10
  NotFoundException,
10
11
  OnModuleInit
11
12
  } from '@nestjs/common';
@@ -14,21 +15,30 @@ import { createWriteStream, promises as fs } from 'fs';
14
15
  import { basename, dirname } from 'path';
15
16
  import { pipeline } from 'stream/promises';
16
17
  import {
17
- buildStorageConfigFromIntegration,
18
- resolveFileProviderSlug,
19
- resolveStorageEnumProvider,
18
+ buildStorageConfigFromIntegration,
19
+ resolveFileProviderSlug,
20
+ resolveStorageEnumProvider,
20
21
  } from '../integration-profile/integration-profile.utils';
21
22
  import { SettingService } from '../setting/setting.service';
22
23
  import { DeleteDTO } from './dto/delete.dto';
23
24
  import { AbstractProvider } from './provider/abstract.provider';
24
- import { EnumProvider } from './provider/provider.enum';
25
25
  import { ProviderFactory } from './provider/provider.factory';
26
26
 
27
27
  @Injectable()
28
28
  export class FileService implements OnModuleInit {
29
- private providerId: number;
29
+ private readonly logger = new Logger(FileService.name);
30
+ private providerId: number | undefined;
30
31
  private mimetypes: Record<string, number> = {};
31
32
 
33
+ private resolveProviderPath(file: { location: string; path: string }): string {
34
+ // Supports legacy basename-only values while prioritizing full provider keys.
35
+ if (typeof file.path === 'string' && file.path.includes('/')) {
36
+ return file.path;
37
+ }
38
+
39
+ return `${file.location}/${file.path}`;
40
+ }
41
+
32
42
  private hasMojibake(value: string): boolean {
33
43
  return /Ã.|Â.|â[\u0080-\u00BF]/.test(value);
34
44
  }
@@ -60,17 +70,21 @@ export class FileService implements OnModuleInit {
60
70
  async onModuleInit() {
61
71
  try {
62
72
  await this.getProvider();
63
- } catch {
73
+ } catch (error) {
64
74
  // No storage profile configured yet — server starts normally, uploads will
65
75
  // fail until a profile is selected in Settings → File Storage.
76
+ this.logger.warn(
77
+ `Storage provider not ready on startup: ${error instanceof Error ? error.message : String(error)}`,
78
+ );
66
79
  }
67
80
  }
68
81
 
69
82
  async getProvider(): Promise<AbstractProvider> {
70
83
  const settings = await this.settingService.getSettingValues(['file-storage-profile-id']);
71
- const profileId = Number(settings['file-storage-profile-id']);
84
+ const rawProfileId = settings['file-storage-profile-id'];
85
+ const profileId = Number(rawProfileId);
72
86
 
73
- if (!profileId) {
87
+ if (!Number.isFinite(profileId) || profileId <= 0) {
74
88
  throw new BadRequestException('No file storage profile configured. Set a storage profile in the File Storage settings.');
75
89
  }
76
90
 
@@ -107,7 +121,7 @@ export class FileService implements OnModuleInit {
107
121
  }
108
122
 
109
123
  this.providerId = providerData.id;
110
- return ProviderFactory.create(enumProvider, config, this.jwtService);
124
+ return await ProviderFactory.create(enumProvider, config, this.jwtService);
111
125
  }
112
126
 
113
127
  async getMimeType(mimetype: string) {
@@ -202,38 +216,6 @@ export class FileService implements OnModuleInit {
202
216
  return file;
203
217
  }
204
218
 
205
- async getStorage(): Promise<string> {
206
- const settings = await this.settingService.getSettingValues(['file-storage-profile-id']);
207
- const profileId = Number(settings['file-storage-profile-id']);
208
-
209
- if (!profileId) {
210
- return EnumProvider.LOCAL;
211
- }
212
-
213
- const profile = await this.prismaService.integration_profile.findUnique({
214
- where: { id: profileId },
215
- include: { integration_provider: { select: { slug: true } } },
216
- });
217
-
218
- if (!profile) {
219
- return EnumProvider.LOCAL;
220
- }
221
-
222
- return resolveStorageEnumProvider(profile.integration_provider.slug);
223
- }
224
-
225
- private getProviderPath(storage: string, filepath: string) {
226
- if (storage === EnumProvider.LOCAL) {
227
- return filepath;
228
- }
229
-
230
- try {
231
- return new URL(filepath).pathname;
232
- } catch {
233
- return filepath;
234
- }
235
- }
236
-
237
219
  private mapStorageNotFoundError(error: any, fileId: number) {
238
220
  const missingInStorage =
239
221
  error?.name === 'NoSuchKey' ||
@@ -265,9 +247,8 @@ export class FileService implements OnModuleInit {
265
247
  throw new NotFoundException(`File not found: ${fileId}`);
266
248
  }
267
249
 
268
- const storage = await this.getStorage();
269
250
  const provider = await this.getProvider();
270
- const providerPath = this.getProviderPath(storage, file.path);
251
+ const providerPath = this.resolveProviderPath(file);
271
252
 
272
253
  let metadata: any;
273
254
  try {
@@ -348,11 +329,8 @@ export class FileService implements OnModuleInit {
348
329
  throw new NotFoundException(`File not found: ${fileId}`);
349
330
  }
350
331
 
351
- const storage = await this.getStorage();
352
-
353
332
  const provider = await this.getProvider();
354
-
355
- const providerPath = this.getProviderPath(storage, file.path);
333
+ const providerPath = this.resolveProviderPath(file);
356
334
 
357
335
  let providerBuffer: Buffer;
358
336
  try {
@@ -526,6 +504,7 @@ export class FileService implements OnModuleInit {
526
504
  },
527
505
  select: {
528
506
  id: true,
507
+ location: true,
529
508
  path: true,
530
509
  },
531
510
  });
@@ -535,7 +514,7 @@ export class FileService implements OnModuleInit {
535
514
  }
536
515
 
537
516
  for (const file of files) {
538
- await (await this.getProvider()).delete(file.path);
517
+ await (await this.getProvider()).delete(this.resolveProviderPath(file));
539
518
 
540
519
  await this.prismaService.file.deleteMany({
541
520
  where: {
@@ -629,8 +608,9 @@ export class FileService implements OnModuleInit {
629
608
  }
630
609
 
631
610
  const provider = await this.getProvider();
632
- const metadata = await provider.metaData(filepath);
633
- const stream = await provider.readStream(filepath);
611
+ const providerPath = this.resolveProviderPath(file);
612
+ const metadata = await provider.metaData(providerPath);
613
+ const stream = await provider.readStream(providerPath);
634
614
  const size = this.getFileSizeFromMetadata(metadata);
635
615
 
636
616
  return {
@@ -658,9 +638,8 @@ export class FileService implements OnModuleInit {
658
638
  );
659
639
  }
660
640
 
661
- const storage = await this.getStorage();
662
641
  const provider = await this.getProvider();
663
- const providerPath = this.getProviderPath(storage, file.path);
642
+ const providerPath = this.resolveProviderPath(file);
664
643
  const metadata = await provider.metaData(providerPath);
665
644
  const stream = await provider.readStream(providerPath);
666
645
  const size = this.getFileSizeFromMetadata(metadata);
@@ -1,8 +1,8 @@
1
1
  import {
2
- BlobServiceClient,
3
- ContainerSASPermissions,
4
- generateBlobSASQueryParameters,
5
- StorageSharedKeyCredential,
2
+ BlobServiceClient,
3
+ ContainerSASPermissions,
4
+ generateBlobSASQueryParameters,
5
+ StorageSharedKeyCredential,
6
6
  } from '@azure/storage-blob';
7
7
  import { BadRequestException } from '@nestjs/common';
8
8
  import { basename } from 'path';
@@ -11,7 +11,6 @@ import { AbstractProvider } from './abstract.provider';
11
11
  export class AzureProvider extends AbstractProvider {
12
12
  constructor(private setting: Record<string, string>) {
13
13
  super();
14
- this.initValidation();
15
14
  }
16
15
 
17
16
  async initValidation() {
@@ -68,13 +67,13 @@ export class AzureProvider extends AbstractProvider {
68
67
  const containerClient = blobServiceClient.getContainerClient(
69
68
  this.setting['storage-abs-container'],
70
69
  );
71
- const filepath = [destination, this.getFilename(filename)].join('/');
72
- const blobClient = containerClient.getBlockBlobClient(filepath);
70
+ const storedFilename = this.getFilename(filename);
71
+ const blobClient = containerClient.getBlockBlobClient([destination, storedFilename].join('/'));
73
72
 
74
73
  await blobClient.upload(fileContent, fileContent.length, {
75
74
  blobHTTPHeaders: { blobContentType: mimetype },
76
75
  });
77
- return `https://${this.setting['storage-abs-container']}.azureedge.net/${filepath}`;
76
+ return storedFilename;
78
77
  }
79
78
 
80
79
  async upload(destination: string, file: MulterFile): Promise<any> {
@@ -82,15 +81,13 @@ export class AzureProvider extends AbstractProvider {
82
81
  const containerClient = blobServiceClient.getContainerClient(
83
82
  this.setting['storage-abs-container'],
84
83
  );
85
- const filepath = [destination, this.getFilename(file.originalname)].join(
86
- '/',
87
- );
88
- const blobClient = containerClient.getBlockBlobClient(filepath);
84
+ const storedFilename = this.getFilename(file.originalname);
85
+ const blobClient = containerClient.getBlockBlobClient([destination, storedFilename].join('/'));
89
86
 
90
87
  await blobClient.uploadData(file.buffer, {
91
88
  blobHTTPHeaders: { blobContentType: file.mimetype },
92
89
  });
93
- return `https://${this.setting['storage-abs-container']}.azureedge.net/${filepath}`;
90
+ return storedFilename;
94
91
  }
95
92
 
96
93
  async uploadFromPath(
@@ -102,10 +99,8 @@ export class AzureProvider extends AbstractProvider {
102
99
  const containerClient = blobServiceClient.getContainerClient(
103
100
  this.setting['storage-abs-container'],
104
101
  );
105
- const filepath = [destination, this.getFilename(options?.filename ?? basename(filePath))].join(
106
- '/',
107
- );
108
- const blobClient = containerClient.getBlockBlobClient(filepath);
102
+ const storedFilename = this.getFilename(options?.filename ?? basename(filePath));
103
+ const blobClient = containerClient.getBlockBlobClient([destination, storedFilename].join('/'));
109
104
 
110
105
  await blobClient.uploadFile(filePath, {
111
106
  blobHTTPHeaders: options?.mimetype
@@ -113,24 +108,15 @@ export class AzureProvider extends AbstractProvider {
113
108
  : undefined,
114
109
  });
115
110
 
116
- return `https://${this.setting['storage-abs-container']}.azureedge.net/${filepath}`;
111
+ return storedFilename;
117
112
  }
118
113
 
119
114
  async delete(filepath: string): Promise<any> {
120
- const url = new URL(filepath);
121
-
122
- if (!url.pathname) {
123
- throw new Error(`Invalid filepath "${filepath}" for S3`);
124
- }
125
-
126
115
  const blobServiceClient = await this.getClient();
127
116
  const containerClient = blobServiceClient.getContainerClient(
128
117
  this.setting['storage-abs-container'],
129
118
  );
130
- const blobClient = containerClient.getBlockBlobClient(
131
- url.pathname.split('/').slice(1).join('/'),
132
- );
133
-
119
+ const blobClient = containerClient.getBlockBlobClient(filepath);
134
120
  return await blobClient.delete();
135
121
  }
136
122
 
@@ -8,7 +8,6 @@ export class GCSProvider extends AbstractProvider {
8
8
 
9
9
  constructor(private setting: Record<string, string>) {
10
10
  super();
11
- this.initValidation();
12
11
  }
13
12
 
14
13
  async initValidation() {
@@ -53,20 +52,21 @@ export class GCSProvider extends AbstractProvider {
53
52
  ): Promise<any> {
54
53
  const storage = await this.getClient();
55
54
  const bucket = storage.bucket(this.setting['storage-gcs-bucket']);
56
- const file = bucket.file(destination + filename);
55
+ const storedFilename = this.getFilename(filename);
56
+ const file = bucket.file([destination, storedFilename].join('/'));
57
57
 
58
- return file.save(fileContent, {
59
- contentType: mimetype,
60
- });
58
+ await file.save(fileContent, { contentType: mimetype });
59
+ return storedFilename;
61
60
  }
61
+
62
62
  async upload(destination: string, file: MulterFile): Promise<any> {
63
63
  const storage = await this.getClient();
64
64
  const bucket = storage.bucket(this.setting['storage-gcs-bucket']);
65
- const fileObject = bucket.file(destination + file.originalname);
65
+ const storedFilename = this.getFilename(file.originalname);
66
+ const fileObject = bucket.file([destination, storedFilename].join('/'));
66
67
 
67
- return fileObject.save(file.buffer, {
68
- contentType: file.mimetype,
69
- });
68
+ await fileObject.save(file.buffer, { contentType: file.mimetype });
69
+ return storedFilename;
70
70
  }
71
71
 
72
72
  async uploadFromPath(
@@ -13,7 +13,6 @@ export class LocalProvider extends AbstractProvider {
13
13
  private jwt: JwtService
14
14
  ) {
15
15
  super();
16
- this.initValidation();
17
16
  }
18
17
 
19
18
  async initValidation() {
@@ -40,7 +39,8 @@ export class LocalProvider extends AbstractProvider {
40
39
  }
41
40
 
42
41
  private resolveFilepath(filepath: string): string {
43
- return path.isAbsolute(filepath) ? filepath : path.resolve(filepath);
42
+ if (path.isAbsolute(filepath)) return filepath;
43
+ return join(this.setting['storage-local-path'], filepath);
44
44
  }
45
45
 
46
46
  async uploadFromString(
@@ -67,11 +67,12 @@ export class LocalProvider extends AbstractProvider {
67
67
  );
68
68
  }
69
69
 
70
- const filePath = join(storagePath, this.getFilename(filename));
70
+ const storedFilename = this.getFilename(filename);
71
+ const filePath = join(storagePath, storedFilename);
71
72
 
72
73
  await writeFile(filePath, fileContent);
73
74
 
74
- return filePath;
75
+ return storedFilename;
75
76
  }
76
77
 
77
78
  async upload(destination: string, file: MulterFile): Promise<any> {
@@ -92,11 +93,9 @@ export class LocalProvider extends AbstractProvider {
92
93
  `The storage path does not exist: ${storagePath}`,
93
94
  );
94
95
  }
95
- const filePath = join(storagePath, this.getFilename(file.originalname));
96
-
97
- await writeFile(filePath, file.buffer);
98
-
99
- return filePath;
96
+ const storedFilename = this.getFilename(file.originalname);
97
+ await writeFile(join(storagePath, storedFilename), file.buffer);
98
+ return storedFilename;
100
99
  }
101
100
 
102
101
  async uploadFromPath(
@@ -121,10 +120,8 @@ export class LocalProvider extends AbstractProvider {
121
120
  }
122
121
 
123
122
  const targetFilename = this.getFilename(options?.filename ?? basename(sourcePath));
124
- const targetPath = join(storagePath, targetFilename);
125
- await copyFile(sourcePath, targetPath);
126
-
127
- return targetPath;
123
+ await copyFile(sourcePath, join(storagePath, targetFilename));
124
+ return targetFilename;
128
125
  }
129
126
 
130
127
  async delete(filepath: string): Promise<any> {
@@ -8,22 +8,34 @@ import { S3Provider } from './s3.provider';
8
8
  import { SpacesProvider } from './spaces.provider';
9
9
 
10
10
  export class ProviderFactory {
11
- static create(
11
+ static async create(
12
12
  providerType: EnumProvider,
13
13
  setting: Record<string, string>,
14
14
  jwtService: JwtService,
15
- ): AbstractProvider {
15
+ ): Promise<AbstractProvider> {
16
+ let provider: AbstractProvider;
17
+
16
18
  switch (providerType) {
17
19
  case EnumProvider.S3:
18
- return new S3Provider(setting);
20
+ provider = new S3Provider(setting);
21
+ break;
19
22
  case EnumProvider.LOCAL:
20
- return new LocalProvider(setting, jwtService);
23
+ provider = new LocalProvider(setting, jwtService);
24
+ break;
21
25
  case EnumProvider.AZURE:
22
- return new AzureProvider(setting);
26
+ provider = new AzureProvider(setting);
27
+ break;
23
28
  case EnumProvider.GCS:
24
- return new GCSProvider(setting);
29
+ provider = new GCSProvider(setting);
30
+ break;
25
31
  case EnumProvider.SPACES:
26
- return new SpacesProvider(setting);
32
+ provider = new SpacesProvider(setting);
33
+ break;
34
+ default:
35
+ throw new Error(`Unsupported provider type: ${providerType}`);
27
36
  }
37
+
38
+ await provider.initValidation();
39
+ return provider;
28
40
  }
29
41
  }
@@ -20,28 +20,13 @@ export class S3Provider extends AbstractProvider {
20
20
 
21
21
  constructor(private setting: Record<string, string>) {
22
22
  super();
23
- this.initValidation();
24
23
  }
25
24
 
26
25
  private resolveS3Key(filepath: string): string {
27
26
  if (!filepath) {
28
27
  throw new Error('Invalid filepath for S3');
29
28
  }
30
-
31
- try {
32
- const url = new URL(filepath);
33
- const keyFromUrl = url.pathname.split('/').filter(Boolean).join('/');
34
- if (!keyFromUrl) {
35
- throw new Error(`Invalid filepath "${filepath}" for S3`);
36
- }
37
- return keyFromUrl;
38
- } catch {
39
- const keyFromPath = filepath.split('/').filter(Boolean).join('/');
40
- if (!keyFromPath) {
41
- throw new Error(`Invalid filepath "${filepath}" for S3`);
42
- }
43
- return keyFromPath;
44
- }
29
+ return filepath;
45
30
  }
46
31
 
47
32
  async buffer(filepath: string): Promise<Buffer> {
@@ -151,8 +136,7 @@ export class S3Provider extends AbstractProvider {
151
136
  Body: fileContent,
152
137
  });
153
138
  await s3.send(command);
154
- // S3 v3 não retorna Location diretamente, construa a URL:
155
- return `https://${this.setting['storage-s3-bucket']}.s3.${this.setting['storage-s3-region']}.amazonaws.com/${key}`;
139
+ return storedFilename;
156
140
  }
157
141
 
158
142
  async upload(destination: string, file: MulterFile): Promise<any> {
@@ -165,7 +149,7 @@ export class S3Provider extends AbstractProvider {
165
149
  Body: file.buffer,
166
150
  });
167
151
  await s3.send(command);
168
- return `https://${this.setting['storage-s3-bucket']}.s3.${this.setting['storage-s3-region']}.amazonaws.com/${key}`;
152
+ return storedFilename;
169
153
  }
170
154
 
171
155
  async uploadFromPath(
@@ -185,7 +169,7 @@ export class S3Provider extends AbstractProvider {
185
169
  });
186
170
 
187
171
  await s3.send(command);
188
- return `https://${this.setting['storage-s3-bucket']}.s3.${this.setting['storage-s3-region']}.amazonaws.com/${key}`;
172
+ return storedFilename;
189
173
  }
190
174
 
191
175
  async delete(filepath: string): Promise<any> {
@@ -20,7 +20,6 @@ export class SpacesProvider extends AbstractProvider {
20
20
 
21
21
  constructor(private setting: Record<string, string>) {
22
22
  super();
23
- this.initValidation();
24
23
  }
25
24
 
26
25
  private get endpoint() {
@@ -39,21 +38,7 @@ export class SpacesProvider extends AbstractProvider {
39
38
  if (!filepath) {
40
39
  throw new Error('Invalid filepath for DigitalOcean Spaces');
41
40
  }
42
-
43
- try {
44
- const url = new URL(filepath);
45
- const key = url.pathname.split('/').filter(Boolean).join('/');
46
- if (!key) {
47
- throw new Error(`Invalid filepath "${filepath}" for DigitalOcean Spaces`);
48
- }
49
- return key;
50
- } catch {
51
- const key = filepath.split('/').filter(Boolean).join('/');
52
- if (!key) {
53
- throw new Error(`Invalid filepath "${filepath}" for DigitalOcean Spaces`);
54
- }
55
- return key;
56
- }
41
+ return filepath;
57
42
  }
58
43
 
59
44
  async initValidation() {
@@ -169,7 +154,7 @@ export class SpacesProvider extends AbstractProvider {
169
154
  ACL: 'public-read',
170
155
  }),
171
156
  );
172
- return `${this.bucketUrl}/${key}`;
157
+ return storedFilename;
173
158
  }
174
159
 
175
160
  async uploadFromString(
@@ -188,7 +173,7 @@ export class SpacesProvider extends AbstractProvider {
188
173
  ACL: 'public-read',
189
174
  }),
190
175
  );
191
- return `${this.bucketUrl}/${key}`;
176
+ return storedFilename;
192
177
  }
193
178
 
194
179
  async uploadFromPath(
@@ -209,7 +194,7 @@ export class SpacesProvider extends AbstractProvider {
209
194
  ACL: 'public-read',
210
195
  }),
211
196
  );
212
- return `${this.bucketUrl}/${key}`;
197
+ return storedFilename;
213
198
  }
214
199
 
215
200
  async delete(filepath: string): Promise<any> {
@@ -54,11 +54,13 @@ export class IntegrationProfileController {
54
54
  @Pagination() paginationParams,
55
55
  @Query('typeId') typeId?: string,
56
56
  @Query('typeSlug') typeSlug?: string,
57
+ @Query('providerSlug') providerSlug?: string,
57
58
  ) {
58
59
  return this.integrationProfileService.list(
59
60
  paginationParams,
60
61
  typeId ? parseInt(typeId, 10) : undefined,
61
62
  typeSlug,
63
+ providerSlug,
62
64
  );
63
65
  }
64
66
 
@@ -100,10 +100,12 @@ export class IntegrationProfileService {
100
100
  paginationParams: PaginationDTO,
101
101
  typeId?: number,
102
102
  typeSlug?: string,
103
+ providerSlug?: string,
103
104
  ) {
104
105
  const fields = ['slug', 'name'];
105
106
  const OR = this.prismaService.createInsensitiveSearch(fields, paginationParams);
106
107
  const normalizedTypeSlug = String(typeSlug ?? '').trim();
108
+ const normalizedProviderSlug = String(providerSlug ?? '').trim();
107
109
 
108
110
  const result = await this.paginationService.paginate(
109
111
  this.prismaService.integration_profile,
@@ -115,6 +117,9 @@ export class IntegrationProfileService {
115
117
  ...(normalizedTypeSlug
116
118
  ? { integration_type: { slug: normalizedTypeSlug } }
117
119
  : {}),
120
+ ...(normalizedProviderSlug
121
+ ? { integration_provider: { slug: normalizedProviderSlug } }
122
+ : {}),
118
123
  },
119
124
  select: {
120
125
  id: true,
@@ -157,8 +162,8 @@ export class IntegrationProfileService {
157
162
  SELECT
158
163
  ip.id,
159
164
  (
160
- COALESCE((SELECT COUNT(*)::int FROM campaign WHERE mail_profile_id = ip.id), 0) +
161
- COALESCE((SELECT COUNT(*)::int FROM webhook_action WHERE mail_profile_id = ip.id), 0)
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)
162
167
  ) AS uses_count
163
168
  FROM integration_profile ip
164
169
  WHERE ip.id = ANY(${ids}::int[])
@@ -135,6 +135,23 @@ export function resolveFileProviderSlug(providerSlug: string): string {
135
135
 
136
136
  // ─── AI utilities ─────────────────────────────────────────────────────────────
137
137
 
138
+ // ─── OAuth utilities ──────────────────────────────────────────────────────────
139
+
140
+ /** Maps an integration_profile config JSON to OAuth client credentials. */
141
+ export function buildOAuthConfigFromIntegration(
142
+ _providerSlug: string,
143
+ config: unknown,
144
+ ): { clientId: string; clientSecret: string; tenantId?: string } {
145
+ const cfg = (config as Cfg) ?? {};
146
+ return {
147
+ clientId: String(cfg.client_id ?? ''),
148
+ clientSecret: String(cfg.client_secret ?? ''),
149
+ ...(cfg.tenant_id ? { tenantId: String(cfg.tenant_id) } : {}),
150
+ };
151
+ }
152
+
153
+ // ─── AI utilities ─────────────────────────────────────────────────────────────
154
+
138
155
  /** Maps an integration_profile config JSON to the API key and optional org needed by AI providers. */
139
156
  export function buildAiConfigFromIntegration(
140
157
  providerSlug: string,
@@ -15,12 +15,22 @@ import { MailService } from './mail.service';
15
15
  inject: [PrismaService],
16
16
  useFactory: async (prisma: PrismaService) => {
17
17
  try {
18
- const profileIdSetting = await prisma.setting.findUnique({
19
- where: { slug: 'mail-profile-id' },
20
- select: { value: true },
18
+ const profileSettings = await prisma.setting.findMany({
19
+ where: {
20
+ slug: {
21
+ in: ['mail-integration-profile-id'],
22
+ },
23
+ },
24
+ select: { slug: true, value: true },
21
25
  });
22
26
 
23
- const profileId = Number(profileIdSetting?.value);
27
+ const profileSettingsMap = new Map(
28
+ profileSettings.map((item) => [item.slug, item.value]),
29
+ );
30
+
31
+ const profileId = Number(
32
+ profileSettingsMap.get('mail-integration-profile-id'),
33
+ );
24
34
 
25
35
  if (!profileId || profileId <= 0) {
26
36
  return { global: true, type: 'SMTP', host: 'localhost', port: 1025, secure: false, username: '', password: '' } as MailModuleOptions;