@hed-hog/core 0.0.387 → 0.0.390

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 (535) hide show
  1. package/README.md +12 -11
  2. package/dist/access-log/access-log.interceptor.d.ts.map +1 -1
  3. package/dist/access-log/access-log.interceptor.js +4 -2
  4. package/dist/access-log/access-log.interceptor.js.map +1 -1
  5. package/dist/ai/ai-attachment.fragments.d.ts +23 -0
  6. package/dist/ai/ai-attachment.fragments.d.ts.map +1 -0
  7. package/dist/ai/ai-attachment.fragments.js +34 -0
  8. package/dist/ai/ai-attachment.fragments.js.map +1 -0
  9. package/dist/ai/ai.controller.d.ts +7 -16
  10. package/dist/ai/ai.controller.d.ts.map +1 -1
  11. package/dist/ai/ai.prompts.d.ts +16 -0
  12. package/dist/ai/ai.prompts.d.ts.map +1 -0
  13. package/dist/ai/ai.prompts.js +37 -0
  14. package/dist/ai/ai.prompts.js.map +1 -0
  15. package/dist/ai/ai.service.d.ts +63 -12
  16. package/dist/ai/ai.service.d.ts.map +1 -1
  17. package/dist/ai/ai.service.js +215 -88
  18. package/dist/ai/ai.service.js.map +1 -1
  19. package/dist/ai/dto/chat.dto.d.ts +4 -1
  20. package/dist/ai/dto/chat.dto.d.ts.map +1 -1
  21. package/dist/ai/dto/chat.dto.js +23 -1
  22. package/dist/ai/dto/chat.dto.js.map +1 -1
  23. package/dist/ai/dto/create-agent.dto.d.ts +1 -1
  24. package/dist/ai/dto/create-agent.dto.d.ts.map +1 -1
  25. package/dist/ai/dto/create-agent.dto.js +1 -1
  26. package/dist/ai/dto/create-agent.dto.js.map +1 -1
  27. package/dist/ai/dto/update-agent.dto.d.ts +1 -1
  28. package/dist/ai/dto/update-agent.dto.d.ts.map +1 -1
  29. package/dist/ai/dto/update-agent.dto.js +1 -1
  30. package/dist/ai/dto/update-agent.dto.js.map +1 -1
  31. package/dist/ai-instruction/ai-instruction.controller.d.ts +61 -7
  32. package/dist/ai-instruction/ai-instruction.controller.d.ts.map +1 -1
  33. package/dist/ai-instruction/ai-instruction.controller.js +63 -20
  34. package/dist/ai-instruction/ai-instruction.controller.js.map +1 -1
  35. package/dist/ai-instruction/ai-instruction.module.d.ts.map +1 -1
  36. package/dist/ai-instruction/ai-instruction.module.js +13 -2
  37. package/dist/ai-instruction/ai-instruction.module.js.map +1 -1
  38. package/dist/ai-instruction/ai-instruction.service.d.ts +131 -6
  39. package/dist/ai-instruction/ai-instruction.service.d.ts.map +1 -1
  40. package/dist/ai-instruction/ai-instruction.service.js +533 -60
  41. package/dist/ai-instruction/ai-instruction.service.js.map +1 -1
  42. package/dist/ai-instruction/ai-prompt-builder.service.d.ts +8 -0
  43. package/dist/ai-instruction/ai-prompt-builder.service.d.ts.map +1 -1
  44. package/dist/ai-instruction/ai-prompt-builder.service.js +10 -1
  45. package/dist/ai-instruction/ai-prompt-builder.service.js.map +1 -1
  46. package/dist/ai-instruction/ai-prompt-registry.d.ts +45 -0
  47. package/dist/ai-instruction/ai-prompt-registry.d.ts.map +1 -0
  48. package/dist/ai-instruction/ai-prompt-registry.js +49 -0
  49. package/dist/ai-instruction/ai-prompt-registry.js.map +1 -0
  50. package/dist/ai-instruction/ai-prompt-resolver.service.d.ts +89 -0
  51. package/dist/ai-instruction/ai-prompt-resolver.service.d.ts.map +1 -0
  52. package/dist/ai-instruction/ai-prompt-resolver.service.js +222 -0
  53. package/dist/ai-instruction/ai-prompt-resolver.service.js.map +1 -0
  54. package/dist/ai-instruction/ai-prompt-template.util.d.ts +70 -0
  55. package/dist/ai-instruction/ai-prompt-template.util.d.ts.map +1 -0
  56. package/dist/ai-instruction/ai-prompt-template.util.js +154 -0
  57. package/dist/ai-instruction/ai-prompt-template.util.js.map +1 -0
  58. package/dist/ai-instruction/dto/create-ai-instruction.dto.d.ts +5 -0
  59. package/dist/ai-instruction/dto/create-ai-instruction.dto.d.ts.map +1 -1
  60. package/dist/ai-instruction/dto/create-ai-instruction.dto.js +27 -1
  61. package/dist/ai-instruction/dto/create-ai-instruction.dto.js.map +1 -1
  62. package/dist/ai-instruction/dto/preview-ai-instruction.dto.d.ts +14 -0
  63. package/dist/ai-instruction/dto/preview-ai-instruction.dto.d.ts.map +1 -0
  64. package/dist/ai-instruction/dto/preview-ai-instruction.dto.js +42 -0
  65. package/dist/ai-instruction/dto/preview-ai-instruction.dto.js.map +1 -0
  66. package/dist/ai-instruction/dto/update-ai-instruction.dto.d.ts +11 -0
  67. package/dist/ai-instruction/dto/update-ai-instruction.dto.d.ts.map +1 -1
  68. package/dist/ai-instruction/dto/update-ai-instruction.dto.js +34 -0
  69. package/dist/ai-instruction/dto/update-ai-instruction.dto.js.map +1 -1
  70. package/dist/ai-instruction/types/ai-instruction.types.d.ts +13 -1
  71. package/dist/ai-instruction/types/ai-instruction.types.d.ts.map +1 -1
  72. package/dist/ai-instruction/types/ai-instruction.types.js +10 -0
  73. package/dist/ai-instruction/types/ai-instruction.types.js.map +1 -1
  74. package/dist/auth/auth.controller.d.ts +24 -9
  75. package/dist/auth/auth.controller.d.ts.map +1 -1
  76. package/dist/auth/auth.controller.js +49 -21
  77. package/dist/auth/auth.controller.js.map +1 -1
  78. package/dist/auth/auth.service.d.ts +33 -8
  79. package/dist/auth/auth.service.d.ts.map +1 -1
  80. package/dist/auth/auth.service.js +78 -29
  81. package/dist/auth/auth.service.js.map +1 -1
  82. package/dist/captcha/captcha.module.d.ts +3 -0
  83. package/dist/captcha/captcha.module.d.ts.map +1 -0
  84. package/dist/captcha/captcha.module.js +23 -0
  85. package/dist/captcha/captcha.module.js.map +1 -0
  86. package/dist/captcha/captcha.service.d.ts +38 -0
  87. package/dist/captcha/captcha.service.d.ts.map +1 -0
  88. package/dist/captcha/captcha.service.js +144 -0
  89. package/dist/captcha/captcha.service.js.map +1 -0
  90. package/dist/challenge/challenge.service.d.ts +16 -7
  91. package/dist/challenge/challenge.service.d.ts.map +1 -1
  92. package/dist/challenge/challenge.service.js +64 -16
  93. package/dist/challenge/challenge.service.js.map +1 -1
  94. package/dist/core.module.d.ts +3 -1
  95. package/dist/core.module.d.ts.map +1 -1
  96. package/dist/core.module.js +29 -2
  97. package/dist/core.module.js.map +1 -1
  98. package/dist/dashboard/dashboard/dashboard.controller.d.ts +15 -15
  99. package/dist/dashboard/dashboard/dashboard.service.d.ts +15 -15
  100. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +6 -6
  101. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +6 -6
  102. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +20 -20
  103. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +20 -20
  104. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +1 -1
  105. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +1 -1
  106. package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +13 -13
  107. package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +13 -13
  108. package/dist/file/file.service.d.ts +6 -0
  109. package/dist/file/file.service.d.ts.map +1 -1
  110. package/dist/file/file.service.js +44 -4
  111. package/dist/file/file.service.js.map +1 -1
  112. package/dist/incident/incident-notifier.service.d.ts +36 -0
  113. package/dist/incident/incident-notifier.service.d.ts.map +1 -0
  114. package/dist/incident/incident-notifier.service.js +208 -0
  115. package/dist/incident/incident-notifier.service.js.map +1 -0
  116. package/dist/incident/incident.controller.d.ts +23 -0
  117. package/dist/incident/incident.controller.d.ts.map +1 -0
  118. package/dist/incident/incident.controller.js +75 -0
  119. package/dist/incident/incident.controller.js.map +1 -0
  120. package/dist/incident/incident.module.d.ts +7 -0
  121. package/dist/incident/incident.module.d.ts.map +1 -0
  122. package/dist/incident/incident.module.js +32 -0
  123. package/dist/incident/incident.module.js.map +1 -0
  124. package/dist/incident/incident.service.d.ts +72 -0
  125. package/dist/incident/incident.service.d.ts.map +1 -0
  126. package/dist/incident/incident.service.js +184 -0
  127. package/dist/incident/incident.service.js.map +1 -0
  128. package/dist/index.d.ts +26 -0
  129. package/dist/index.d.ts.map +1 -1
  130. package/dist/index.js +30 -0
  131. package/dist/index.js.map +1 -1
  132. package/dist/integration-profile/dto/test.dto.d.ts +13 -0
  133. package/dist/integration-profile/dto/test.dto.d.ts.map +1 -0
  134. package/dist/integration-profile/dto/test.dto.js +26 -0
  135. package/dist/integration-profile/dto/test.dto.js.map +1 -0
  136. package/dist/integration-profile/integration-profile.controller.d.ts +12 -1
  137. package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
  138. package/dist/integration-profile/integration-profile.controller.js +9 -1
  139. package/dist/integration-profile/integration-profile.controller.js.map +1 -1
  140. package/dist/integration-profile/integration-profile.module.d.ts +6 -1
  141. package/dist/integration-profile/integration-profile.module.d.ts.map +1 -1
  142. package/dist/integration-profile/integration-profile.module.js +26 -3
  143. package/dist/integration-profile/integration-profile.module.js.map +1 -1
  144. package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -1
  145. package/dist/integration-profile/integration-profile.secrets.js +9 -1
  146. package/dist/integration-profile/integration-profile.secrets.js.map +1 -1
  147. package/dist/integration-profile/integration-profile.service.d.ts +26 -7
  148. package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
  149. package/dist/integration-profile/integration-profile.service.js +94 -91
  150. package/dist/integration-profile/integration-profile.service.js.map +1 -1
  151. package/dist/integration-profile/integration-profile.utils.d.ts +13 -1
  152. package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
  153. package/dist/integration-profile/integration-profile.utils.js +20 -5
  154. package/dist/integration-profile/integration-profile.utils.js.map +1 -1
  155. package/dist/integration-profile/testers/ai.testers.d.ts +3 -0
  156. package/dist/integration-profile/testers/ai.testers.d.ts.map +1 -0
  157. package/dist/integration-profile/testers/ai.testers.js +125 -0
  158. package/dist/integration-profile/testers/ai.testers.js.map +1 -0
  159. package/dist/integration-profile/testers/captcha.testers.d.ts +3 -0
  160. package/dist/integration-profile/testers/captcha.testers.d.ts.map +1 -0
  161. package/dist/integration-profile/testers/captcha.testers.js +121 -0
  162. package/dist/integration-profile/testers/captcha.testers.js.map +1 -0
  163. package/dist/integration-profile/testers/email.testers.d.ts +3 -0
  164. package/dist/integration-profile/testers/email.testers.d.ts.map +1 -0
  165. package/dist/integration-profile/testers/email.testers.js +128 -0
  166. package/dist/integration-profile/testers/email.testers.js.map +1 -0
  167. package/dist/integration-profile/testers/http-probe.d.ts +57 -0
  168. package/dist/integration-profile/testers/http-probe.d.ts.map +1 -0
  169. package/dist/integration-profile/testers/http-probe.js +140 -0
  170. package/dist/integration-profile/testers/http-probe.js.map +1 -0
  171. package/dist/integration-profile/testers/index.d.ts +12 -0
  172. package/dist/integration-profile/testers/index.d.ts.map +1 -0
  173. package/dist/integration-profile/testers/index.js +45 -0
  174. package/dist/integration-profile/testers/index.js.map +1 -0
  175. package/dist/integration-profile/testers/infrastructure.testers.d.ts +3 -0
  176. package/dist/integration-profile/testers/infrastructure.testers.d.ts.map +1 -0
  177. package/dist/integration-profile/testers/infrastructure.testers.js +105 -0
  178. package/dist/integration-profile/testers/infrastructure.testers.js.map +1 -0
  179. package/dist/integration-profile/testers/integration-tester.registry.d.ts +17 -0
  180. package/dist/integration-profile/testers/integration-tester.registry.d.ts.map +1 -0
  181. package/dist/integration-profile/testers/integration-tester.registry.js +52 -0
  182. package/dist/integration-profile/testers/integration-tester.registry.js.map +1 -0
  183. package/dist/integration-profile/testers/integration-tester.types.d.ts +55 -0
  184. package/dist/integration-profile/testers/integration-tester.types.d.ts.map +1 -0
  185. package/dist/integration-profile/testers/integration-tester.types.js +16 -0
  186. package/dist/integration-profile/testers/integration-tester.types.js.map +1 -0
  187. package/dist/integration-profile/testers/messaging.testers.d.ts +3 -0
  188. package/dist/integration-profile/testers/messaging.testers.d.ts.map +1 -0
  189. package/dist/integration-profile/testers/messaging.testers.js +70 -0
  190. package/dist/integration-profile/testers/messaging.testers.js.map +1 -0
  191. package/dist/integration-profile/testers/oauth.testers.d.ts +3 -0
  192. package/dist/integration-profile/testers/oauth.testers.d.ts.map +1 -0
  193. package/dist/integration-profile/testers/oauth.testers.js +285 -0
  194. package/dist/integration-profile/testers/oauth.testers.js.map +1 -0
  195. package/dist/integration-profile/testers/payment.testers.d.ts +3 -0
  196. package/dist/integration-profile/testers/payment.testers.d.ts.map +1 -0
  197. package/dist/integration-profile/testers/payment.testers.js +169 -0
  198. package/dist/integration-profile/testers/payment.testers.js.map +1 -0
  199. package/dist/integration-profile/testers/storage.testers.d.ts +3 -0
  200. package/dist/integration-profile/testers/storage.testers.d.ts.map +1 -0
  201. package/dist/integration-profile/testers/storage.testers.js +208 -0
  202. package/dist/integration-profile/testers/storage.testers.js.map +1 -0
  203. package/dist/language/en.json +4 -2
  204. package/dist/language/pt.json +4 -2
  205. package/dist/leaked-password/leaked-password.controller.d.ts +24 -0
  206. package/dist/leaked-password/leaked-password.controller.d.ts.map +1 -0
  207. package/dist/leaked-password/leaked-password.controller.js +78 -0
  208. package/dist/leaked-password/leaked-password.controller.js.map +1 -0
  209. package/dist/leaked-password/leaked-password.module.d.ts +8 -0
  210. package/dist/leaked-password/leaked-password.module.d.ts.map +1 -0
  211. package/dist/leaked-password/leaked-password.module.js +31 -0
  212. package/dist/leaked-password/leaked-password.module.js.map +1 -0
  213. package/dist/leaked-password/leaked-password.service.d.ts +64 -0
  214. package/dist/leaked-password/leaked-password.service.d.ts.map +1 -0
  215. package/dist/leaked-password/leaked-password.service.js +218 -0
  216. package/dist/leaked-password/leaked-password.service.js.map +1 -0
  217. package/dist/locale/locale.controller.d.ts +1 -1
  218. package/dist/locale/locale.service.d.ts +1 -1
  219. package/dist/mail/dto/preview.dto.d.ts +18 -0
  220. package/dist/mail/dto/preview.dto.d.ts.map +1 -0
  221. package/dist/mail/dto/preview.dto.js +66 -0
  222. package/dist/mail/dto/preview.dto.js.map +1 -0
  223. package/dist/mail/dto/send.dto.d.ts +7 -0
  224. package/dist/mail/dto/send.dto.d.ts.map +1 -1
  225. package/dist/mail/dto/send.dto.js +10 -0
  226. package/dist/mail/dto/send.dto.js.map +1 -1
  227. package/dist/mail/mail.controller.d.ts +4 -0
  228. package/dist/mail/mail.controller.d.ts.map +1 -1
  229. package/dist/mail/mail.controller.js +16 -2
  230. package/dist/mail/mail.controller.js.map +1 -1
  231. package/dist/mail/mail.service.d.ts +23 -2
  232. package/dist/mail/mail.service.d.ts.map +1 -1
  233. package/dist/mail/mail.service.js +102 -13
  234. package/dist/mail/mail.service.js.map +1 -1
  235. package/dist/mail-layout/dto/update.dto.d.ts +17 -0
  236. package/dist/mail-layout/dto/update.dto.d.ts.map +1 -0
  237. package/dist/mail-layout/dto/update.dto.js +27 -0
  238. package/dist/mail-layout/dto/update.dto.js.map +1 -0
  239. package/dist/mail-layout/mail-layout.controller.d.ts +15 -0
  240. package/dist/mail-layout/mail-layout.controller.d.ts.map +1 -0
  241. package/dist/mail-layout/mail-layout.controller.js +71 -0
  242. package/dist/mail-layout/mail-layout.controller.js.map +1 -0
  243. package/dist/mail-layout/mail-layout.module.d.ts +3 -0
  244. package/dist/mail-layout/mail-layout.module.d.ts.map +1 -0
  245. package/dist/mail-layout/mail-layout.module.js +35 -0
  246. package/dist/mail-layout/mail-layout.module.js.map +1 -0
  247. package/dist/mail-layout/mail-layout.service.d.ts +27 -0
  248. package/dist/mail-layout/mail-layout.service.d.ts.map +1 -0
  249. package/dist/mail-layout/mail-layout.service.js +80 -0
  250. package/dist/mail-layout/mail-layout.service.js.map +1 -0
  251. package/dist/mcp-chat/mcp-chat.controller.d.ts +2 -2
  252. package/dist/mcp-chat/mcp-chat.service.d.ts +24 -2
  253. package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
  254. package/dist/mcp-chat/mcp-chat.service.js +80 -24
  255. package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
  256. package/dist/menu/menu.controller.d.ts +3 -3
  257. package/dist/menu/menu.service.d.ts +3 -3
  258. package/dist/notification/notification.controller.d.ts +12 -12
  259. package/dist/notification/notification.service.d.ts +12 -12
  260. package/dist/oauth/oauth-token-broker.service.d.ts +72 -0
  261. package/dist/oauth/oauth-token-broker.service.d.ts.map +1 -0
  262. package/dist/oauth/oauth-token-broker.service.js +258 -0
  263. package/dist/oauth/oauth-token-broker.service.js.map +1 -0
  264. package/dist/oauth/oauth.module.d.ts.map +1 -1
  265. package/dist/oauth/oauth.module.js +3 -1
  266. package/dist/oauth/oauth.module.js.map +1 -1
  267. package/dist/oauth/oauth.service.d.ts +2 -0
  268. package/dist/oauth/oauth.service.d.ts.map +1 -1
  269. package/dist/oauth/oauth.service.js +6 -3
  270. package/dist/oauth/oauth.service.js.map +1 -1
  271. package/dist/pdf/pdf.service.d.ts +8 -0
  272. package/dist/pdf/pdf.service.d.ts.map +1 -1
  273. package/dist/pdf/pdf.service.js +52 -0
  274. package/dist/pdf/pdf.service.js.map +1 -1
  275. package/dist/presence/presence.controller.d.ts +16 -0
  276. package/dist/presence/presence.controller.d.ts.map +1 -0
  277. package/dist/presence/presence.controller.js +60 -0
  278. package/dist/presence/presence.controller.js.map +1 -0
  279. package/dist/presence/presence.listener.d.ts +29 -0
  280. package/dist/presence/presence.listener.d.ts.map +1 -0
  281. package/dist/presence/presence.listener.js +82 -0
  282. package/dist/presence/presence.listener.js.map +1 -0
  283. package/dist/presence/presence.module.d.ts +3 -0
  284. package/dist/presence/presence.module.d.ts.map +1 -0
  285. package/dist/presence/presence.module.js +32 -0
  286. package/dist/presence/presence.module.js.map +1 -0
  287. package/dist/presence/presence.service.d.ts +42 -0
  288. package/dist/presence/presence.service.d.ts.map +1 -0
  289. package/dist/presence/presence.service.js +147 -0
  290. package/dist/presence/presence.service.js.map +1 -0
  291. package/dist/profile/dto/delete-account.dto.d.ts +8 -0
  292. package/dist/profile/dto/delete-account.dto.d.ts.map +1 -0
  293. package/dist/profile/dto/delete-account.dto.js +22 -0
  294. package/dist/profile/dto/delete-account.dto.js.map +1 -0
  295. package/dist/profile/events/profile-account-deleted.event.d.ts +12 -0
  296. package/dist/profile/events/profile-account-deleted.event.d.ts.map +1 -0
  297. package/dist/profile/events/profile-account-deleted.event.js +17 -0
  298. package/dist/profile/events/profile-account-deleted.event.js.map +1 -0
  299. package/dist/profile/profile.controller.d.ts +21 -15
  300. package/dist/profile/profile.controller.d.ts.map +1 -1
  301. package/dist/profile/profile.controller.js +75 -46
  302. package/dist/profile/profile.controller.js.map +1 -1
  303. package/dist/profile/profile.service.d.ts +30 -18
  304. package/dist/profile/profile.service.d.ts.map +1 -1
  305. package/dist/profile/profile.service.js +113 -37
  306. package/dist/profile/profile.service.js.map +1 -1
  307. package/dist/realtime/realtime.gateway.d.ts +48 -0
  308. package/dist/realtime/realtime.gateway.d.ts.map +1 -0
  309. package/dist/realtime/realtime.gateway.js +156 -0
  310. package/dist/realtime/realtime.gateway.js.map +1 -0
  311. package/dist/realtime/realtime.module.d.ts +8 -0
  312. package/dist/realtime/realtime.module.d.ts.map +1 -0
  313. package/dist/realtime/realtime.module.js +29 -0
  314. package/dist/realtime/realtime.module.js.map +1 -0
  315. package/dist/realtime/realtime.service.d.ts +51 -0
  316. package/dist/realtime/realtime.service.d.ts.map +1 -0
  317. package/dist/realtime/realtime.service.js +177 -0
  318. package/dist/realtime/realtime.service.js.map +1 -0
  319. package/dist/redis/redis-pubsub.d.ts +27 -0
  320. package/dist/redis/redis-pubsub.d.ts.map +1 -0
  321. package/dist/redis/redis-pubsub.js +140 -0
  322. package/dist/redis/redis-pubsub.js.map +1 -0
  323. package/dist/role/role.controller.d.ts +3 -3
  324. package/dist/role/role.service.d.ts +3 -3
  325. package/dist/setting/dto/create.dto.d.ts.map +1 -1
  326. package/dist/setting/dto/create.dto.js +9 -2
  327. package/dist/setting/dto/create.dto.js.map +1 -1
  328. package/dist/setting/setting.controller.d.ts +5 -5
  329. package/dist/setting/setting.service.d.ts +8 -5
  330. package/dist/setting/setting.service.d.ts.map +1 -1
  331. package/dist/setting/setting.service.js +25 -1
  332. package/dist/setting/setting.service.js.map +1 -1
  333. package/dist/user/dto/create-with-email-and-password.dto.d.ts +2 -0
  334. package/dist/user/dto/create-with-email-and-password.dto.d.ts.map +1 -1
  335. package/dist/user/dto/create-with-email-and-password.dto.js +5 -0
  336. package/dist/user/dto/create-with-email-and-password.dto.js.map +1 -1
  337. package/dist/user/dto/query-user-mail-list.dto.d.ts +5 -0
  338. package/dist/user/dto/query-user-mail-list.dto.d.ts.map +1 -0
  339. package/dist/user/dto/query-user-mail-list.dto.js +25 -0
  340. package/dist/user/dto/query-user-mail-list.dto.js.map +1 -0
  341. package/dist/user/user-credential.util.d.ts +19 -0
  342. package/dist/user/user-credential.util.d.ts.map +1 -0
  343. package/dist/user/user-credential.util.js +32 -0
  344. package/dist/user/user-credential.util.js.map +1 -0
  345. package/dist/user/user.controller.d.ts +17 -5
  346. package/dist/user/user.controller.d.ts.map +1 -1
  347. package/dist/user/user.controller.js +18 -3
  348. package/dist/user/user.controller.js.map +1 -1
  349. package/dist/user/user.service.d.ts +28 -6
  350. package/dist/user/user.service.d.ts.map +1 -1
  351. package/dist/user/user.service.js +204 -13
  352. package/dist/user/user.service.js.map +1 -1
  353. package/dist/validators/is-strong-password-with-settings.validator.d.ts.map +1 -1
  354. package/dist/validators/is-strong-password-with-settings.validator.js +11 -0
  355. package/dist/validators/is-strong-password-with-settings.validator.js.map +1 -1
  356. package/dist/validators/service-locator.d.ts +9 -0
  357. package/dist/validators/service-locator.d.ts.map +1 -1
  358. package/dist/validators/service-locator.js +12 -0
  359. package/dist/validators/service-locator.js.map +1 -1
  360. package/dist/whatsapp-sender/whatsapp-sender.module.d.ts +3 -0
  361. package/dist/whatsapp-sender/whatsapp-sender.module.d.ts.map +1 -0
  362. package/dist/whatsapp-sender/whatsapp-sender.module.js +23 -0
  363. package/dist/whatsapp-sender/whatsapp-sender.module.js.map +1 -0
  364. package/dist/whatsapp-sender/whatsapp-sender.service.d.ts +50 -0
  365. package/dist/whatsapp-sender/whatsapp-sender.service.d.ts.map +1 -0
  366. package/dist/whatsapp-sender/whatsapp-sender.service.js +224 -0
  367. package/dist/whatsapp-sender/whatsapp-sender.service.js.map +1 -0
  368. package/hedhog/data/integration_provider.yaml +16 -0
  369. package/hedhog/data/mail.yaml +88 -61
  370. package/hedhog/data/mail_layout.yaml +188 -0
  371. package/hedhog/data/menu.yaml +67 -3
  372. package/hedhog/data/route.yaml +135 -0
  373. package/hedhog/data/setting_group.yaml +139 -1
  374. package/hedhog/frontend/app/account/components/mfa-setup-dialog.tsx.ejs +36 -0
  375. package/hedhog/frontend/app/account/components/two-factor-auth.tsx.ejs +2 -0
  376. package/hedhog/frontend/app/account/hooks/use-mfa-setup.ts.ejs +10 -7
  377. package/hedhog/frontend/app/ai_agent/page.tsx.ejs +7 -4
  378. package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-form.tsx.ejs +580 -0
  379. package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-preview.tsx.ejs +150 -0
  380. package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-versions.tsx.ejs +199 -0
  381. package/hedhog/frontend/app/ai_instruction/_components/prompt-diff.tsx.ejs +63 -0
  382. package/hedhog/frontend/app/ai_instruction/_lib/line-diff.spec.ts.ejs +59 -0
  383. package/hedhog/frontend/app/ai_instruction/_lib/line-diff.ts.ejs +72 -0
  384. package/hedhog/frontend/app/ai_instruction/_lib/prompt-template.spec.ts.ejs +170 -0
  385. package/hedhog/frontend/app/ai_instruction/_lib/prompt-template.ts.ejs +167 -0
  386. package/hedhog/frontend/app/ai_instruction/_lib/types.ts.ejs +99 -0
  387. package/hedhog/frontend/app/ai_instruction/page.tsx.ejs +422 -0
  388. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +51 -4
  389. package/hedhog/frontend/app/incidents/page.tsx.ejs +400 -0
  390. package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +153 -84
  391. package/hedhog/frontend/app/mail/layout/page.tsx.ejs +344 -0
  392. package/hedhog/frontend/app/mail/template/page.tsx.ejs +155 -121
  393. package/hedhog/frontend/app/mcp_chat/page.tsx.ejs +129 -17
  394. package/hedhog/frontend/app/pages/_components/page-delete-dialog.tsx.ejs +343 -0
  395. package/hedhog/frontend/app/pages/_components/page-detail-sheet.tsx.ejs +438 -0
  396. package/hedhog/frontend/app/pages/_components/page-wizard-sheet.tsx.ejs +1009 -0
  397. package/hedhog/frontend/app/pages/_components/system-page-types.ts.ejs +56 -0
  398. package/hedhog/frontend/app/pages/_components/use-system-pages.ts.ejs +171 -0
  399. package/hedhog/frontend/app/pages/page.tsx.ejs +514 -0
  400. package/hedhog/frontend/app/users/page.tsx.ejs +119 -13
  401. package/hedhog/frontend/app/users/user-activity-timeline.tsx.ejs +231 -0
  402. package/hedhog/frontend/app/users/user-mails.tsx.ejs +399 -0
  403. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +60 -75
  404. package/hedhog/frontend/messages/en.json +499 -3
  405. package/hedhog/frontend/messages/pt.json +499 -3
  406. package/hedhog/table/ai_agent.yaml +1 -0
  407. package/hedhog/table/ai_instruction.yaml +15 -0
  408. package/hedhog/table/leaked_password.yaml +18 -0
  409. package/hedhog/table/mail_layout.yaml +35 -0
  410. package/hedhog/table/mail_var.yaml +4 -0
  411. package/hedhog/table/setting.yaml +1 -0
  412. package/hedhog/table/system_incident.yaml +59 -0
  413. package/hedhog/table/user_presence.yaml +36 -0
  414. package/package.json +8 -4
  415. package/src/access-log/access-log.interceptor.ts +4 -2
  416. package/src/ai/ai-attachment.fragments.spec.ts +319 -0
  417. package/src/ai/ai-attachment.fragments.ts +35 -0
  418. package/src/ai/ai.prompts.ts +38 -0
  419. package/src/ai/ai.service.spec.ts +280 -0
  420. package/src/ai/ai.service.ts +338 -82
  421. package/src/ai/dto/chat.dto.ts +33 -3
  422. package/src/ai/dto/create-agent.dto.ts +2 -2
  423. package/src/ai/dto/update-agent.dto.ts +2 -2
  424. package/src/ai-instruction/ai-instruction.controller.ts +40 -9
  425. package/src/ai-instruction/ai-instruction.module.ts +13 -2
  426. package/src/ai-instruction/ai-instruction.service.spec.ts +658 -0
  427. package/src/ai-instruction/ai-instruction.service.ts +661 -62
  428. package/src/ai-instruction/ai-prompt-builder.service.ts +10 -1
  429. package/src/ai-instruction/ai-prompt-registry.ts +87 -0
  430. package/src/ai-instruction/ai-prompt-resolver.service.spec.ts +375 -0
  431. package/src/ai-instruction/ai-prompt-resolver.service.ts +295 -0
  432. package/src/ai-instruction/ai-prompt-template.util.spec.ts +162 -0
  433. package/src/ai-instruction/ai-prompt-template.util.ts +199 -0
  434. package/src/ai-instruction/dto/create-ai-instruction.dto.ts +39 -3
  435. package/src/ai-instruction/dto/preview-ai-instruction.dto.ts +26 -0
  436. package/src/ai-instruction/dto/update-ai-instruction.dto.ts +42 -1
  437. package/src/ai-instruction/types/ai-instruction.types.ts +24 -1
  438. package/src/auth/auth.controller.ts +31 -5
  439. package/src/auth/auth.service.spec.ts +3 -1
  440. package/src/auth/auth.service.ts +101 -36
  441. package/src/captcha/captcha.module.ts +10 -0
  442. package/src/captcha/captcha.service.spec.ts +210 -0
  443. package/src/captcha/captcha.service.ts +191 -0
  444. package/src/challenge/challenge.service.spec.ts +39 -4
  445. package/src/challenge/challenge.service.ts +72 -17
  446. package/src/core.module.ts +27 -1
  447. package/src/file/file.service.spec.ts +105 -0
  448. package/src/file/file.service.ts +60 -3
  449. package/src/incident/incident-notifier.service.spec.ts +205 -0
  450. package/src/incident/incident-notifier.service.ts +241 -0
  451. package/src/incident/incident.controller.ts +38 -0
  452. package/src/incident/incident.module.ts +19 -0
  453. package/src/incident/incident.service.spec.ts +150 -0
  454. package/src/incident/incident.service.ts +209 -0
  455. package/src/index.ts +37 -0
  456. package/src/integration-profile/dto/test.dto.ts +19 -0
  457. package/src/integration-profile/integration-profile.controller.ts +10 -1
  458. package/src/integration-profile/integration-profile.module.spec.ts +51 -0
  459. package/src/integration-profile/integration-profile.module.ts +23 -4
  460. package/src/integration-profile/integration-profile.secrets.ts +9 -1
  461. package/src/integration-profile/integration-profile.service.spec.ts +279 -0
  462. package/src/integration-profile/integration-profile.service.ts +120 -124
  463. package/src/integration-profile/integration-profile.utils.spec.ts +22 -0
  464. package/src/integration-profile/integration-profile.utils.ts +40 -3
  465. package/src/integration-profile/testers/ai.testers.ts +165 -0
  466. package/src/integration-profile/testers/captcha.testers.spec.ts +92 -0
  467. package/src/integration-profile/testers/captcha.testers.ts +139 -0
  468. package/src/integration-profile/testers/email.testers.ts +161 -0
  469. package/src/integration-profile/testers/http-probe.spec.ts +153 -0
  470. package/src/integration-profile/testers/http-probe.ts +171 -0
  471. package/src/integration-profile/testers/index.ts +30 -0
  472. package/src/integration-profile/testers/infrastructure.testers.ts +122 -0
  473. package/src/integration-profile/testers/integration-tester.registry.spec.ts +75 -0
  474. package/src/integration-profile/testers/integration-tester.registry.ts +43 -0
  475. package/src/integration-profile/testers/integration-tester.types.ts +61 -0
  476. package/src/integration-profile/testers/messaging.testers.ts +102 -0
  477. package/src/integration-profile/testers/oauth.testers.spec.ts +220 -0
  478. package/src/integration-profile/testers/oauth.testers.ts +305 -0
  479. package/src/integration-profile/testers/payment.testers.ts +185 -0
  480. package/src/integration-profile/testers/remaining.testers.spec.ts +401 -0
  481. package/src/integration-profile/testers/storage.testers.spec.ts +185 -0
  482. package/src/integration-profile/testers/storage.testers.ts +226 -0
  483. package/src/integration-profile/testers/tester-coverage.spec.ts +58 -0
  484. package/src/language/en.json +4 -2
  485. package/src/language/pt.json +4 -2
  486. package/src/leaked-password/README.md +83 -0
  487. package/src/leaked-password/leaked-password.controller.ts +52 -0
  488. package/src/leaked-password/leaked-password.module.ts +18 -0
  489. package/src/leaked-password/leaked-password.service.ts +242 -0
  490. package/src/leaked-password/seed/build-seed-sql.mjs +180 -0
  491. package/src/leaked-password/seed/common-passwords.txt +335 -0
  492. package/src/mail/dto/preview.dto.ts +50 -0
  493. package/src/mail/dto/send.dto.ts +14 -1
  494. package/src/mail/mail-template.spec.ts +197 -0
  495. package/src/mail/mail.controller.ts +9 -2
  496. package/src/mail/mail.service.spec.ts +116 -1
  497. package/src/mail/mail.service.ts +118 -12
  498. package/src/mail-layout/dto/update.dto.ts +19 -0
  499. package/src/mail-layout/mail-layout.controller.ts +47 -0
  500. package/src/mail-layout/mail-layout.module.ts +22 -0
  501. package/src/mail-layout/mail-layout.service.ts +94 -0
  502. package/src/mcp-chat/mcp-chat.service.spec.ts +236 -0
  503. package/src/mcp-chat/mcp-chat.service.ts +122 -26
  504. package/src/oauth/oauth-token-broker.service.spec.ts +172 -0
  505. package/src/oauth/oauth-token-broker.service.ts +363 -0
  506. package/src/oauth/oauth.module.ts +3 -1
  507. package/src/oauth/oauth.service.ts +8 -2
  508. package/src/pdf/pdf.service.ts +75 -0
  509. package/src/presence/presence.controller.ts +29 -0
  510. package/src/presence/presence.listener.ts +53 -0
  511. package/src/presence/presence.module.ts +19 -0
  512. package/src/presence/presence.service.spec.ts +97 -0
  513. package/src/presence/presence.service.ts +160 -0
  514. package/src/profile/dto/delete-account.dto.ts +11 -0
  515. package/src/profile/events/profile-account-deleted.event.ts +13 -0
  516. package/src/profile/profile.controller.ts +46 -34
  517. package/src/profile/profile.service.ts +138 -38
  518. package/src/realtime/realtime.gateway.ts +138 -0
  519. package/src/realtime/realtime.module.ts +16 -0
  520. package/src/realtime/realtime.service.spec.ts +50 -0
  521. package/src/realtime/realtime.service.ts +187 -0
  522. package/src/redis/redis-pubsub.ts +179 -0
  523. package/src/setting/dto/create.dto.ts +13 -2
  524. package/src/setting/setting.service.spec.ts +56 -0
  525. package/src/setting/setting.service.ts +29 -1
  526. package/src/user/dto/create-with-email-and-password.dto.ts +6 -1
  527. package/src/user/dto/query-user-mail-list.dto.ts +10 -0
  528. package/src/user/user-credential.util.ts +35 -0
  529. package/src/user/user.controller.ts +17 -2
  530. package/src/user/user.service.spec.ts +153 -0
  531. package/src/user/user.service.ts +255 -12
  532. package/src/validators/is-strong-password-with-settings.validator.ts +12 -0
  533. package/src/validators/service-locator.ts +15 -0
  534. package/src/whatsapp-sender/whatsapp-sender.module.ts +10 -0
  535. package/src/whatsapp-sender/whatsapp-sender.service.ts +313 -0
