@hed-hog/core 0.0.350 → 0.0.351

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 (284) hide show
  1. package/dist/access-log/access-log.controller.d.ts +16 -0
  2. package/dist/access-log/access-log.controller.d.ts.map +1 -0
  3. package/dist/access-log/access-log.controller.js +44 -0
  4. package/dist/access-log/access-log.controller.js.map +1 -0
  5. package/dist/access-log/access-log.interceptor.d.ts +9 -0
  6. package/dist/access-log/access-log.interceptor.d.ts.map +1 -0
  7. package/dist/access-log/access-log.interceptor.js +46 -0
  8. package/dist/access-log/access-log.interceptor.js.map +1 -0
  9. package/dist/access-log/access-log.module.d.ts +3 -0
  10. package/dist/access-log/access-log.module.d.ts.map +1 -0
  11. package/dist/access-log/access-log.module.js +29 -0
  12. package/dist/access-log/access-log.module.js.map +1 -0
  13. package/dist/access-log/access-log.service.d.ts +47 -0
  14. package/dist/access-log/access-log.service.d.ts.map +1 -0
  15. package/dist/access-log/access-log.service.js +113 -0
  16. package/dist/access-log/access-log.service.js.map +1 -0
  17. package/dist/access-log/dto/query-access-log.dto.d.ts +8 -0
  18. package/dist/access-log/dto/query-access-log.dto.d.ts.map +1 -0
  19. package/dist/access-log/dto/query-access-log.dto.js +42 -0
  20. package/dist/access-log/dto/query-access-log.dto.js.map +1 -0
  21. package/dist/auth/auth.controller.d.ts +9 -9
  22. package/dist/auth/auth.service.d.ts +19 -19
  23. package/dist/auth/auth.service.d.ts.map +1 -1
  24. package/dist/auth/auth.service.js +0 -4
  25. package/dist/auth/auth.service.js.map +1 -1
  26. package/dist/challenge/challenge.service.d.ts +2 -2
  27. package/dist/challenge/challenge.service.d.ts.map +1 -1
  28. package/dist/challenge/challenge.service.js +0 -1
  29. package/dist/challenge/challenge.service.js.map +1 -1
  30. package/dist/core/system.module.d.ts.map +1 -1
  31. package/dist/core/system.module.js +1 -0
  32. package/dist/core/system.module.js.map +1 -1
  33. package/dist/core.module.d.ts.map +1 -1
  34. package/dist/core.module.js +12 -0
  35. package/dist/core.module.js.map +1 -1
  36. package/dist/dashboard/dashboard-user/dashboard-user.controller.d.ts +3 -3
  37. package/dist/dashboard/dashboard-user/dashboard-user.service.d.ts +3 -3
  38. package/dist/dashboard/dashboard.module.d.ts.map +1 -1
  39. package/dist/dashboard/dashboard.module.js +4 -1
  40. package/dist/dashboard/dashboard.module.js.map +1 -1
  41. package/dist/file/file.controller.js +0 -1
  42. package/dist/file/file.controller.js.map +1 -1
  43. package/dist/file/file.service.d.ts +65 -0
  44. package/dist/file/file.service.d.ts.map +1 -1
  45. package/dist/file/file.service.js +151 -3
  46. package/dist/file/file.service.js.map +1 -1
  47. package/dist/file/provider/abstract.provider.d.ts +8 -0
  48. package/dist/file/provider/abstract.provider.d.ts.map +1 -1
  49. package/dist/file/provider/abstract.provider.js.map +1 -1
  50. package/dist/file/provider/azure.provider.d.ts +4 -0
  51. package/dist/file/provider/azure.provider.d.ts.map +1 -1
  52. package/dist/file/provider/azure.provider.js +14 -0
  53. package/dist/file/provider/azure.provider.js.map +1 -1
  54. package/dist/file/provider/gcs.provider.d.ts +4 -0
  55. package/dist/file/provider/gcs.provider.d.ts.map +1 -1
  56. package/dist/file/provider/gcs.provider.js +14 -0
  57. package/dist/file/provider/gcs.provider.js.map +1 -1
  58. package/dist/file/provider/local.provider.d.ts +4 -0
  59. package/dist/file/provider/local.provider.d.ts.map +1 -1
  60. package/dist/file/provider/local.provider.js +19 -0
  61. package/dist/file/provider/local.provider.js.map +1 -1
  62. package/dist/file/provider/provider.factory.d.ts.map +1 -1
  63. package/dist/file/provider/provider.factory.js +3 -0
  64. package/dist/file/provider/provider.factory.js.map +1 -1
  65. package/dist/file/provider/s3.provider.d.ts +4 -0
  66. package/dist/file/provider/s3.provider.d.ts.map +1 -1
  67. package/dist/file/provider/s3.provider.js +17 -0
  68. package/dist/file/provider/s3.provider.js.map +1 -1
  69. package/dist/mcp/mcp-audit.service.d.ts +3 -1
  70. package/dist/mcp/mcp-audit.service.d.ts.map +1 -1
  71. package/dist/mcp/mcp-audit.service.js +14 -2
  72. package/dist/mcp/mcp-audit.service.js.map +1 -1
  73. package/dist/mcp/mcp.controller.d.ts +3 -1
  74. package/dist/mcp/mcp.controller.d.ts.map +1 -1
  75. package/dist/mcp/mcp.controller.js +10 -2
  76. package/dist/mcp/mcp.controller.js.map +1 -1
  77. package/dist/mcp/mcp.module.d.ts.map +1 -1
  78. package/dist/mcp/mcp.module.js +72 -0
  79. package/dist/mcp/mcp.module.js.map +1 -1
  80. package/dist/mcp/tools/core-ai.mcp-tools.d.ts +45 -0
  81. package/dist/mcp/tools/core-ai.mcp-tools.d.ts.map +1 -0
  82. package/dist/mcp/tools/core-ai.mcp-tools.js +214 -0
  83. package/dist/mcp/tools/core-ai.mcp-tools.js.map +1 -0
  84. package/dist/mcp/tools/core-dashboard-items.mcp-tools.d.ts +21 -0
  85. package/dist/mcp/tools/core-dashboard-items.mcp-tools.d.ts.map +1 -0
  86. package/dist/mcp/tools/core-dashboard-items.mcp-tools.js +89 -0
  87. package/dist/mcp/tools/core-dashboard-items.mcp-tools.js.map +1 -0
  88. package/dist/mcp/tools/core-dashboard.mcp-tools.d.ts +29 -0
  89. package/dist/mcp/tools/core-dashboard.mcp-tools.d.ts.map +1 -0
  90. package/dist/mcp/tools/core-dashboard.mcp-tools.js +149 -0
  91. package/dist/mcp/tools/core-dashboard.mcp-tools.js.map +1 -0
  92. package/dist/mcp/tools/core-event-webhooks.mcp-tools.d.ts +30 -0
  93. package/dist/mcp/tools/core-event-webhooks.mcp-tools.d.ts.map +1 -0
  94. package/dist/mcp/tools/core-event-webhooks.mcp-tools.js +185 -0
  95. package/dist/mcp/tools/core-event-webhooks.mcp-tools.js.map +1 -0
  96. package/dist/mcp/tools/core-files.mcp-tools.d.ts +26 -0
  97. package/dist/mcp/tools/core-files.mcp-tools.d.ts.map +1 -0
  98. package/dist/mcp/tools/core-files.mcp-tools.js +140 -0
  99. package/dist/mcp/tools/core-files.mcp-tools.js.map +1 -0
  100. package/dist/mcp/tools/core-integration-events.mcp-tools.d.ts +26 -0
  101. package/dist/mcp/tools/core-integration-events.mcp-tools.d.ts.map +1 -0
  102. package/dist/mcp/tools/core-integration-events.mcp-tools.js +168 -0
  103. package/dist/mcp/tools/core-integration-events.mcp-tools.js.map +1 -0
  104. package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts +32 -0
  105. package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts.map +1 -0
  106. package/dist/mcp/tools/core-mail-profile.mcp-tools.js +178 -0
  107. package/dist/mcp/tools/core-mail-profile.mcp-tools.js.map +1 -0
  108. package/dist/mcp/tools/core-mail-sent.mcp-tools.d.ts +14 -0
  109. package/dist/mcp/tools/core-mail-sent.mcp-tools.d.ts.map +1 -0
  110. package/dist/mcp/tools/core-mail-sent.mcp-tools.js +106 -0
  111. package/dist/mcp/tools/core-mail-sent.mcp-tools.js.map +1 -0
  112. package/dist/mcp/tools/core-mail.mcp-tools.d.ts +36 -0
  113. package/dist/mcp/tools/core-mail.mcp-tools.d.ts.map +1 -0
  114. package/dist/mcp/tools/core-mail.mcp-tools.js +184 -0
  115. package/dist/mcp/tools/core-mail.mcp-tools.js.map +1 -0
  116. package/dist/mcp/tools/core-mcp-chat.mcp-tools.d.ts +21 -0
  117. package/dist/mcp/tools/core-mcp-chat.mcp-tools.d.ts.map +1 -0
  118. package/dist/mcp/tools/core-mcp-chat.mcp-tools.js +118 -0
  119. package/dist/mcp/tools/core-mcp-chat.mcp-tools.js.map +1 -0
  120. package/dist/mcp/tools/core-menus.mcp-tools.d.ts +40 -0
  121. package/dist/mcp/tools/core-menus.mcp-tools.d.ts.map +1 -0
  122. package/dist/mcp/tools/core-menus.mcp-tools.js +268 -0
  123. package/dist/mcp/tools/core-menus.mcp-tools.js.map +1 -0
  124. package/dist/mcp/tools/core-notifications.mcp-tools.d.ts +31 -0
  125. package/dist/mcp/tools/core-notifications.mcp-tools.d.ts.map +1 -0
  126. package/dist/mcp/tools/core-notifications.mcp-tools.js +178 -0
  127. package/dist/mcp/tools/core-notifications.mcp-tools.js.map +1 -0
  128. package/dist/mcp/tools/core-profile.mcp-tools.d.ts +20 -0
  129. package/dist/mcp/tools/core-profile.mcp-tools.d.ts.map +1 -0
  130. package/dist/mcp/tools/core-profile.mcp-tools.js +111 -0
  131. package/dist/mcp/tools/core-profile.mcp-tools.js.map +1 -0
  132. package/dist/mcp/tools/core-roles.mcp-tools.d.ts +70 -0
  133. package/dist/mcp/tools/core-roles.mcp-tools.d.ts.map +1 -0
  134. package/dist/mcp/tools/core-roles.mcp-tools.js +355 -0
  135. package/dist/mcp/tools/core-roles.mcp-tools.js.map +1 -0
  136. package/dist/mcp/tools/core-routes.mcp-tools.d.ts +52 -0
  137. package/dist/mcp/tools/core-routes.mcp-tools.d.ts.map +1 -0
  138. package/dist/mcp/tools/core-routes.mcp-tools.js +254 -0
  139. package/dist/mcp/tools/core-routes.mcp-tools.js.map +1 -0
  140. package/dist/mcp/tools/core-screens.mcp-tools.d.ts +47 -0
  141. package/dist/mcp/tools/core-screens.mcp-tools.d.ts.map +1 -0
  142. package/dist/mcp/tools/core-screens.mcp-tools.js +250 -0
  143. package/dist/mcp/tools/core-screens.mcp-tools.js.map +1 -0
  144. package/dist/mcp/tools/core-sessions.mcp-tools.d.ts +20 -0
  145. package/dist/mcp/tools/core-sessions.mcp-tools.d.ts.map +1 -0
  146. package/dist/mcp/tools/core-sessions.mcp-tools.js +128 -0
  147. package/dist/mcp/tools/core-sessions.mcp-tools.js.map +1 -0
  148. package/dist/mcp/tools/core-settings.mcp-tools.d.ts +25 -0
  149. package/dist/mcp/tools/core-settings.mcp-tools.d.ts.map +1 -1
  150. package/dist/mcp/tools/core-settings.mcp-tools.js +138 -0
  151. package/dist/mcp/tools/core-settings.mcp-tools.js.map +1 -1
  152. package/dist/mcp/tools/core-system.mcp-tools.d.ts +8 -0
  153. package/dist/mcp/tools/core-system.mcp-tools.d.ts.map +1 -0
  154. package/dist/mcp/tools/core-system.mcp-tools.js +40 -0
  155. package/dist/mcp/tools/core-system.mcp-tools.js.map +1 -0
  156. package/dist/mcp/tools/core-webhooks.mcp-tools.d.ts +46 -0
  157. package/dist/mcp/tools/core-webhooks.mcp-tools.d.ts.map +1 -0
  158. package/dist/mcp/tools/core-webhooks.mcp-tools.js +266 -0
  159. package/dist/mcp/tools/core-webhooks.mcp-tools.js.map +1 -0
  160. package/dist/mcp-chat/mcp-chat.controller.d.ts +1 -1
  161. package/dist/mcp-chat/mcp-chat.service.d.ts +25 -2
  162. package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
  163. package/dist/mcp-chat/mcp-chat.service.js +666 -36
  164. package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
  165. package/dist/notification/notification.controller.d.ts +8 -4
  166. package/dist/notification/notification.controller.d.ts.map +1 -1
  167. package/dist/notification/notification.controller.js +12 -0
  168. package/dist/notification/notification.controller.js.map +1 -1
  169. package/dist/notification/notification.service.d.ts +19 -5
  170. package/dist/notification/notification.service.d.ts.map +1 -1
  171. package/dist/notification/notification.service.js +155 -9
  172. package/dist/notification/notification.service.js.map +1 -1
  173. package/dist/pdf/pdf.service.js +1 -1
  174. package/dist/pdf/pdf.service.js.map +1 -1
  175. package/dist/profile/profile.controller.d.ts +3 -3
  176. package/dist/profile/profile.controller.js +0 -1
  177. package/dist/profile/profile.controller.js.map +1 -1
  178. package/dist/profile/profile.module.d.ts.map +1 -1
  179. package/dist/profile/profile.module.js +1 -0
  180. package/dist/profile/profile.module.js.map +1 -1
  181. package/dist/profile/profile.service.d.ts +3 -3
  182. package/dist/session/session.controller.d.ts +2 -2
  183. package/dist/session/session.service.d.ts +6 -6
  184. package/dist/setting/setting.controller.d.ts +1 -1
  185. package/dist/setting/setting.service.d.ts +1 -1
  186. package/dist/setting/setting.service.d.ts.map +1 -1
  187. package/dist/setting/setting.service.js +1 -4
  188. package/dist/setting/setting.service.js.map +1 -1
  189. package/dist/task/task.service.d.ts +1 -0
  190. package/dist/task/task.service.d.ts.map +1 -1
  191. package/dist/task/task.service.js +25 -0
  192. package/dist/task/task.service.js.map +1 -1
  193. package/dist/token/token.service.d.ts.map +1 -1
  194. package/dist/token/token.service.js +0 -1
  195. package/dist/token/token.service.js.map +1 -1
  196. package/dist/user/user.controller.d.ts +9 -9
  197. package/dist/user/user.service.d.ts +21 -21
  198. package/dist/webhook-integration/dto/webhook-action.dto.d.ts +22 -0
  199. package/dist/webhook-integration/dto/webhook-action.dto.d.ts.map +1 -0
  200. package/dist/webhook-integration/dto/webhook-action.dto.js +120 -0
  201. package/dist/webhook-integration/dto/webhook-action.dto.js.map +1 -0
  202. package/dist/webhook-integration/webhook-integration.controller.d.ts +7 -5
  203. package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
  204. package/dist/webhook-integration/webhook-integration.controller.js +18 -5
  205. package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
  206. package/dist/webhook-integration/webhook-integration.service.d.ts +14 -5
  207. package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
  208. package/dist/webhook-integration/webhook-integration.service.js +174 -41
  209. package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
  210. package/hedhog/data/menu.yaml +15 -0
  211. package/hedhog/data/route.yaml +1304 -0
  212. package/hedhog/data/setting_group.yaml +67 -0
  213. package/hedhog/data/setting_subgroup.yaml +16 -0
  214. package/hedhog/frontend/app/access-log/page.tsx.ejs +772 -0
  215. package/hedhog/frontend/app/ai_agent/page.tsx.ejs +10 -3
  216. package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +33 -13
  217. package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +12 -5
  218. package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +12 -5
  219. package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +1817 -356
  220. package/hedhog/frontend/app/mcp_chat/chat-components.tsx.ejs +43 -5
  221. package/hedhog/frontend/app/mcp_chat/page.tsx.ejs +35 -7
  222. package/hedhog/frontend/app/menu/page.tsx.ejs +9 -3
  223. package/hedhog/frontend/app/roles/page.tsx.ejs +18 -6
  224. package/hedhog/frontend/app/routes/page.tsx.ejs +9 -3
  225. package/hedhog/frontend/app/setting-list/page.tsx.ejs +344 -171
  226. package/hedhog/frontend/app/users/user-access-sheet.tsx.ejs +310 -304
  227. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +25 -7
  228. package/hedhog/frontend/messages/en.json +104 -3
  229. package/hedhog/frontend/messages/pt.json +104 -3
  230. package/hedhog/table/access_log.yaml +31 -0
  231. package/package.json +5 -4
  232. package/src/access-log/access-log.controller.ts +29 -0
  233. package/src/access-log/access-log.interceptor.ts +38 -0
  234. package/src/access-log/access-log.module.ts +16 -0
  235. package/src/access-log/access-log.service.ts +129 -0
  236. package/src/access-log/dto/query-access-log.dto.ts +23 -0
  237. package/src/auth/auth.service.ts +0 -7
  238. package/src/challenge/challenge.service.ts +0 -2
  239. package/src/core/system.module.ts +1 -0
  240. package/src/core.module.ts +12 -0
  241. package/src/dashboard/dashboard.module.ts +4 -1
  242. package/src/file/file.controller.ts +14 -14
  243. package/src/file/file.service.ts +198 -4
  244. package/src/file/provider/abstract.provider.ts +10 -0
  245. package/src/file/provider/azure.provider.ts +24 -0
  246. package/src/file/provider/gcs.provider.ts +20 -0
  247. package/src/file/provider/local.provider.ts +30 -2
  248. package/src/file/provider/provider.factory.ts +3 -0
  249. package/src/file/provider/s3.provider.ts +22 -0
  250. package/src/mcp/mcp-audit.service.ts +47 -36
  251. package/src/mcp/mcp.controller.ts +40 -30
  252. package/src/mcp/mcp.module.ts +72 -0
  253. package/src/mcp/tools/core-ai.mcp-tools.ts +156 -0
  254. package/src/mcp/tools/core-dashboard-items.mcp-tools.ts +63 -0
  255. package/src/mcp/tools/core-dashboard.mcp-tools.ts +103 -0
  256. package/src/mcp/tools/core-event-webhooks.mcp-tools.ts +134 -0
  257. package/src/mcp/tools/core-files.mcp-tools.ts +105 -0
  258. package/src/mcp/tools/core-integration-events.mcp-tools.ts +118 -0
  259. package/src/mcp/tools/core-mail-profile.mcp-tools.ts +128 -0
  260. package/src/mcp/tools/core-mail-sent.mcp-tools.ts +76 -0
  261. package/src/mcp/tools/core-mail.mcp-tools.ts +130 -0
  262. package/src/mcp/tools/core-mcp-chat.mcp-tools.ts +81 -0
  263. package/src/mcp/tools/core-menus.mcp-tools.ts +197 -0
  264. package/src/mcp/tools/core-notifications.mcp-tools.ts +128 -0
  265. package/src/mcp/tools/core-profile.mcp-tools.ts +74 -0
  266. package/src/mcp/tools/core-roles.mcp-tools.ts +288 -0
  267. package/src/mcp/tools/core-routes.mcp-tools.ts +196 -0
  268. package/src/mcp/tools/core-screens.mcp-tools.ts +186 -0
  269. package/src/mcp/tools/core-sessions.mcp-tools.ts +97 -0
  270. package/src/mcp/tools/core-settings.mcp-tools.ts +171 -53
  271. package/src/mcp/tools/core-system.mcp-tools.ts +19 -0
  272. package/src/mcp/tools/core-webhooks.mcp-tools.ts +199 -0
  273. package/src/mcp-chat/mcp-chat.service.ts +887 -38
  274. package/src/notification/notification.controller.ts +20 -8
  275. package/src/notification/notification.service.ts +209 -9
  276. package/src/pdf/pdf.service.ts +1 -1
  277. package/src/profile/profile.controller.ts +1 -1
  278. package/src/profile/profile.module.ts +1 -0
  279. package/src/setting/setting.service.ts +8 -11
  280. package/src/task/task.service.ts +31 -0
  281. package/src/token/token.service.ts +0 -2
  282. package/src/webhook-integration/dto/webhook-action.dto.ts +96 -0
  283. package/src/webhook-integration/webhook-integration.controller.ts +124 -103
  284. package/src/webhook-integration/webhook-integration.service.ts +267 -64
