@hed-hog/core 0.0.368 → 0.0.371

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 (271) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +2 -0
  3. package/dist/ai/ai.service.d.ts.map +1 -1
  4. package/dist/ai/ai.service.js +10 -7
  5. package/dist/ai/ai.service.js.map +1 -1
  6. package/dist/app-command/app-command.module.d.ts +3 -0
  7. package/dist/app-command/app-command.module.d.ts.map +1 -0
  8. package/dist/app-command/app-command.module.js +22 -0
  9. package/dist/app-command/app-command.module.js.map +1 -0
  10. package/dist/app-command/app-command.registry.d.ts +22 -0
  11. package/dist/app-command/app-command.registry.d.ts.map +1 -0
  12. package/dist/{webhook-integration/webhook-command.registry.js → app-command/app-command.registry.js} +6 -6
  13. package/dist/app-command/app-command.registry.js.map +1 -0
  14. package/dist/auth/auth.controller.d.ts +2 -2
  15. package/dist/auth/auth.controller.d.ts.map +1 -1
  16. package/dist/auth/auth.controller.js +20 -7
  17. package/dist/auth/auth.controller.js.map +1 -1
  18. package/dist/auth/auth.service.d.ts +3 -3
  19. package/dist/auth/auth.service.d.ts.map +1 -1
  20. package/dist/auth/auth.service.js +10 -36
  21. package/dist/auth/auth.service.js.map +1 -1
  22. package/dist/auth/dto/forget.dto.d.ts +1 -0
  23. package/dist/auth/dto/forget.dto.d.ts.map +1 -1
  24. package/dist/auth/dto/forget.dto.js +5 -0
  25. package/dist/auth/dto/forget.dto.js.map +1 -1
  26. package/dist/auth/guards/auth.guard.d.ts.map +1 -1
  27. package/dist/auth/guards/auth.guard.js +4 -1
  28. package/dist/auth/guards/auth.guard.js.map +1 -1
  29. package/dist/challenge/challenge.service.d.ts +8 -7
  30. package/dist/challenge/challenge.service.d.ts.map +1 -1
  31. package/dist/challenge/challenge.service.js +27 -19
  32. package/dist/challenge/challenge.service.js.map +1 -1
  33. package/dist/file/dto/upload.dto.d.ts +3 -0
  34. package/dist/file/dto/upload.dto.d.ts.map +1 -1
  35. package/dist/file/dto/upload.dto.js +22 -0
  36. package/dist/file/dto/upload.dto.js.map +1 -1
  37. package/dist/file/file.controller.d.ts +2 -1
  38. package/dist/file/file.controller.d.ts.map +1 -1
  39. package/dist/file/file.controller.js +29 -16
  40. package/dist/file/file.controller.js.map +1 -1
  41. package/dist/file/file.service.d.ts +51 -1
  42. package/dist/file/file.service.d.ts.map +1 -1
  43. package/dist/file/file.service.js +250 -18
  44. package/dist/file/file.service.js.map +1 -1
  45. package/dist/file/image-optimization.service.d.ts +17 -4
  46. package/dist/file/image-optimization.service.d.ts.map +1 -1
  47. package/dist/file/image-optimization.service.js +101 -46
  48. package/dist/file/image-optimization.service.js.map +1 -1
  49. package/dist/file/provider/s3.provider.d.ts.map +1 -1
  50. package/dist/file/provider/s3.provider.js +15 -7
  51. package/dist/file/provider/s3.provider.js.map +1 -1
  52. package/dist/file/provider/spaces.provider.d.ts.map +1 -1
  53. package/dist/file/provider/spaces.provider.js +18 -8
  54. package/dist/file/provider/spaces.provider.js.map +1 -1
  55. package/dist/index.d.ts +4 -1
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +4 -1
  58. package/dist/index.js.map +1 -1
  59. package/dist/integration/services/event-subscriber.registry.d.ts +5 -0
  60. package/dist/integration/services/event-subscriber.registry.d.ts.map +1 -1
  61. package/dist/integration/services/event-subscriber.registry.js +15 -0
  62. package/dist/integration/services/event-subscriber.registry.js.map +1 -1
  63. package/dist/integration/services/outbox.processor.d.ts.map +1 -1
  64. package/dist/integration/services/outbox.processor.js +2 -4
  65. package/dist/integration/services/outbox.processor.js.map +1 -1
  66. package/dist/integration/services/outbox.service.d.ts +4 -2
  67. package/dist/integration/services/outbox.service.d.ts.map +1 -1
  68. package/dist/integration/services/outbox.service.js +26 -15
  69. package/dist/integration/services/outbox.service.js.map +1 -1
  70. package/dist/integration-profile/integration-credential-crypto.service.d.ts +43 -0
  71. package/dist/integration-profile/integration-credential-crypto.service.d.ts.map +1 -0
  72. package/dist/integration-profile/integration-credential-crypto.service.js +142 -0
  73. package/dist/integration-profile/integration-credential-crypto.service.js.map +1 -0
  74. package/dist/integration-profile/integration-profile.controller.d.ts +6 -7
  75. package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
  76. package/dist/integration-profile/integration-profile.controller.js +4 -3
  77. package/dist/integration-profile/integration-profile.controller.js.map +1 -1
  78. package/dist/integration-profile/integration-profile.module.d.ts.map +1 -1
  79. package/dist/integration-profile/integration-profile.module.js +5 -2
  80. package/dist/integration-profile/integration-profile.module.js.map +1 -1
  81. package/dist/integration-profile/integration-profile.secrets.d.ts +16 -0
  82. package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -0
  83. package/dist/integration-profile/integration-profile.secrets.js +56 -0
  84. package/dist/integration-profile/integration-profile.secrets.js.map +1 -0
  85. package/dist/integration-profile/integration-profile.service.d.ts +125 -8
  86. package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
  87. package/dist/integration-profile/integration-profile.service.js +257 -68
  88. package/dist/integration-profile/integration-profile.service.js.map +1 -1
  89. package/dist/integration-profile/integration-profile.utils.d.ts +17 -0
  90. package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
  91. package/dist/integration-profile/integration-profile.utils.js +31 -0
  92. package/dist/integration-profile/integration-profile.utils.js.map +1 -1
  93. package/dist/language/en.json +178 -0
  94. package/dist/language/pt.json +178 -0
  95. package/dist/mail/mail.controller.d.ts.map +1 -1
  96. package/dist/mail/mail.controller.js +5 -28
  97. package/dist/mail/mail.controller.js.map +1 -1
  98. package/dist/mail/mail.module.js +18 -18
  99. package/dist/mail/mail.module.js.map +1 -1
  100. package/dist/mail/mail.service.d.ts +13 -2
  101. package/dist/mail/mail.service.d.ts.map +1 -1
  102. package/dist/mail/mail.service.js +25 -15
  103. package/dist/mail/mail.service.js.map +1 -1
  104. package/dist/mcp-chat/mcp-chat.service.js +6 -5
  105. package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
  106. package/dist/oauth/providers/facebook.provider.js +5 -5
  107. package/dist/oauth/providers/facebook.provider.js.map +1 -1
  108. package/dist/oauth/providers/github.provider.js +5 -5
  109. package/dist/oauth/providers/github.provider.js.map +1 -1
  110. package/dist/oauth/providers/google.provider.js +5 -5
  111. package/dist/oauth/providers/google.provider.js.map +1 -1
  112. package/dist/oauth/providers/microsoft-entra-id.provider.js +5 -5
  113. package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
  114. package/dist/oauth/providers/microsoft.provider.js +5 -5
  115. package/dist/oauth/providers/microsoft.provider.js.map +1 -1
  116. package/dist/pdf/pdf.service.d.ts +4 -0
  117. package/dist/pdf/pdf.service.d.ts.map +1 -1
  118. package/dist/pdf/pdf.service.js +16 -9
  119. package/dist/pdf/pdf.service.js.map +1 -1
  120. package/dist/profile/profile.controller.d.ts +7 -7
  121. package/dist/profile/profile.controller.d.ts.map +1 -1
  122. package/dist/profile/profile.controller.js +28 -21
  123. package/dist/profile/profile.controller.js.map +1 -1
  124. package/dist/profile/profile.service.d.ts +7 -7
  125. package/dist/profile/profile.service.d.ts.map +1 -1
  126. package/dist/profile/profile.service.js +21 -25
  127. package/dist/profile/profile.service.js.map +1 -1
  128. package/dist/setting/setting.service.d.ts +1 -0
  129. package/dist/setting/setting.service.d.ts.map +1 -1
  130. package/dist/setting/setting.service.js +15 -2
  131. package/dist/setting/setting.service.js.map +1 -1
  132. package/dist/token/token.service.d.ts.map +1 -1
  133. package/dist/token/token.service.js +2 -1
  134. package/dist/token/token.service.js.map +1 -1
  135. package/dist/user/user.controller.d.ts +9 -0
  136. package/dist/user/user.controller.d.ts.map +1 -1
  137. package/dist/user/user.controller.js +13 -0
  138. package/dist/user/user.controller.js.map +1 -1
  139. package/dist/user/user.service.d.ts +7 -0
  140. package/dist/user/user.service.d.ts.map +1 -1
  141. package/dist/user/user.service.js +19 -0
  142. package/dist/user/user.service.js.map +1 -1
  143. package/dist/utils/webauthn-origin.d.ts +11 -0
  144. package/dist/utils/webauthn-origin.d.ts.map +1 -0
  145. package/dist/utils/webauthn-origin.js +37 -0
  146. package/dist/utils/webauthn-origin.js.map +1 -0
  147. package/dist/webhook-integration/webhook-integration.controller.d.ts +13 -0
  148. package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
  149. package/dist/webhook-integration/webhook-integration.controller.js +29 -0
  150. package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
  151. package/dist/webhook-integration/webhook-integration.module.js +3 -3
  152. package/dist/webhook-integration/webhook-integration.module.js.map +1 -1
  153. package/dist/webhook-integration/webhook-integration.service.d.ts +18 -2
  154. package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
  155. package/dist/webhook-integration/webhook-integration.service.js +110 -6
  156. package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
  157. package/hedhog/data/integration_event_catalog.yaml +118 -118
  158. package/hedhog/data/integration_provider.yaml +9 -0
  159. package/hedhog/data/integration_type.yaml +6 -0
  160. package/hedhog/data/menu.yaml +6 -0
  161. package/hedhog/data/role.yaml +7 -0
  162. package/hedhog/data/route.yaml +192 -207
  163. package/hedhog/data/setting_group.yaml +119 -13
  164. package/hedhog/frontend/app/access-log/page.tsx.ejs +35 -3
  165. package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +36 -3
  166. package/hedhog/frontend/app/account/components/mcp-tokens.tsx.ejs +39 -4
  167. package/hedhog/frontend/app/account/components/tokens.tsx.ejs +39 -4
  168. package/hedhog/frontend/app/ai_agent/page.tsx.ejs +35 -2
  169. package/hedhog/frontend/app/configurations/[slug]/components/byte-size-input.tsx.ejs +160 -0
  170. package/hedhog/frontend/app/configurations/[slug]/components/duration-input.tsx.ejs +157 -0
  171. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +244 -63
  172. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +33 -1
  173. package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +35 -3
  174. package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +36 -4
  175. package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +35 -3
  176. package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +36 -4
  177. package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +35 -3
  178. package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +141 -29
  179. package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +118 -11
  180. package/hedhog/frontend/app/mail/log/page.tsx.ejs +37 -4
  181. package/hedhog/frontend/app/menu/page.tsx.ejs +34 -2
  182. package/hedhog/frontend/app/preferences/page.tsx.ejs +77 -0
  183. package/hedhog/frontend/app/roles/menus.tsx.ejs +35 -3
  184. package/hedhog/frontend/app/roles/page.tsx.ejs +34 -2
  185. package/hedhog/frontend/app/roles/routes.tsx.ejs +36 -4
  186. package/hedhog/frontend/app/roles/users.tsx.ejs +36 -4
  187. package/hedhog/frontend/app/routes/page.tsx.ejs +34 -2
  188. package/hedhog/frontend/app/setting-list/page.tsx.ejs +34 -2
  189. package/hedhog/frontend/app/users/page.tsx.ejs +51 -21
  190. package/hedhog/frontend/messages/en.json +69 -4
  191. package/hedhog/frontend/messages/pt.json +69 -4
  192. package/hedhog/table/event_webhook.yaml +55 -55
  193. package/hedhog/table/event_webhook_delivery_log.yaml +66 -66
  194. package/hedhog/table/event_webhook_event.yaml +26 -26
  195. package/hedhog/table/integration_event_catalog.yaml +41 -41
  196. package/hedhog/table/mcp_conversation.yaml +12 -12
  197. package/hedhog/table/mcp_conversation_message.yaml +23 -23
  198. package/hedhog/table/mcp_tool_call_log.yaml +22 -22
  199. package/hedhog/table/route.yaml +33 -33
  200. package/hedhog/table/setting.yaml +2 -0
  201. package/hedhog/table/webhook_call_log.yaml +3 -0
  202. package/hedhog/table/webhook_integration.yaml +43 -43
  203. package/package.json +9 -4
  204. package/src/ai/ai.service.ts +9 -6
  205. package/src/app-command/app-command.module.ts +9 -0
  206. package/src/{webhook-integration/webhook-command.registry.ts → app-command/app-command.registry.ts} +9 -9
  207. package/src/auth/auth.controller.ts +20 -4
  208. package/src/auth/auth.service.ts +10 -36
  209. package/src/auth/dto/forget.dto.ts +5 -1
  210. package/src/auth/guards/auth.guard.ts +4 -1
  211. package/src/challenge/challenge.service.ts +30 -19
  212. package/src/file/dto/upload.dto.ts +25 -1
  213. package/src/file/file.controller.ts +31 -18
  214. package/src/file/file.service.ts +341 -16
  215. package/src/file/image-optimization.service.ts +111 -46
  216. package/src/file/provider/s3.provider.ts +16 -7
  217. package/src/file/provider/spaces.provider.ts +15 -5
  218. package/src/index.ts +4 -1
  219. package/src/integration/services/event-subscriber.registry.ts +15 -0
  220. package/src/integration/services/outbox.processor.ts +2 -5
  221. package/src/integration/services/outbox.service.ts +35 -17
  222. package/src/integration/types/event.types.ts +4 -4
  223. package/src/integration-profile/integration-credential-crypto.service.ts +146 -0
  224. package/src/integration-profile/integration-profile.controller.ts +2 -0
  225. package/src/integration-profile/integration-profile.module.ts +5 -2
  226. package/src/integration-profile/integration-profile.secrets.ts +51 -0
  227. package/src/integration-profile/integration-profile.service.ts +335 -54
  228. package/src/integration-profile/integration-profile.utils.ts +56 -0
  229. package/src/language/en.json +5 -1
  230. package/src/language/pt.json +5 -1
  231. package/src/mail/mail.controller.ts +5 -29
  232. package/src/mail/mail.module.ts +5 -5
  233. package/src/mail/mail.service.ts +37 -13
  234. package/src/mcp/decorators/mcp-tool.decorator.ts +7 -7
  235. package/src/mcp/guards/mcp-permission.guard.ts +51 -51
  236. package/src/mcp/interfaces/mcp-tool-definition.interface.ts +6 -6
  237. package/src/mcp/mcp-server.service.ts +87 -87
  238. package/src/mcp/types/mcp-context.type.ts +6 -6
  239. package/src/mcp-chat/dto/create-conversation.dto.ts +7 -7
  240. package/src/mcp-chat/dto/send-message.dto.ts +7 -7
  241. package/src/mcp-chat/mcp-chat.service.ts +5 -5
  242. package/src/notification/events/notification-stale-progress-check.event.ts +5 -5
  243. package/src/oauth/oauth-callback-coordinator.service.spec.ts +159 -0
  244. package/src/oauth/providers/facebook.provider.ts +3 -3
  245. package/src/oauth/providers/github.provider.ts +3 -3
  246. package/src/oauth/providers/google.provider.ts +3 -3
  247. package/src/oauth/providers/microsoft-entra-id.provider.ts +3 -3
  248. package/src/oauth/providers/microsoft.provider.ts +3 -3
  249. package/src/pdf/pdf.service.ts +19 -6
  250. package/src/profile/profile.controller.ts +21 -14
  251. package/src/profile/profile.service.ts +23 -20
  252. package/src/route/dto/create.dto.ts +32 -32
  253. package/src/route/dto/update.dto.ts +31 -31
  254. package/src/security/security.service.spec.ts +222 -0
  255. package/src/setting/setting.service.ts +15 -2
  256. package/src/token/token.service.ts +2 -1
  257. package/src/user/user.controller.ts +26 -16
  258. package/src/user/user.service.ts +25 -0
  259. package/src/utils/webauthn-origin.ts +38 -0
  260. package/src/webhook-integration/integration-event-catalog.controller.ts +50 -50
  261. package/src/webhook-integration/integration-event-catalog.service.ts +117 -117
  262. package/src/webhook-integration/webhook-integration.controller.ts +23 -0
  263. package/src/webhook-integration/webhook-integration.module.ts +3 -3
  264. package/src/webhook-integration/webhook-integration.service.ts +115 -5
  265. package/dist/webhook-integration/webhook-command.registry.d.ts +0 -22
  266. package/dist/webhook-integration/webhook-command.registry.d.ts.map +0 -1
  267. package/dist/webhook-integration/webhook-command.registry.js.map +0 -1
  268. package/src/menu/menu.service.spec.ts +0 -247
  269. package/src/route/route.service.spec.ts +0 -300
  270. package/src/screen/screen.service.spec.ts +0 -298
  271. package/src/user/user.service.spec.ts +0 -294