@@ -0,0 +1,187 @@
1
+ import { Injectable, Logger, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
2
+ import { randomUUID } from 'crypto';
3
+ import { createClient, type RedisClientType } from 'redis';
4
+ import type { Server } from 'socket.io';
5
+
6
+ type EmitTarget =
7
+ | { type: 'user'; target: string }
8
+ | { type: 'channel'; target: string }
9
+ | { type: 'broadcast' };
10
+
11
+ interface RealtimeMessage {
12
+ target: EmitTarget;
13
+ event: string;
14
+ payload: unknown;
15
+ originInstanceId: string;
16
+ }
17
+
18
+ /**
19
+ * Generic real-time messaging layer usable by ANY module. Wraps the Socket.IO
20
+ * server (bound by {@link RealtimeGateway}) and fans emits out across API
21
+ * instances via Redis pub/sub — mirroring the settings-sync pattern — so a
22
+ * message emitted on one pod reaches clients connected to another. Without
23
+ * `REDIS_URL` it degrades to single-instance (local emit still works).
24
+ *
25
+ * Rooms: `user:{id}` (auto-joined on connect) and `channel:{name}` (joined via
26
+ * the gateway's `subscribe` message). Other modules just inject this service and
27
+ * call {@link emitToUser} / {@link emitToChannel} / {@link broadcast}.
28
+ */
29
+ @Injectable()
30
+ export class RealtimeService implements OnModuleInit, OnModuleDestroy {
31
+ private readonly logger = new Logger(RealtimeService.name);
32
+ private readonly instanceId = randomUUID();
33
+ private readonly redisUrl = String(process.env.REDIS_URL || '').trim();
34
+ private readonly channel = 'core:realtime:emit';
35
+
36
+ private server: Server | null = null;
37
+ private redisPublisher: RedisClientType | null = null;
38
+ private redisSubscriber: RedisClientType | null = null;
39
+
40
+ /** Local (this-instance) socket count per user id, for presence/liveness. */
41
+ private readonly localConnections = new Map<number, number>();
42
+
43
+ async onModuleInit() {
44
+ await this.initRedis();
45
+ }
46
+
47
+ async onModuleDestroy() {
48
+ await Promise.allSettled([
49
+ this.safeClose(this.redisSubscriber),
50
+ this.safeClose(this.redisPublisher),
51
+ ]);
52
+ }
53
+
54
+ /** Called by the gateway once Socket.IO is initialized. */
55
+ setServer(server: Server) {
56
+ this.server = server;
57
+ }
58
+
59
+ private userRoom(userId: string | number) {
60
+ return `user:${userId}`;
61
+ }
62
+ private channelRoom(name: string) {
63
+ return `channel:${name}`;
64
+ }
65
+
66
+ /** Emits an event to every socket of a given user (all their tabs/devices). */
67
+ emitToUser(userId: string | number, event: string, payload: unknown) {
68
+ this.dispatch({ target: { type: 'user', target: String(userId) }, event, payload });
69
+ }
70
+
71
+ /** Emits an event to everyone subscribed to a named channel. */
72
+ emitToChannel(channel: string, event: string, payload: unknown) {
73
+ this.dispatch({ target: { type: 'channel', target: channel }, event, payload });
74
+ }
75
+
76
+ /** Emits an event to every connected client. */
77
+ broadcast(event: string, payload: unknown) {
78
+ this.dispatch({ target: { type: 'broadcast' }, event, payload });
79
+ }
80
+
81
+ private dispatch(msg: Omit<RealtimeMessage, 'originInstanceId'>) {
82
+ this.emitLocal(msg);
83
+ this.publish(msg);
84
+ }
85
+
86
+ private emitLocal(msg: Omit<RealtimeMessage, 'originInstanceId'>) {
87
+ if (!this.server) return;
88
+ const { target, event, payload } = msg;
89
+ if (target.type === 'user') {
90
+ this.server.to(this.userRoom(target.target)).emit(event, payload);
91
+ } else if (target.type === 'channel') {
92
+ this.server.to(this.channelRoom(target.target)).emit(event, payload);
93
+ } else {
94
+ this.server.emit(event, payload);
95
+ }
96
+ }
97
+
98
+ private publish(msg: Omit<RealtimeMessage, 'originInstanceId'>) {
99
+ if (!this.redisPublisher) return;
100
+ const full: RealtimeMessage = { ...msg, originInstanceId: this.instanceId };
101
+ this.redisPublisher
102
+ .publish(this.channel, JSON.stringify(full))
103
+ .catch((error) =>
104
+ this.logger.warn(
105
+ `Realtime publish failed: ${error instanceof Error ? error.message : 'unknown'}`,
106
+ ),
107
+ );
108
+ }
109
+
110
+ private handleRedisMessage(raw: string) {
111
+ let msg: RealtimeMessage;
112
+ try {
113
+ msg = JSON.parse(raw) as RealtimeMessage;
114
+ } catch {
115
+ return;
116
+ }
117
+ // Skip our own messages — they were already emitted locally.
118
+ if (!msg || msg.originInstanceId === this.instanceId) return;
119
+ this.emitLocal(msg);
120
+ }
121
+
122
+ // ── Connection tracking (called by the gateway) ────────────────────────────
123
+
124
+ /** Records a new local socket for a user; returns the new local count. */
125
+ trackConnect(userId: number): number {
126
+ const next = (this.localConnections.get(userId) ?? 0) + 1;
127
+ this.localConnections.set(userId, next);
128
+ return next;
129
+ }
130
+
131
+ /** Drops a local socket for a user; returns the remaining local count. */
132
+ trackDisconnect(userId: number): number {
133
+ const next = Math.max(0, (this.localConnections.get(userId) ?? 0) - 1);
134
+ if (next === 0) this.localConnections.delete(userId);
135
+ else this.localConnections.set(userId, next);
136
+ return next;
137
+ }
138
+
139
+ /** Whether the user has at least one socket on THIS instance. */
140
+ isLocallyConnected(userId: number): boolean {
141
+ return (this.localConnections.get(userId) ?? 0) > 0;
142
+ }
143
+
144
+ /** All user ids with at least one socket on this instance. */
145
+ getLocallyConnectedUserIds(): number[] {
146
+ return [...this.localConnections.keys()];
147
+ }
148
+
149
+ // ── Redis wiring (mirrors setting.service) ─────────────────────────────────
150
+
151
+ private async initRedis() {
152
+ if (!this.redisUrl) {
153
+ this.logger.log('Realtime Redis fan-out disabled (REDIS_URL not set); single-instance only.');
154
+ return;
155
+ }
156
+ try {
157
+ const opts = {
158
+ url: this.redisUrl,
159
+ socket: { reconnectStrategy: (retries: number) => Math.min(retries * 250, 5000) },
160
+ };
161
+ this.redisPublisher = createClient(opts);
162
+ this.redisSubscriber = createClient(opts);
163
+ this.redisPublisher.on('error', (e) => this.logger.warn(`Realtime Redis publisher error: ${e?.message ?? e}`));
164
+ this.redisSubscriber.on('error', (e) => this.logger.warn(`Realtime Redis subscriber error: ${e?.message ?? e}`));
165
+ await this.redisPublisher.connect();
166
+ await this.redisSubscriber.connect();
167
+ await this.redisSubscriber.subscribe(this.channel, (msg) => this.handleRedisMessage(msg));
168
+ this.logger.log(`Realtime Redis fan-out enabled on channel ${this.channel}`);
169
+ } catch (error) {
170
+ this.logger.warn(
171
+ `Failed to init realtime Redis fan-out: ${error instanceof Error ? error.message : 'unknown'}`,
172
+ );
173
+ await Promise.allSettled([this.safeClose(this.redisSubscriber), this.safeClose(this.redisPublisher)]);
174
+ this.redisPublisher = null;
175
+ this.redisSubscriber = null;
176
+ }
177
+ }
178
+
179
+ private async safeClose(client: RedisClientType | null) {
180
+ if (!client) return;
181
+ try {
182
+ if (client.isOpen) await client.quit();
183
+ } catch {
184
+ /* ignore */
185
+ }
186
+ }
187
+ }
@@ -0,0 +1,179 @@
1
+ import { Logger } from '@nestjs/common';
2
+ import { createClient, type RedisClientType } from 'redis';
3
+
4
+ export type RedisPubSub = {
5
+ /** `false` means no Redis: publishing is a no-op and this process runs single-instance. */
6
+ readonly enabled: boolean;
7
+ publish(payload: unknown): Promise<void>;
8
+ close(): Promise<void>;
9
+ };
10
+
11
+ export type CreateRedisPubSubOptions = {
12
+ channel: string;
13
+ logger: Logger;
14
+ /** Used in log lines, e.g. `ai instruction` -> "Redis ai instruction sync enabled...". */
15
+ label: string;
16
+ onMessage: (raw: string) => void | Promise<void>;
17
+ /** Defaults to `REDIS_URL`, read at call time so tests can set it before init. */
18
+ url?: string;
19
+ errorLogCooldownMs?: number;
20
+ };
21
+
22
+ const DEFAULT_ERROR_LOG_COOLDOWN_MS = 30000;
23
+
24
+ /**
25
+ * A connection failure must never take the client's data path down with it, so
26
+ * every error path here degrades to `INERT` and lets the caller keep serving
27
+ * from its local cache.
28
+ */
29
+ const INERT: RedisPubSub = {
30
+ enabled: false,
31
+ async publish() {
32
+ // no-op
33
+ },
34
+ async close() {
35
+ // no-op
36
+ },
37
+ };
38
+
39
+ /**
40
+ * `createClient` rejects with an `AggregateError` whose own `message` is empty,
41
+ * so logging it directly yields "Redis error: " with nothing after it.
42
+ */
43
+ function extractRedisErrorMessage(error: unknown): string {
44
+ if (error instanceof AggregateError && error.errors?.length) {
45
+ return error.errors
46
+ .map((e) => (e instanceof Error ? e.message || e.toString() : String(e)))
47
+ .join('; ');
48
+ }
49
+ if (error instanceof Error) {
50
+ return error.message || error.toString();
51
+ }
52
+ return String(error);
53
+ }
54
+
55
+ async function safeClose(client: RedisClientType | null) {
56
+ if (!client || !client.isOpen) {
57
+ return;
58
+ }
59
+
60
+ try {
61
+ await client.quit();
62
+ } catch {
63
+ try {
64
+ client.disconnect();
65
+ } catch {
66
+ // ignore shutdown errors
67
+ }
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Publisher/subscriber pair on a single channel, mirroring the settings-sync
73
+ * pattern. Never throws: without a URL — or if the connection fails — it returns
74
+ * an inert handle and logs once.
75
+ *
76
+ * A dedicated subscriber connection is required: node-redis v4 puts a client in
77
+ * subscriber mode, where it can no longer issue regular commands.
78
+ */
79
+ export async function createRedisPubSub(
80
+ options: CreateRedisPubSubOptions,
81
+ ): Promise<RedisPubSub> {
82
+ const { channel, logger, label, onMessage } = options;
83
+ const url = String(options.url ?? process.env.REDIS_URL ?? '').trim();
84
+
85
+ if (!url) {
86
+ logger.log(`Redis ${label} sync disabled because REDIS_URL is not configured.`);
87
+ return INERT;
88
+ }
89
+
90
+ const cooldownMs = Math.max(
91
+ 1000,
92
+ Number(options.errorLogCooldownMs || DEFAULT_ERROR_LOG_COOLDOWN_MS),
93
+ );
94
+ // A Redis outage fires `error` on every reconnect attempt; unthrottled that
95
+ // buries every other log line in the pod.
96
+ const lastErrorLogAt: Record<'subscriber' | 'publisher', number> = {
97
+ subscriber: 0,
98
+ publisher: 0,
99
+ };
100
+
101
+ const logClientError = (kind: 'subscriber' | 'publisher', error: unknown) => {
102
+ const now = Date.now();
103
+ if (now - lastErrorLogAt[kind] < cooldownMs) {
104
+ return;
105
+ }
106
+ lastErrorLogAt[kind] = now;
107
+ logger.warn(`Redis ${label} ${kind} error: ${extractRedisErrorMessage(error)}`);
108
+ };
109
+
110
+ let subscriber: RedisClientType | null = null;
111
+ let publisher: RedisClientType | null = null;
112
+
113
+ try {
114
+ const clientOptions = {
115
+ url,
116
+ socket: {
117
+ reconnectStrategy: (retries: number) => Math.min(retries * 250, 5000),
118
+ },
119
+ };
120
+
121
+ subscriber = createClient(clientOptions) as RedisClientType;
122
+ publisher = createClient(clientOptions) as RedisClientType;
123
+
124
+ // Registered before `connect` so a failure during the handshake is handled
125
+ // rather than thrown as an unhandled 'error' event.
126
+ subscriber.on('error', (error) => logClientError('subscriber', error));
127
+ publisher.on('error', (error) => logClientError('publisher', error));
128
+
129
+ await subscriber.connect();
130
+ await publisher.connect();
131
+ await subscriber.subscribe(channel, async (message) => {
132
+ try {
133
+ await onMessage(message);
134
+ } catch (error) {
135
+ logger.warn(
136
+ `Failed to handle Redis ${label} message: ${extractRedisErrorMessage(error)}`,
137
+ );
138
+ }
139
+ });
140
+
141
+ logger.log(`Redis ${label} sync enabled on channel ${channel}`);
142
+ } catch (error) {
143
+ logger.warn(
144
+ `Failed to initialize Redis ${label} sync: ${extractRedisErrorMessage(error)}`,
145
+ );
146
+
147
+ await Promise.allSettled([safeClose(subscriber), safeClose(publisher)]);
148
+
149
+ return INERT;
150
+ }
151
+
152
+ const activePublisher = publisher;
153
+ const activeSubscriber = subscriber;
154
+ let closed = false;
155
+
156
+ return {
157
+ enabled: true,
158
+ async publish(payload: unknown) {
159
+ if (closed || !activePublisher.isOpen) {
160
+ return;
161
+ }
162
+
163
+ try {
164
+ await activePublisher.publish(channel, JSON.stringify(payload));
165
+ } catch (error) {
166
+ logger.warn(
167
+ `Failed to publish Redis ${label} message: ${extractRedisErrorMessage(error)}`,
168
+ );
169
+ }
170
+ },
171
+ async close() {
172
+ if (closed) {
173
+ return;
174
+ }
175
+ closed = true;
176
+ await Promise.allSettled([safeClose(activeSubscriber), safeClose(activePublisher)]);
177
+ },
178
+ };
179
+ }
@@ -1,5 +1,13 @@
1
+ import { SettingComponentEnum } from '@hed-hog/api-types';
1
2
  import { IsBoolean, IsIn, IsInt, IsOptional, IsString, Length, ValidationArguments } from 'class-validator';
2
3
 
4
+ /**
5
+ * Derived from the enum instead of a literal list: the previous hardcoded list
6
+ * had drifted and was rejecting `input-duration`, `input-bytes` and
7
+ * `entity-picker`, which already existed in the database.
8
+ */
9
+ const SETTING_COMPONENTS = Object.values(SettingComponentEnum);
10
+
3
11
  export class CreateDTO {
4
12
  @IsString({ message: (args: ValidationArguments) => `${args.property} must be a string` })
5
13
  @Length(0, 255, { message: (args: ValidationArguments) => `${args.property} must be at most 255 characters long` })
@@ -11,10 +19,13 @@ export class CreateDTO {
11
19
  })
12
20
  type: string;
13
21
 
22
+ // Kept in sync with the `component` enum in
23
+ // libraries/core/hedhog/table/setting.yaml and SettingComponentEnum.
14
24
  @IsOptional()
15
25
  @IsString({ message: (args: ValidationArguments) => `${args.property} must be a string` })
16
- @IsIn(['input-text', 'input-number', 'input-secret', 'input-file', 'input-tags', 'combobox', 'radio', 'color-picker', 'switch', 'checkbox'], {
17
- message: (args: ValidationArguments) => `${args.property} must be one of: input-text, input-number, input-secret, input-file, input-tags, combobox, radio, color-picker, switch, checkbox`
26
+ @IsIn(SETTING_COMPONENTS, {
27
+ message: (args: ValidationArguments) =>
28
+ `${args.property} must be one of: ${SETTING_COMPONENTS.join(', ')}`,
18
29
  })
19
30
  component: string;
20
31
 
@@ -236,6 +236,62 @@ describe('SettingService', () => {
236
236
  });
237
237
  });
238
238
 
239
+ describe('ensureCacheVersionFresh', () => {
240
+ // Rajadas de leitura (ex.: lote de uploads de segmentos HLS) chegavam juntas antes de
241
+ // `lastSettingsVersionCheckAt` avançar e cada uma abria sua própria dupla de queries,
242
+ // ocupando o pool de conexões inteiro durante a conversão de vídeo.
243
+ it('deduplica checagens simultâneas em uma única ida ao banco', async () => {
244
+ const { service, deps } = makeService();
245
+ let resolveQuery: (rows: unknown[]) => void = () => {};
246
+ const pendingQuery = new Promise<unknown[]>((resolve) => {
247
+ resolveQuery = resolve;
248
+ });
249
+ deps.prisma.$queryRawUnsafe.mockReturnValue(pendingQuery);
250
+
251
+ const checks = Promise.all(
252
+ Array.from({ length: 5 }, () => (service as any).ensureCacheVersionFresh()),
253
+ );
254
+ resolveQuery([{ version: 1 }]);
255
+ await checks;
256
+
257
+ expect(deps.prisma.$queryRawUnsafe).toHaveBeenCalledTimes(1);
258
+ });
259
+
260
+ it('garante a linha de estado uma vez só, não a cada checagem', async () => {
261
+ const { service, deps } = makeService();
262
+ deps.prisma.$queryRawUnsafe.mockResolvedValue([{ version: 1 }]);
263
+ jest.useFakeTimers();
264
+
265
+ await (service as any).ensureCacheVersionFresh();
266
+ jest.advanceTimersByTime(60_000);
267
+ await (service as any).ensureCacheVersionFresh();
268
+
269
+ expect(deps.prisma.$queryRawUnsafe).toHaveBeenCalledTimes(2);
270
+ expect(deps.prisma.$executeRawUnsafe).toHaveBeenCalledTimes(1);
271
+ });
272
+
273
+ it('limpa o cache quando a versão do banco avança', async () => {
274
+ const { service, deps } = makeService();
275
+ service.setCache({ flag: true });
276
+ deps.prisma.$queryRawUnsafe.mockResolvedValue([{ version: 9 }]);
277
+
278
+ await (service as any).ensureCacheVersionFresh();
279
+
280
+ expect((service as any).cachedSettings).toEqual({});
281
+ expect((service as any).localSettingsVersion).toBe(9);
282
+ });
283
+
284
+ it('libera o dedup mesmo quando a checagem falha', async () => {
285
+ const { service, deps } = makeService();
286
+ deps.prisma.$queryRawUnsafe.mockRejectedValueOnce(new Error('too many clients'));
287
+
288
+ await expect((service as any).ensureCacheVersionFresh()).rejects.toThrow(
289
+ 'too many clients',
290
+ );
291
+ expect((service as any).settingsVersionCheckInFlight).toBeNull();
292
+ });
293
+ });
294
+
239
295
  describe('getEffectiveSettings', () => {
240
296
  it('coage overrides do usuário conforme tipo global', async () => {
241
297
  const { service } = makeService();
@@ -51,6 +51,8 @@ export class SettingService implements OnModuleInit, OnModuleDestroy {
51
51
  );
52
52
  private localSettingsVersion = 0;
53
53
  private lastSettingsVersionCheckAt = 0;
54
+ private settingsVersionCheckInFlight: Promise<void> | null = null;
55
+ private settingsVersionStateEnsured = false;
54
56
  private redisPublisher: RedisClientType | null = null;
55
57
  private redisSubscriber: RedisClientType | null = null;
56
58
  private readonly redisErrorLogCooldownMs = Math.max(
@@ -208,11 +210,18 @@ export class SettingService implements OnModuleInit, OnModuleDestroy {
208
210
  }
209
211
 
210
212
  private async ensureSettingsVersionState() {
213
+ // A linha é criada uma vez e nunca some; repetir o INSERT a cada checagem de versão
214
+ // dobrava as queries de um caminho que roda a cada 5s em todo processo.
215
+ if (this.settingsVersionStateEnsured) {
216
+ return;
217
+ }
218
+
211
219
  await this.prismaService.$executeRawUnsafe(`
212
220
  INSERT INTO "setting_runtime_state" ("scope", "version", "updated_at")
213
221
  VALUES ('global', 0, NOW())
214
222
  ON CONFLICT ("scope") DO NOTHING
215
223
  `);
224
+ this.settingsVersionStateEnsured = true;
216
225
  }
217
226
 
218
227
  private parseVersionValue(value: unknown): number {
@@ -272,8 +281,23 @@ export class SettingService implements OnModuleInit, OnModuleDestroy {
272
281
  return;
273
282
  }
274
283
 
284
+ // `lastSettingsVersionCheckAt` só avança depois do await, então uma rajada de leituras
285
+ // paralelas (ex.: lote de uploads de segmentos HLS) disparava a mesma dupla de queries
286
+ // uma vez por chamada e ocupava o pool inteiro. Todas passam a esperar a mesma checagem.
287
+ if (this.settingsVersionCheckInFlight) {
288
+ return this.settingsVersionCheckInFlight;
289
+ }
290
+
291
+ this.settingsVersionCheckInFlight = this.runSettingsVersionCheck(now).finally(() => {
292
+ this.settingsVersionCheckInFlight = null;
293
+ });
294
+
295
+ return this.settingsVersionCheckInFlight;
296
+ }
297
+
298
+ private async runSettingsVersionCheck(startedAt: number) {
275
299
  const currentVersion = await this.getSettingsVersionFromDatabase();
276
- this.lastSettingsVersionCheckAt = now;
300
+ this.lastSettingsVersionCheckAt = startedAt;
277
301
 
278
302
  if (currentVersion > this.localSettingsVersion) {
279
303
  this.localSettingsVersion = currentVersion;
@@ -471,12 +495,15 @@ export class SettingService implements OnModuleInit, OnModuleDestroy {
471
495
  'password-min-numbers',
472
496
  'password-min-uppercase',
473
497
  'password-min-length',
498
+ 'password-max-age-days',
499
+ 'password-block-leaked',
474
500
  'mfa-email-code-length',
475
501
  'mcp-enabled',
476
502
  'date-format',
477
503
  'time-format',
478
504
  'timezone',
479
505
  'menu-width',
506
+ 'pagination-page-sizes',
480
507
  'maintenance-mode-enabled',
481
508
  'providers',
482
509
  'oauth-facebook-enabled',
@@ -489,6 +516,7 @@ export class SettingService implements OnModuleInit, OnModuleDestroy {
489
516
  'disable-authentication-with-email-and-password',
490
517
  'contact-allow-company-registration',
491
518
  'operations.comment-edit-window',
519
+ 'vaults.auto-lock-seconds',
492
520
  'lms-video-conversion-enabled',
493
521
  'lms-video-frame-capture-interval-seconds',
494
522
  'lms-error-modal-enabled',
@@ -1,5 +1,5 @@
1
1
  import { getLocaleText } from '@hed-hog/api-locale';
2
- import { IsString } from 'class-validator';
2
+ import { IsOptional, IsString } from 'class-validator';
3
3
  import { IsEmailWithSettings } from '../../validators/is-email-with-settings.validator';
4
4
  import { IsStrongPasswordWithSettings } from '../../validators/is-strong-password-with-settings.validator';
5
5
 
@@ -18,4 +18,9 @@ export class CreateWithEmailAndPasswordDTO {
18
18
 
19
19
  @IsString({ message: (args) => getLocaleText('validation.nameRequired', args.value) })
20
20
  name: string;
21
+
22
+ /** Chave do app de origem (ex.: "class"), usada para resolver o nome de marca no e-mail de confirmacao. */
23
+ @IsOptional()
24
+ @IsString()
25
+ app?: string;
21
26
  }
@@ -0,0 +1,10 @@
1
+ import { PaginationDTO } from '@hed-hog/api-pagination';
2
+ import { Transform } from 'class-transformer';
3
+ import { IsIn, IsOptional } from 'class-validator';
4
+
5
+ export class QueryUserMailListDTO extends PaginationDTO {
6
+ @IsOptional()
7
+ @Transform(({ value }) => String(value ?? '').trim().toLowerCase())
8
+ @IsIn(['all', 'received', 'read', 'error'])
9
+ status?: 'all' | 'received' | 'read' | 'error';
10
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Regra única de "reset de senha pendente": credencial de senha ativa
3
+ * (não revogada) marcada com requires_reset.
4
+ */
5
+ export const PENDING_PASSWORD_RESET_WHERE = {
6
+ some: {
7
+ type: 'password',
8
+ requires_reset: true,
9
+ revoked_at: null,
10
+ },
11
+ } as const;
12
+
13
+ export function hasPendingPasswordReset(user: any): boolean {
14
+ return Boolean(
15
+ user?.user_credential?.some(
16
+ (credential) =>
17
+ credential?.type === 'password' &&
18
+ credential?.requires_reset === true &&
19
+ !credential?.revoked_at,
20
+ ),
21
+ );
22
+ }
23
+
24
+ /**
25
+ * Usuários criados/autenticados apenas via provedor externo (ex: Microsoft
26
+ * Entra ID) nunca têm uma credencial de senha ativa — não faz sentido
27
+ * oferecer o fluxo de "esqueci a senha" para eles.
28
+ */
29
+ export function hasPasswordCredential(user: any): boolean {
30
+ return Boolean(
31
+ user?.user_credential?.some(
32
+ (credential) => credential?.type === 'password' && !credential?.revoked_at,
33
+ ),
34
+ );
35
+ }
@@ -12,6 +12,7 @@ import {
12
12
  ParseIntPipe,
13
13
  Patch,
14
14
  Post,
15
+ Query,
15
16
  Req,
16
17
  Res,
17
18
  UploadedFile,
@@ -22,6 +23,7 @@ import { FileInterceptor } from '@nestjs/platform-express';
22
23
  import { Request } from 'express';
23
24
  import { DeleteDTO } from '../dto/delete.dto';
24
25
  import { CreateWithEmailAndPasswordDTO } from './dto/create-with-email-and-password.dto';
26
+ import { QueryUserMailListDTO } from './dto/query-user-mail-list.dto';
25
27
  import { ResetPasswordDTO } from './dto/reset-password.dto';
26
28
  import { UpdateDTO } from './dto/update.dto';
27
29
  import { UserService } from './user.service';
@@ -35,8 +37,8 @@ export class UserController {
35
37
  ) { }
36
38
 
37
39
  @Get()
38
- async list(@Pagination() paginationParams) {
39
- return this.userService.list(paginationParams);
40
+ async list(@Pagination() paginationParams, @Query('filter') filter?: string) {
41
+ return this.userService.list(paginationParams, filter);
40
42
  }
41
43
 
42
44
  @Get(':userId')
@@ -154,6 +156,19 @@ export class UserController {
154
156
  return this.userService.listUserChangeLogs(locale, userId, paginationParams);
155
157
  }
156
158
 
159
+ @Get(':userId/mail')
160
+ async listMails(
161
+ @Param('userId', ParseIntPipe) userId: number,
162
+ @Pagination() paginationParams,
163
+ @Query() query: QueryUserMailListDTO,
164
+ @Locale() locale: string,
165
+ ) {
166
+ return this.userService.listUserMails(locale, userId, {
167
+ ...paginationParams,
168
+ status: query.status,
169
+ });
170
+ }
171
+
157
172
  @Post(':userId/role/:roleId')
158
173
  async assignRoleToUser(
159
174
  @Param('userId', ParseIntPipe) userId: number,