@@ -1,15 +1,18 @@
1
1
  import { Role, User } from '@hed-hog/api';
2
2
  import { Pagination } from '@hed-hog/api-pagination';
3
3
  import {
4
- Body,
5
- Controller,
6
- Delete,
7
- Get,
8
- Param,
9
- ParseIntPipe,
10
- Patch,
11
- Post,
4
+ Body,
5
+ Controller,
6
+ Delete,
7
+ Get,
8
+ Param,
9
+ ParseIntPipe,
10
+ Patch,
11
+ Post,
12
+ Req,
13
+ Res,
12
14
  } from '@nestjs/common';
15
+ import type { Request, Response } from 'express';
13
16
  import { CreateNotificationDto } from './dto/create-notification.dto';
14
17
  import { UpdateProgressDto } from './dto/update-progress.dto';
15
18
  import { NotificationService } from './notification.service';
@@ -24,6 +27,15 @@ export class NotificationController {
24
27
  return this.notificationService.list(id, paginationParams);
25
28
  }
26
29
 
30
+ @Get('stream')
31
+ async stream(
32
+ @User() { id },
33
+ @Req() req: Request,
34
+ @Res() res: Response,
35
+ ) {
36
+ return this.notificationService.stream(id, req, res);
37
+ }
38
+
27
39
  @Get('unread-count')