@@ -1,5 +1,6 @@
1
1
  import { DeleteDTO, Role } from '@hed-hog/api';
2
2
  import { Locale } from '@hed-hog/api-locale';
3
+ import { renderMailTemplate } from '@hed-hog/api-mail';
3
4
  import { Pagination } from '@hed-hog/api-pagination';
4
5
  import {
5
6
  Body,
@@ -56,35 +57,10 @@ export class MailController {
56
57
  ) {
57
58
  const locale = queryLocale || decoratorLocale;
58
59
  const mail = await this.mailService.get(locale, id);
59
-
60
- const html = `<!DOCTYPE html>
61
- <html>
62
- <head>
63
- <meta charset="UTF-8">
64
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
65
- <title>${mail.subject}</title>
66
- <style>
67
- body {
68
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
69
- line-height: 1.6;
70
- color: #333;
71
- max-width: 600px;
72
- margin: 0 auto;
73
- padding: 20px;
74
- }
75
- a {
76
- color: #0066cc;
77
- text-decoration: none;
78
- }
79
- a:hover {
80
- text-decoration: underline;
81
- }
82
- </style>
83
- </head>
84
- <body>
85
- ${mail.body}
86
- </body>
87
- </html>`;
60
+ const html = renderMailTemplate({
61
+ subject: mail.subject,
62
+ body: mail.body,
63
+ });
88
64
 
89
65
  res.setHeader('Content-Disposition', `attachment; filename="${mail.slug}-${locale}.html"`);
90
66
  res.send(html);
@@ -28,16 +28,16 @@ import { MailService } from './mail.service';
28
28
  profileSettings.map((item) => [item.slug, item.value]),
29
29
  );
30
30
 
31
- const profileId = Number(
32
- profileSettingsMap.get('mail-integration-profile-id'),
33
- );
31
+ const profileSlug = String(
32
+ profileSettingsMap.get('mail-integration-profile-id') ?? '',
33
+ ).trim();
34
34
 
35
- if (!profileId || profileId <= 0) {
35
+ if (!profileSlug) {
36
36
  return { global: true, type: 'SMTP', host: 'localhost', port: 1025, secure: false, username: '', password: '' } as MailModuleOptions;
37
37
  }
38
38
 
39
39
  const profile = await (prisma as any).integration_profile.findUnique({
40
- where: { id: profileId },
40
+ where: { slug: profileSlug },
41
41
  include: {
42
42
  integration_provider: { select: { slug: true } },
43
43
  integration_type: { select: { slug: true } },
@@ -76,20 +76,22 @@ export class MailService implements OnModuleInit {
76
76
  const settings = await this.setting.getSettingValues([
77
77
  this.integrationProfileSettingSlug,
78
78
  ]);
79
- const profileId = settings[this.integrationProfileSettingSlug];
79
+ const profileSlug = String(
80
+ settings[this.integrationProfileSettingSlug] ?? '',
81
+ ).trim();
80
82
 
81
- if (!profileId || Number(profileId) <= 0) {
83
+ if (!profileSlug) {
82
84
  await this.setting.setValue('configured-mail', 'false');
83
85
  return;
84
86
  }
85
87
 
86
- const { from, config } = await this.reloadConfig();
88
+ const { from } = await this.reloadConfig();
87
89
 
88
90
  await this.mailMainService.send({
89
91
  to: from,
90
92
  from,
91
93
  subject: '[HedHog] Mail configuration validation',
92
- body: `This is an automatic validation email for the configured profile (id: ${profileId}).`,
94
+ body: `This is an automatic validation email for the configured profile (slug: ${profileSlug}).`,
93
95
  });
94
96
 
95
97
  await this.setting.setValue('configured-mail', 'true');
@@ -103,27 +105,29 @@ export class MailService implements OnModuleInit {
103
105
 
104
106
  /**
105
107
  * Reload mail configuration from database and update MailMainService.
106
- * When profileId is provided, that profile is used directly.
108
+ * When profileSlug is provided, that profile is used directly.
107
109
  * Otherwise the global integration profile setting is read.
108
110
  * Returns the active from address and the built config object.
109
111
  */
110
- async reloadConfig(profileId?: number): Promise<{ success: boolean; provider: string; from: string; config: any }> {
112
+ async reloadConfig(profileSlug?: string): Promise<{ success: boolean; provider: string; from: string; config: any }> {
111
113
  try {
112
- let resolvedProfileId = profileId;
114
+ let resolvedProfileSlug = String(profileSlug ?? '').trim();
113
115
 
114
- if (!resolvedProfileId) {
116
+ if (!resolvedProfileSlug) {
115
117
  const settings = await this.setting.getSettingValues([
116
118
  this.integrationProfileSettingSlug,
117
119
  ]);
118
- resolvedProfileId = Number(settings[this.integrationProfileSettingSlug]);
120
+ resolvedProfileSlug = String(
121
+ settings[this.integrationProfileSettingSlug] ?? '',
122
+ ).trim();
119
123
  }
120
124
 
121
- if (!resolvedProfileId || resolvedProfileId <= 0) {
125
+ if (!resolvedProfileSlug) {
122
126
  throw new BadRequestException('No mail profile configured. Set a mail profile in the Mail settings.');
123
127
  }
124
128
 
125
129
  const profile = await this.prismaService.integration_profile.findUnique({
126
- where: { id: resolvedProfileId },
130
+ where: { slug: resolvedProfileSlug },
127
131
  include: {
128
132
  integration_provider: { select: { slug: true } },
129
133
  integration_type: { select: { slug: true } },
@@ -131,12 +135,12 @@ export class MailService implements OnModuleInit {
131
135
  });
132
136
 
133
137
  if (!profile) {
134
- throw new NotFoundException(`Mail profile with id ${profileId} not found.`);
138
+ throw new NotFoundException(`Mail profile with slug "${resolvedProfileSlug}" not found.`);
135
139
  }
136
140
 
137
141
  if (profile.integration_type?.slug !== 'email') {
138
142
  throw new BadRequestException(
139
- `Integration profile ${resolvedProfileId} is not an email profile.`,
143
+ `Integration profile "${resolvedProfileSlug}" is not an email profile.`,
140
144
  );
141
145
  }
142
146
 
@@ -155,6 +159,26 @@ export class MailService implements OnModuleInit {
155
159
  }
156
160
  }
157
161
 
162
+ /**
163
+ * Sends an ad-hoc HTML email using the configured (or given) mail profile,
164
+ * without requiring a stored template. Used by other modules (e.g. agent
165
+ * human-approval notifications).
166
+ */
167
+ async sendRawMail({
168
+ to,
169
+ subject,
170
+ html,
171
+ profileSlug,
172
+ }: {
173
+ to: string;
174
+ subject: string;
175
+ html: string;
176
+ profileSlug?: string;
177
+ }) {
178
+ const { from } = await this.reloadConfig(profileSlug);
179
+ return this.mailMainService.send({ to, from, subject, body: html } as any);
180
+ }
181
+
158
182
 
159
183
  async list(locale: string, paginationParams: PaginationDTO) {
160
184
  const result = await this.localeService.listModelWithLocale(
@@ -1,7 +1,7 @@
1
- import { SetMetadata } from '@nestjs/common';
2
- import { McpToolDefinition } from '../interfaces/mcp-tool-definition.interface';
3
-
4
- export const MCP_TOOL_METADATA = 'mcp:tool';
5
-
6
- export const McpTool = (definition: McpToolDefinition): MethodDecorator =>
7
- SetMetadata(MCP_TOOL_METADATA, definition);
1
+ import { SetMetadata } from '@nestjs/common';
2
+ import { McpToolDefinition } from '../interfaces/mcp-tool-definition.interface';
3
+
4
+ export const MCP_TOOL_METADATA = 'mcp:tool';
5
+
6
+ export const McpTool = (definition: McpToolDefinition): MethodDecorator =>
7
+ SetMetadata(MCP_TOOL_METADATA, definition);
@@ -1,51 +1,51 @@
1
- import { PrismaService } from '@hed-hog/api-prisma';
2
- import {
3
- CanActivate,
4
- ExecutionContext,
5
- ForbiddenException,
6
- Injectable,
7
- } from '@nestjs/common';
8
- import { Request } from 'express';
9
-
10
- @Injectable()
11
- export class McpPermissionGuard implements CanActivate {
12
- constructor(private readonly prisma: PrismaService) {}
13
-
14
- async canActivate(context: ExecutionContext): Promise<boolean> {
15
- const request = context.switchToHttp().getRequest<Request>();
16
-
17
- // Only POST requests carry tool invocations
18
- if (request.method !== 'POST') return true;
19
-
20
- const body = request.body as any;
21
- const toolName: string | undefined =
22
- body?.method === 'tools/call' ? body?.params?.name : undefined;
23
-
24
- // tools/list, initialize, ping, etc. — no per-tool permission check needed
25
- if (!toolName) return true;
26
-
27
- const userId = (request as any)?.auth?.sub;
28
-
29
- const count = await this.prisma.route.count({
30
- where: {
31
- type: 'MCP' as any,
32
- tool_name: toolName,
33
- role_route: {
34
- some: {
35
- role: {
36
- role_user: {
37
- some: { user_id: userId },
38
- },
39
- },
40
- },
41
- },
42
- },
43
- });
44
-
45
- if (count === 0) {
46
- throw new ForbiddenException(`Access denied to MCP tool '${toolName}'`);
47
- }
48
-
49
- return true;
50
- }
51
- }
1
+ import { PrismaService } from '@hed-hog/api-prisma';
2
+ import {
3
+ CanActivate,
4
+ ExecutionContext,
5
+ ForbiddenException,
6
+ Injectable,
7
+ } from '@nestjs/common';
8
+ import { Request } from 'express';
9
+
10
+ @Injectable()
11
+ export class McpPermissionGuard implements CanActivate {
12
+ constructor(private readonly prisma: PrismaService) {}
13
+
14
+ async canActivate(context: ExecutionContext): Promise<boolean> {
15
+ const request = context.switchToHttp().getRequest<Request>();
16
+
17
+ // Only POST requests carry tool invocations
18
+ if (request.method !== 'POST') return true;
19
+
20
+ const body = request.body as any;
21
+ const toolName: string | undefined =
22
+ body?.method === 'tools/call' ? body?.params?.name : undefined;
23
+
24
+ // tools/list, initialize, ping, etc. — no per-tool permission check needed
25
+ if (!toolName) return true;
26
+
27
+ const userId = (request as any)?.auth?.sub;
28
+
29
+ const count = await this.prisma.route.count({
30
+ where: {
31
+ type: 'MCP' as any,
32
+ tool_name: toolName,
33
+ role_route: {
34
+ some: {
35
+ role: {
36
+ role_user: {
37
+ some: { user_id: userId },
38
+ },
39
+ },
40
+ },
41
+ },
42
+ },
43
+ });
44
+
45
+ if (count === 0) {
46
+ throw new ForbiddenException(`Access denied to MCP tool '${toolName}'`);
47
+ }
48
+
49
+ return true;
50
+ }
51
+ }
@@ -1,6 +1,6 @@
1
- export interface McpToolDefinition {
2
- name: string;
3
- description: string;
4
- inputSchema?: Record<string, any>;
5
- readOnly?: boolean;
6
- }
1
+ export interface McpToolDefinition {
2
+ name: string;
3
+ description: string;
4
+ inputSchema?: Record<string, any>;
5
+ readOnly?: boolean;
6
+ }
@@ -1,87 +1,87 @@
1
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
- import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
3
- import {
4
- CallToolRequestSchema,
5
- ListToolsRequestSchema,
6
- } from '@modelcontextprotocol/sdk/types.js';
7
- import { Injectable } from '@nestjs/common';
8
- import { Request, Response } from 'express';
9
- import { McpAuditService } from './mcp-audit.service';
10
- import { McpRegistryService } from './mcp-registry.service';
11
- import { McpContext } from './types/mcp-context.type';
12
-
13
- @Injectable()
14
- export class McpServerService {
15
- constructor(
16
- private readonly registry: McpRegistryService,
17
- private readonly audit: McpAuditService,
18
- ) {}
19
-
20
- async handleRequest(req: Request, res: Response, context: McpContext): Promise<void> {
21
- const server = this.buildServer(context);
22
- const transport = new StreamableHTTPServerTransport({
23
- sessionIdGenerator: undefined,
24
- });
25
- await server.connect(transport);
26
- await transport.handleRequest(req, res, req.body);
27
- }
28
-
29
- private buildServer(context: McpContext): Server {
30
- const server = new Server(
31
- { name: 'hedhog-mcp', version: '1.0.0' },
32
- { capabilities: { tools: {} } },
33
- );
34
-
35
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
36
- tools: this.registry.getAll().map((e) => ({
37
- name: e.definition.name,
38
- description: e.definition.description,
39
- inputSchema: e.definition.inputSchema ?? {
40
- type: 'object',
41
- properties: {},
42
- },
43
- })),
44
- }));
45
-
46
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
47
- const toolName = request.params.name;
48
- const args = (request.params.arguments ?? {}) as Record<string, any>;
49
- const entry = this.registry.get(toolName);
50
-
51
- if (!entry) {
52
- return {
53
- isError: true,
54
- content: [{ type: 'text', text: `Tool '${toolName}' not found` }],
55
- };
56
- }
57
-
58
- const start = Date.now();
59
- try {
60
- const result = await entry.handler(args, { ...context, toolName });
61
- await this.audit.record({
62
- userId: context.userId,
63
- toolName,
64
- input: args,
65
- success: true,
66
- durationMs: Date.now() - start,
67
- });
68
- return { content: [{ type: 'text', text: JSON.stringify(result) }] };
69
- } catch (e: any) {
70
- await this.audit.record({
71
- userId: context.userId,
72
- toolName,
73
- input: args,
74
- success: false,
75
- error: e.message,
76
- durationMs: Date.now() - start,
77
- });
78
- return {
79
- isError: true,
80
- content: [{ type: 'text', text: e.message }],
81
- };
82
- }
83
- });
84
-
85
- return server;
86
- }
87
- }
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
3
+ import {
4
+ CallToolRequestSchema,
5
+ ListToolsRequestSchema,
6
+ } from '@modelcontextprotocol/sdk/types.js';
7
+ import { Injectable } from '@nestjs/common';
8
+ import { Request, Response } from 'express';
9
+ import { McpAuditService } from './mcp-audit.service';
10
+ import { McpRegistryService } from './mcp-registry.service';
11
+ import { McpContext } from './types/mcp-context.type';
12
+
13
+ @Injectable()
14
+ export class McpServerService {
15
+ constructor(
16
+ private readonly registry: McpRegistryService,
17
+ private readonly audit: McpAuditService,
18
+ ) {}
19
+
20
+ async handleRequest(req: Request, res: Response, context: McpContext): Promise<void> {
21
+ const server = this.buildServer(context);
22
+ const transport = new StreamableHTTPServerTransport({
23
+ sessionIdGenerator: undefined,
24
+ });
25
+ await server.connect(transport);
26
+ await transport.handleRequest(req, res, req.body);
27
+ }
28
+
29
+ private buildServer(context: McpContext): Server {
30
+ const server = new Server(
31
+ { name: 'hedhog-mcp', version: '1.0.0' },
32
+ { capabilities: { tools: {} } },
33
+ );
34
+
35
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
36
+ tools: this.registry.getAll().map((e) => ({
37
+ name: e.definition.name,
38
+ description: e.definition.description,
39
+ inputSchema: e.definition.inputSchema ?? {
40
+ type: 'object',
41
+ properties: {},
42
+ },
43
+ })),
44
+ }));
45
+
46
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
47
+ const toolName = request.params.name;
48
+ const args = (request.params.arguments ?? {}) as Record<string, any>;
49
+ const entry = this.registry.get(toolName);
50
+
51
+ if (!entry) {
52
+ return {
53
+ isError: true,
54
+ content: [{ type: 'text', text: `Tool '${toolName}' not found` }],
55
+ };
56
+ }
57
+
58
+ const start = Date.now();
59
+ try {
60
+ const result = await entry.handler(args, { ...context, toolName });
61
+ await this.audit.record({
62
+ userId: context.userId,
63
+ toolName,
64
+ input: args,
65
+ success: true,
66
+ durationMs: Date.now() - start,
67
+ });
68
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
69
+ } catch (e: any) {
70
+ await this.audit.record({
71
+ userId: context.userId,
72
+ toolName,
73
+ input: args,
74
+ success: false,
75
+ error: e.message,
76
+ durationMs: Date.now() - start,
77
+ });
78
+ return {
79
+ isError: true,
80
+ content: [{ type: 'text', text: e.message }],
81
+ };
82
+ }
83
+ });
84
+
85
+ return server;
86
+ }
87
+ }
@@ -1,6 +1,6 @@
1
- export type McpContext = {
2
- userId: number;
3
- sessionId: string;
4
- locale: string;
5
- toolName: string;
6
- };
1
+ export type McpContext = {
2
+ userId: number;
3
+ sessionId: string;
4
+ locale: string;
5
+ toolName: string;
6
+ };
@@ -1,7 +1,7 @@
1
- import { IsOptional, IsString } from 'class-validator';
2
-
3
- export class CreateConversationDTO {
4
- @IsOptional()
5
- @IsString()
6
- title?: string;
7
- }
1
+ import { IsOptional, IsString } from 'class-validator';
2
+
3
+ export class CreateConversationDTO {
4
+ @IsOptional()
5
+ @IsString()
6
+ title?: string;
7
+ }
@@ -1,7 +1,7 @@
1
- import { IsNotEmpty, IsString } from 'class-validator';
2
-
3
- export class SendMessageDTO {
4
- @IsString()
5
- @IsNotEmpty()
6
- message: string;
7
- }
1
+ import { IsNotEmpty, IsString } from 'class-validator';
2
+
3
+ export class SendMessageDTO {
4
+ @IsString()
5
+ @IsNotEmpty()
6
+ message: string;
7
+ }
@@ -860,28 +860,28 @@ export class McpChatService {
860
860
 
861
861
  private async getProviderKey(): Promise<{ provider: 'openai' | 'gemini'; apiKey: string }> {
862
862
  const settings = await this.setting.getSettingValues(['mcp-ai-profile-id']);
863
- const profileId = Number(settings['mcp-ai-profile-id']);
863
+ const profileSlug = String(settings['mcp-ai-profile-id'] ?? '').trim();
864
864
 
865
- if (!profileId) {
865
+ if (!profileSlug) {
866
866
  throw new BadRequestException(
867
867
  'No MCP AI profile configured. Select one in Settings → MCP.',
868
868
  );
869
869
  }
870
870
 
871
871
  const profile = await this.prisma.integration_profile.findUnique({
872
- where: { id: profileId },
872
+ where: { slug: profileSlug },
873
873
  include: { integration_provider: { select: { slug: true } } },
874
874
  });
875
875
 
876
876
  if (!profile) {
877
- throw new BadRequestException(`MCP AI profile ${profileId} not found.`);
877
+ throw new BadRequestException(`MCP AI profile "${profileSlug}" not found.`);
878
878
  }
879
879
 
880
880
  const providerSlug = profile.integration_provider.slug;
881
881
  const { apiKey } = buildAiConfigFromIntegration(providerSlug, profile.config);
882
882
 
883
883
  if (!apiKey) {
884
- throw new BadRequestException(`MCP AI profile ${profileId} has no API key configured.`);
884
+ throw new BadRequestException(`MCP AI profile "${profileSlug}" has no API key configured.`);
885
885
  }
886
886
 
887
887
  return { provider: providerSlug === 'gemini' ? 'gemini' : 'openai', apiKey };
@@ -1,5 +1,5 @@
1
- export const NOTIFICATION_STALE_PROGRESS_CHECK = 'notification.stale-progress-check';
2
-
3
- export class NotificationStaleProgressCheckEvent {
4
- constructor(public readonly userId: number) {}
5
- }
1
+ export const NOTIFICATION_STALE_PROGRESS_CHECK = 'notification.stale-progress-check';
2
+
3
+ export class NotificationStaleProgressCheckEvent {
4
+ constructor(public readonly userId: number) {}
5
+ }