28
40
  async unreadCount(@User() { id }) {
29
41
  return this.notificationService.unreadCount(id);
@@ -5,19 +5,92 @@ import {
5
5
  forwardRef,
6
6
  Inject,
7
7
  Injectable,
8
+ Logger,
8
9
  NotFoundException,
10
+ OnModuleDestroy,
11
+ OnModuleInit,
9
12
  } from '@nestjs/common';
13
+ import type { Request, Response } from 'express';
14
+ import { randomUUID } from 'node:crypto';
15
+ import { EventEmitter } from 'node:events';
16
+ import { Client, type Notification as PgNotification } from 'pg';
10
17
  import { CreateNotificationDto } from './dto/create-notification.dto';
11
18
  import { UpdateProgressDto } from './dto/update-progress.dto';
12
19
 
20
+ type NotificationStreamEventType =
21
+ | 'ready'
22
+ | 'notification.created'
23
+ | 'notification.updated';
24
+
25
+ type NotificationStreamEvent = {
26
+ type: NotificationStreamEventType;
27
+ notificationId?: number;
28
+ notification?: unknown;
29
+ };
30
+
31
+ type ClusterNotificationPayload = NotificationStreamEvent & {
32
+ userId: number;
33
+ originInstanceId: string;
34
+ };
35
+
13
36
  @Injectable()
14
- export class NotificationService {
37
+ export class NotificationService implements OnModuleInit, OnModuleDestroy {
38
+ private readonly logger = new Logger(NotificationService.name);
39
+ private readonly notificationEvents = new EventEmitter();
40
+ private readonly instanceId = randomUUID();
41
+ private readonly channelName = 'notification_events';
42
+ private pgClient: Client | null = null;
43
+
15
44
  constructor(
16
45
  @Inject(forwardRef(() => PrismaService))
17
46
  private readonly prisma: PrismaService,
18
47
  @Inject(forwardRef(() => PaginationService))
19
48
  private readonly pagination: PaginationService,
20
- ) {}
49
+ ) {
50
+ this.notificationEvents.setMaxListeners(0);
51
+ }
52
+
53
+ async onModuleInit() {
54
+ const databaseUrl = String(process.env.DATABASE_URL || '');
55
+ if (!databaseUrl.startsWith('postgresql://')) {
56
+ this.logger.warn(
57
+ 'Cluster notification stream disabled because DATABASE_URL is not PostgreSQL',
58
+ );
59
+ return;
60
+ }
61
+
62
+ this.pgClient = new Client({ connectionString: databaseUrl });
63
+ this.pgClient.on('notification', (message: PgNotification) => {
64
+ this.handleClusterNotification(message);
65
+ });
66
+ this.pgClient.on('error', (error) => {
67
+ this.logger.error('Cluster notification listener error', error);
68
+ });
69
+
70
+ await this.pgClient.connect();
71
+ await this.pgClient.query(`LISTEN ${this.channelName}`);
72
+ this.logger.log(
73
+ `Cluster notification listener ready on channel ${this.channelName}`,
74
+ );
75
+ }
76
+
77
+ async onModuleDestroy() {
78
+ if (!this.pgClient) {
79
+ return;
80
+ }
81
+
82
+ try {
83
+ await this.pgClient.query(`UNLISTEN ${this.channelName}`);
84
+ } catch (error) {
85
+ this.logger.warn('Failed to unlisten notification channel', error as any);
86
+ }
87
+
88
+ try {
89
+ await this.pgClient.end();
90
+ } finally {
91
+ this.pgClient = null;
92
+ }
93
+ }
21
94
 
22
95
  async list(userId: number, paginationParams: any) {
23
96
  return this.pagination.paginate(
@@ -40,35 +113,56 @@ export class NotificationService {
40
113
  async markRead(userId: number, id: number) {
41
114
  const notification = await this.assertOwnership(userId, id);
42
115
  if (notification.auto_remove) {
43
- return this.prisma.notification.delete({ where: { id } });
116
+ const deleted = await this.prisma.notification.delete({ where: { id } });
117
+ await this.broadcastNotificationEvent(userId, {
118
+ type: 'notification.updated',
119
+ notificationId: id,
120
+ });
121
+ return deleted;
44
122
  }
45
- return this.prisma.notification.update({
123
+ const updated = await this.prisma.notification.update({
46
124
  where: { id },
47
125
  data: { status: 'read' },
48
126
  });
127
+ await this.broadcastNotificationEvent(userId, {
128
+ type: 'notification.updated',
129
+ notificationId: id,
130
+ });
131
+ return updated;
49
132
  }
50
133
 
51
134
  async markAllRead(userId: number) {
52
135
  await this.prisma.notification.deleteMany({
53
136
  where: { user_id: userId, status: 'unread', auto_remove: true },
54
137
  });
55
- return this.prisma.notification.updateMany({
138
+ const updated = await this.prisma.notification.updateMany({
56
139
  where: { user_id: userId, status: 'unread' },
57
140
  data: { status: 'read' },
58
141
  });
142
+ await this.broadcastNotificationEvent(userId, { type: 'notification.updated' });
143
+ return updated;
59
144
  }
60
145
 
61
146
  async delete(userId: number, id: number) {
62
147
  await this.assertOwnership(userId, id);
63
- return this.prisma.notification.delete({ where: { id } });
148
+ const deleted = await this.prisma.notification.delete({ where: { id } });
149
+ await this.broadcastNotificationEvent(userId, {
150
+ type: 'notification.updated',
151
+ notificationId: id,
152
+ });
153
+ return deleted;
64
154
  }
65
155
 
66
156
  async deleteAll(userId: number) {
67
- return this.prisma.notification.deleteMany({ where: { user_id: userId } });
157
+ const result = await this.prisma.notification.deleteMany({
158
+ where: { user_id: userId },
159
+ });
160
+ await this.broadcastNotificationEvent(userId, { type: 'notification.updated' });
161
+ return result;
68
162
  }
69
163
 
70
164
  async create(data: CreateNotificationDto) {
71
- return this.prisma.notification.create({
165
+ const notification = await this.prisma.notification.create({
72
166
  data: {
73
167
  user_id: data.user_id,
74
168
  title: data.title,
@@ -83,12 +177,18 @@ export class NotificationService {
83
177
  started_at: data.started_at ? new Date(data.started_at) : undefined,
84
178
  },
85
179
  });
180
+ await this.broadcastNotificationEvent(data.user_id, {
181
+ type: 'notification.created',
182
+ notificationId: notification.id,
183
+ notification,
184
+ });
185
+ return notification;
86
186
  }
87
187
 
88
188
  async updateProgress(userId: number, id: number, dto: UpdateProgressDto) {
89
189
  await this.assertOwnership(userId, id);
90
190
  const finished = dto.progress >= 100;
91
- return this.prisma.notification.update({
191
+ const notification = await this.prisma.notification.update({
92
192
  where: { id },
93
193
  data: {
94
194
  progress: dto.progress,
@@ -97,6 +197,51 @@ export class NotificationService {
97
197
  finished_at: finished ? new Date() : undefined,
98
198
  },
99
199
  });
200
+ await this.broadcastNotificationEvent(userId, {
201
+ type: 'notification.updated',
202
+ notificationId: id,
203
+ notification,
204
+ });
205
+ return notification;
206
+ }
207
+
208
+ stream(userId: number, req: Request, res: Response) {
209
+ res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
210
+ res.setHeader('Cache-Control', 'no-cache, no-transform');
211
+ res.setHeader('Connection', 'keep-alive');
212
+ res.setHeader('X-Accel-Buffering', 'no');
213
+ res.flushHeaders();
214
+
215
+ const writeEvent = (event: NotificationStreamEvent) => {
216
+ if (res.writableEnded) {
217
+ return;
218
+ }
219
+
220
+ res.write(`event: ${event.type}\n`);
221
+ res.write(`data: ${JSON.stringify(event)}\n\n`);
222
+ };
223
+
224
+ const eventKey = this.getEventKey(userId);
225
+ const handler = (event: NotificationStreamEvent) => {
226
+ writeEvent(event);
227
+ };
228
+
229
+ this.notificationEvents.on(eventKey, handler);
230
+ writeEvent({ type: 'ready' });
231
+
232
+ const heartbeat = setInterval(() => {
233
+ if (!res.writableEnded) {
234
+ res.write(': ping\n\n');
235
+ }
236
+ }, 25000);
237
+
238
+ const cleanup = () => {
239
+ clearInterval(heartbeat);
240
+ this.notificationEvents.off(eventKey, handler);
241
+ };
242
+
243
+ req.on('close', cleanup);
244
+ res.on('close', cleanup);
100
245
  }
101
246
 
102
247
  async clearOldNotifications(retentionDays: number, batchLimit: number) {
@@ -121,4 +266,59 @@ export class NotificationService {
121
266
  if (notification.user_id !== userId) throw new ForbiddenException();
122
267
  return notification;
123
268
  }
269
+
270
+ private async broadcastNotificationEvent(
271
+ userId: number,
272
+ event: NotificationStreamEvent,
273
+ ) {
274
+ this.emitLocalNotificationEvent(userId, event);
275
+
276
+ const payload: ClusterNotificationPayload = {
277
+ ...event,
278
+ userId,
279
+ originInstanceId: this.instanceId,
280
+ };
281
+
282
+ try {
283
+ await this.prisma.$executeRaw`
284
+ SELECT pg_notify(${this.channelName}, ${JSON.stringify(payload)})
285
+ `;
286
+ } catch (error) {
287
+ this.logger.warn('Failed to broadcast notification event', error as any);
288
+ }
289
+ }
290
+
291
+ private getEventKey(userId: number) {
292
+ return `notification:${userId}`;
293
+ }
294
+
295
+ private emitLocalNotificationEvent(
296
+ userId: number,
297
+ event: NotificationStreamEvent,
298
+ ) {
299
+ this.notificationEvents.emit(this.getEventKey(userId), event);
300
+ }
301
+
302
+ private handleClusterNotification(message: PgNotification) {
303
+ if (message.channel !== this.channelName || !message.payload) {
304
+ return;
305
+ }
306
+
307
+ let payload: ClusterNotificationPayload;
308
+ try {
309
+ payload = JSON.parse(message.payload) as ClusterNotificationPayload;
310
+ } catch {
311
+ return;
312
+ }
313
+
314
+ if (!payload || payload.originInstanceId === this.instanceId) {
315
+ return;
316
+ }
317
+
318
+ this.emitLocalNotificationEvent(payload.userId, {
319
+ type: payload.type,
320
+ notificationId: payload.notificationId,
321
+ notification: payload.notification,
322
+ });
323
+ }
124
324
  }
@@ -182,7 +182,7 @@ export class PdfService {
182
182
 
183
183
  if (missingRuntime) {
184
184
  throw new InternalServerErrorException(
185
- 'Playwright runtime is not available. Run `npx playwright install chromium` in apps/api.',
185
+ 'Playwright runtime is not available. Run `pnpm --filter api run playwright:install` in the API environment.',
186
186
  );
187
187
  }
188
188
 
@@ -81,7 +81,7 @@ export class ProfileController {
81
81
 
82
82
  @Post('mfa/email/verify/confirm')
83
83
  sendEmailVerificationMfaConfirm(@User() {id}, @Body() data: EmailVerificationDto, @Locale() locale: string) {
84
- console.log('sendEmailVerificationMfaConfirm')
84
+
85
85
  return this.profileService.confirmEmailVerificationMfa(locale, id, data);
86
86
  }
87
87
 
@@ -17,5 +17,6 @@ import { ProfileService } from './profile.service';
17
17
  ],
18
18
  controllers: [ProfileController],
19
19
  providers: [ProfileService],
20
+ exports: [ProfileService],
20
21
  })
21
22
  export class ProfileModule {}
@@ -3,12 +3,12 @@ import { getLocaleText, LocaleService } from '@hed-hog/api-locale';
3
3
  import { PaginationDTO, PaginationService } from '@hed-hog/api-pagination';
4
4
  import { PrismaService, setting_type_0f055cd5ea_enum } from '@hed-hog/api-prisma';
5
5
  import {
6
- BadRequestException,
7
- forwardRef,
8
- Inject,
9
- Injectable,
10
- Logger,
11
- NotFoundException
6
+ BadRequestException,
7
+ forwardRef,
8
+ Inject,
9
+ Injectable,
10
+ Logger,
11
+ NotFoundException
12
12
  } from '@nestjs/common';
13
13
  import * as pako from 'pako';
14
14
  import { IntegrationDeveloperApiService } from '../integration/services/integration-developer-api.service';
@@ -208,6 +208,7 @@ export class SettingService {
208
208
  'mfa-email-code-length',
209
209
  'ai-openai-api-key-enabled',
210
210
  'ai-gemini-api-key-enabled',
211
+ 'mcp-enabled',
211
212
  'date-format',
212
213
  'time-format',
213
214
  'timezone',
@@ -974,8 +975,6 @@ export class SettingService {
974
975
  this.cachedSettings &&
975
976
  slugs.every(s => Object.prototype.hasOwnProperty.call(this.cachedSettings, s))
976
977
  ) {
977
- this.logger.debug('Returning settings from cache');
978
-
979
978
  // Retorna apenas os slugs requisitados do cache
980
979
  const cached: Record<string, any> = {};
981
980
  slugs.forEach(s => {
@@ -984,7 +983,7 @@ export class SettingService {
984
983
  return cached;
985
984
  }
986
985
 
987
- this.logger.debug('Fetching settings from database');
986
+
988
987
 
989
988
  slug = Array.isArray(slug) ? slug : [slug];
990
989
 
@@ -1064,7 +1063,6 @@ export class SettingService {
1064
1063
  }
1065
1064
 
1066
1065
  setCache(value: Record<string, any>) {
1067
- this.logger.debug(`Setting settings cache with ${Object.keys(value).length} items`);
1068
1066
  Object.keys(value).forEach(key => {
1069
1067
  this.cachedSettings[key] = value[key];
1070
1068
  });
@@ -1072,7 +1070,6 @@ export class SettingService {
1072
1070
  }
1073
1071
 
1074
1072
  clearCache() {
1075
- this.logger.debug('Clearing settings cache');
1076
1073
  this.cachedSettings = {};
1077
1074
  }
1078
1075
 
@@ -42,6 +42,9 @@ export class TasksService {
42
42
  await this.clearOldNotifications();
43
43
  this.logger.verbose('++++++++++++++++++++++++++++++++++');
44
44
 
45
+ await this.clearOldAccessLogs();
46
+ this.logger.verbose('++++++++++++++++++++++++++++++++++');
47
+
45
48
  this.logger.verbose('Finished scheduled cleanup tasks');
46
49
  this.logger.verbose('++++++++++++++++++++++++++++++++++');
47
50
  }
@@ -270,6 +273,34 @@ export class TasksService {
270
273
  );
271
274
  }
272
275
 
276
+ private async clearOldAccessLogs(): Promise<void> {
277
+ const settings = await this.settingService.getSettingValues([
278
+ 'cleanup-batch-limit',
279
+ 'access-log-retention-days',
280
+ ]);
281
+ const batchLimit = Number(settings['cleanup-batch-limit']) || 20000;
282
+ const retentionDays = Number(settings['access-log-retention-days']) || 30;
283
+ this.logger.verbose('Starting cleanup of old access logs');
284
+ const startAt = Date.now();
285
+
286
+ await this.prismaService.$executeRaw`
287
+ WITH del AS (
288
+ SELECT id
289
+ FROM access_log
290
+ WHERE created_at < (now() - ${Prisma.sql`INTERVAL '${Prisma.raw(String(retentionDays))} days'`})
291
+ ORDER BY id
292
+ LIMIT ${batchLimit}
293
+ )
294
+ DELETE FROM access_log al
295
+ USING del
296
+ WHERE al.id = del.id
297
+ `;
298
+
299
+ this.logger.verbose(
300
+ `Old access logs cleaned up in ${Date.now() - startAt}ms`,
301
+ );
302
+ }
303
+
273
304
  private async clearOldNotifications(): Promise<void> {
274
305
  const settings = await this.settingService.getSettingValues([
275
306
  'cleanup-batch-limit',
@@ -51,8 +51,6 @@ export class TokenService {
51
51
  return payload;
52
52
  } catch (error) {
53
53
 
54
- console.log('JWT ERROR', error);
55
-
56
54
  // Return 401 for JWT errors (expired, invalid, etc.)
57
55
  throw new UnauthorizedException(
58
56
  (error as any).message || getLocaleText('accessDenied', locale, 'Access denied.')
@@ -0,0 +1,96 @@
1
+ import { Transform } from 'class-transformer';
2
+ import { PartialType } from '@nestjs/mapped-types';
3
+ import { IsIn, IsInt, IsOptional, IsString } from 'class-validator';
4
+
5
+ const toNullableInteger = ({ value }: { value: unknown }) => {
6
+ if (value === undefined || value === null || value === '') {
7
+ return undefined;
8
+ }
9
+
10
+ return Number(value);
11
+ };
12
+
13
+ const toOptionalTrimmedString = ({ value }: { value: unknown }) => {
14
+ if (value === undefined || value === null) {
15
+ return undefined;
16
+ }
17
+
18
+ return String(value).trim();
19
+ };
20
+
21
+ export class CreateWebhookActionDTO {
22
+ @IsIn(['email', 'whatsapp_evolution'])
23
+ type: 'email' | 'whatsapp_evolution';
24
+
25
+ @IsOptional()
26
+ @Transform(toOptionalTrimmedString)
27
+ @IsString()
28
+ name?: string;
29
+
30
+ @IsOptional()
31
+ @Transform(toNullableInteger)
32
+ @IsInt()
33
+ order?: number;
34
+
35
+ @IsOptional()
36
+ @IsIn(['active', 'inactive'])
37
+ status?: 'active' | 'inactive';
38
+
39
+ @IsOptional()
40
+ @Transform(toNullableInteger)
41
+ @IsInt()
42
+ mail_profile_id?: number;
43
+
44
+ @IsOptional()
45
+ @Transform(toNullableInteger)
46
+ @IsInt()
47
+ mail_id?: number;
48
+
49
+ @IsOptional()
50
+ @Transform(toOptionalTrimmedString)
51
+ @IsString()
52
+ email_to?: string;
53
+
54
+ @IsOptional()
55
+ @Transform(toOptionalTrimmedString)
56
+ @IsString()
57
+ email_cc?: string;
58
+
59
+ @IsOptional()
60
+ @Transform(toOptionalTrimmedString)
61
+ @IsString()
62
+ email_bcc?: string;
63
+
64
+ @IsOptional()
65
+ @IsIn(['phone', 'group'])
66
+ whatsapp_target_type?: 'phone' | 'group';
67
+
68
+ @IsOptional()
69
+ @Transform(toOptionalTrimmedString)
70
+ @IsString()
71
+ whatsapp_target?: string;
72
+
73
+ @IsOptional()
74
+ @Transform(toOptionalTrimmedString)
75
+ @IsString()
76
+ whatsapp_template?: string;
77
+
78
+ @IsOptional()
79
+ @Transform(toOptionalTrimmedString)
80
+ @IsString()
81
+ whatsapp_instance?: string;
82
+
83
+ @IsOptional()
84
+ @Transform(toOptionalTrimmedString)
85
+ @IsString()
86
+ whatsapp_base_url?: string;
87
+
88
+ @IsOptional()
89
+ @Transform(toOptionalTrimmedString)
90
+ @IsString()
91
+ whatsapp_token?: string;
92
+ }
93
+
94
+ export class UpdateWebhookActionDTO extends PartialType(
95
+ CreateWebhookActionDTO,
96
+ ) {}