@hed-hog/core 0.0.399 → 0.0.400

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 (668) hide show
  1. package/dist/ai/ai-agent.sort.d.ts +6 -0
  2. package/dist/ai/ai-agent.sort.d.ts.map +1 -0
  3. package/dist/ai/ai-agent.sort.js +21 -0
  4. package/dist/ai/ai-agent.sort.js.map +1 -0
  5. package/dist/ai/ai.controller.d.ts +4 -4
  6. package/dist/ai/ai.service.d.ts +35 -4
  7. package/dist/ai/ai.service.d.ts.map +1 -1
  8. package/dist/ai/ai.service.js +51 -137
  9. package/dist/ai/ai.service.js.map +1 -1
  10. package/dist/ai-instruction/ai-instruction.service.d.ts.map +1 -1
  11. package/dist/ai-instruction/ai-instruction.service.js +26 -23
  12. package/dist/ai-instruction/ai-instruction.service.js.map +1 -1
  13. package/dist/ai-instruction/ai-instruction.sort.d.ts +16 -0
  14. package/dist/ai-instruction/ai-instruction.sort.d.ts.map +1 -0
  15. package/dist/ai-instruction/ai-instruction.sort.js +32 -0
  16. package/dist/ai-instruction/ai-instruction.sort.js.map +1 -0
  17. package/dist/auth/auth.controller.d.ts +24 -8
  18. package/dist/auth/auth.controller.d.ts.map +1 -1
  19. package/dist/auth/auth.module.d.ts.map +1 -1
  20. package/dist/auth/auth.module.js +8 -1
  21. package/dist/auth/auth.module.js.map +1 -1
  22. package/dist/auth/auth.service.d.ts +72 -25
  23. package/dist/auth/auth.service.d.ts.map +1 -1
  24. package/dist/auth/auth.service.js +41 -4
  25. package/dist/auth/auth.service.js.map +1 -1
  26. package/dist/auth/guards/auth.guard.d.ts +3 -1
  27. package/dist/auth/guards/auth.guard.d.ts.map +1 -1
  28. package/dist/auth/guards/auth.guard.js +12 -3
  29. package/dist/auth/guards/auth.guard.js.map +1 -1
  30. package/dist/authorize/authorize.controller.d.ts +29 -5
  31. package/dist/authorize/authorize.controller.d.ts.map +1 -1
  32. package/dist/authorize/authorize.controller.js +120 -13
  33. package/dist/authorize/authorize.controller.js.map +1 -1
  34. package/dist/authorize/authorize.errors.d.ts +34 -0
  35. package/dist/authorize/authorize.errors.d.ts.map +1 -0
  36. package/dist/authorize/authorize.errors.js +37 -0
  37. package/dist/authorize/authorize.errors.js.map +1 -0
  38. package/dist/authorize/authorize.module.d.ts.map +1 -1
  39. package/dist/authorize/authorize.module.js +6 -1
  40. package/dist/authorize/authorize.module.js.map +1 -1
  41. package/dist/authorize/authorize.service.d.ts +77 -3
  42. package/dist/authorize/authorize.service.d.ts.map +1 -1
  43. package/dist/authorize/authorize.service.js +212 -21
  44. package/dist/authorize/authorize.service.js.map +1 -1
  45. package/dist/authorize/dto/authorize-info.dto.d.ts +10 -1
  46. package/dist/authorize/dto/authorize-info.dto.d.ts.map +1 -1
  47. package/dist/authorize/dto/authorize-info.dto.js +16 -1
  48. package/dist/authorize/dto/authorize-info.dto.js.map +1 -1
  49. package/dist/authorize/dto/consent.dto.d.ts +10 -1
  50. package/dist/authorize/dto/consent.dto.d.ts.map +1 -1
  51. package/dist/authorize/dto/consent.dto.js +16 -1
  52. package/dist/authorize/dto/consent.dto.js.map +1 -1
  53. package/dist/authorize/dto/token.dto.d.ts +12 -0
  54. package/dist/authorize/dto/token.dto.d.ts.map +1 -1
  55. package/dist/authorize/dto/token.dto.js +21 -1
  56. package/dist/authorize/dto/token.dto.js.map +1 -1
  57. package/dist/authorize/oauth-exception.filter.d.ts +17 -0
  58. package/dist/authorize/oauth-exception.filter.d.ts.map +1 -0
  59. package/dist/authorize/oauth-exception.filter.js +54 -0
  60. package/dist/authorize/oauth-exception.filter.js.map +1 -0
  61. package/dist/authorize/oauth-metadata.service.d.ts +39 -0
  62. package/dist/authorize/oauth-metadata.service.d.ts.map +1 -0
  63. package/dist/authorize/oauth-metadata.service.js +81 -0
  64. package/dist/authorize/oauth-metadata.service.js.map +1 -0
  65. package/dist/authorize/well-known.controller.d.ts +67 -0
  66. package/dist/authorize/well-known.controller.d.ts.map +1 -0
  67. package/dist/authorize/well-known.controller.js +126 -0
  68. package/dist/authorize/well-known.controller.js.map +1 -0
  69. package/dist/captcha/captcha.service.d.ts +32 -2
  70. package/dist/captcha/captcha.service.d.ts.map +1 -1
  71. package/dist/captcha/captcha.service.js +85 -12
  72. package/dist/captcha/captcha.service.js.map +1 -1
  73. package/dist/challenge/challenge.service.d.ts +2 -2
  74. package/dist/core.module.d.ts.map +1 -1
  75. package/dist/core.module.js +6 -0
  76. package/dist/core.module.js.map +1 -1
  77. package/dist/dashboard/dashboard/dashboard.controller.d.ts +9 -9
  78. package/dist/dashboard/dashboard/dashboard.service.d.ts +9 -9
  79. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +3 -3
  80. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +3 -3
  81. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +3 -3
  82. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +3 -3
  83. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts.map +1 -1
  84. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.js +2 -4
  85. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.js.map +1 -1
  86. package/dist/dashboard/dashboard-component-role/dashboard-component-role.sort.d.ts +8 -0
  87. package/dist/dashboard/dashboard-component-role/dashboard-component-role.sort.d.ts.map +1 -0
  88. package/dist/dashboard/dashboard-component-role/dashboard-component-role.sort.js +18 -0
  89. package/dist/dashboard/dashboard-component-role/dashboard-component-role.sort.js.map +1 -0
  90. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts.map +1 -1
  91. package/dist/dashboard/dashboard-item/dashboard-item.service.js +2 -4
  92. package/dist/dashboard/dashboard-item/dashboard-item.service.js.map +1 -1
  93. package/dist/dashboard/dashboard-item/dashboard-item.sort.d.ts +9 -0
  94. package/dist/dashboard/dashboard-item/dashboard-item.sort.d.ts.map +1 -0
  95. package/dist/dashboard/dashboard-item/dashboard-item.sort.js +19 -0
  96. package/dist/dashboard/dashboard-item/dashboard-item.sort.js.map +1 -0
  97. package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +3 -3
  98. package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +3 -3
  99. package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts.map +1 -1
  100. package/dist/dashboard/dashboard-role/dashboard-role.service.js +2 -4
  101. package/dist/dashboard/dashboard-role/dashboard-role.service.js.map +1 -1
  102. package/dist/dashboard/dashboard-role/dashboard-role.sort.d.ts +8 -0
  103. package/dist/dashboard/dashboard-role/dashboard-role.sort.d.ts.map +1 -0
  104. package/dist/dashboard/dashboard-role/dashboard-role.sort.js +16 -0
  105. package/dist/dashboard/dashboard-role/dashboard-role.sort.js.map +1 -0
  106. package/dist/file/file-upload-ticket.service.d.ts +160 -0
  107. package/dist/file/file-upload-ticket.service.d.ts.map +1 -0
  108. package/dist/file/file-upload-ticket.service.js +382 -0
  109. package/dist/file/file-upload-ticket.service.js.map +1 -0
  110. package/dist/file/file.controller.d.ts +24 -1
  111. package/dist/file/file.controller.d.ts.map +1 -1
  112. package/dist/file/file.controller.js +63 -2
  113. package/dist/file/file.controller.js.map +1 -1
  114. package/dist/file/file.module.d.ts.map +1 -1
  115. package/dist/file/file.module.js +14 -2
  116. package/dist/file/file.module.js.map +1 -1
  117. package/dist/file/file.service.d.ts +182 -6
  118. package/dist/file/file.service.d.ts.map +1 -1
  119. package/dist/file/file.service.js +386 -54
  120. package/dist/file/file.service.js.map +1 -1
  121. package/dist/file/filename-encoding.util.d.ts +24 -0
  122. package/dist/file/filename-encoding.util.d.ts.map +1 -0
  123. package/dist/file/filename-encoding.util.js +63 -0
  124. package/dist/file/filename-encoding.util.js.map +1 -0
  125. package/dist/file/image-optimization.service.d.ts +27 -0
  126. package/dist/file/image-optimization.service.d.ts.map +1 -1
  127. package/dist/file/image-optimization.service.js +63 -14
  128. package/dist/file/image-optimization.service.js.map +1 -1
  129. package/dist/file/mcp-file-source.schema.d.ts +33 -0
  130. package/dist/file/mcp-file-source.schema.d.ts.map +1 -0
  131. package/dist/file/mcp-file-source.schema.js +75 -0
  132. package/dist/file/mcp-file-source.schema.js.map +1 -0
  133. package/dist/file/mcp-file-source.service.d.ts +124 -0
  134. package/dist/file/mcp-file-source.service.d.ts.map +1 -0
  135. package/dist/file/mcp-file-source.service.js +262 -0
  136. package/dist/file/mcp-file-source.service.js.map +1 -0
  137. package/dist/file/mimetype-sniff.util.d.ts +62 -0
  138. package/dist/file/mimetype-sniff.util.d.ts.map +1 -0
  139. package/dist/file/mimetype-sniff.util.js +226 -0
  140. package/dist/file/mimetype-sniff.util.js.map +1 -0
  141. package/dist/file/remote-fetch.util.d.ts +35 -0
  142. package/dist/file/remote-fetch.util.d.ts.map +1 -0
  143. package/dist/file/remote-fetch.util.js +236 -0
  144. package/dist/file/remote-fetch.util.js.map +1 -0
  145. package/dist/impersonation/dto/start-impersonation.dto.d.ts +4 -1
  146. package/dist/impersonation/dto/start-impersonation.dto.d.ts.map +1 -1
  147. package/dist/impersonation/dto/start-impersonation.dto.js +1 -1
  148. package/dist/impersonation/dto/start-impersonation.dto.js.map +1 -1
  149. package/dist/impersonation/impersonation.service.d.ts +8 -3
  150. package/dist/impersonation/impersonation.service.d.ts.map +1 -1
  151. package/dist/impersonation/impersonation.service.js +14 -4
  152. package/dist/impersonation/impersonation.service.js.map +1 -1
  153. package/dist/incident/incident.controller.d.ts +2 -1
  154. package/dist/incident/incident.controller.d.ts.map +1 -1
  155. package/dist/incident/incident.controller.js +10 -6
  156. package/dist/incident/incident.controller.js.map +1 -1
  157. package/dist/incident/incident.service.d.ts +2 -0
  158. package/dist/incident/incident.service.d.ts.map +1 -1
  159. package/dist/incident/incident.service.js +4 -0
  160. package/dist/incident/incident.service.js.map +1 -1
  161. package/dist/index.d.ts +17 -0
  162. package/dist/index.d.ts.map +1 -1
  163. package/dist/index.js +17 -0
  164. package/dist/index.js.map +1 -1
  165. package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -1
  166. package/dist/integration-profile/integration-profile.secrets.js +7 -0
  167. package/dist/integration-profile/integration-profile.secrets.js.map +1 -1
  168. package/dist/integration-profile/integration-profile.service.d.ts +0 -13
  169. package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
  170. package/dist/integration-profile/integration-profile.service.js +5 -40
  171. package/dist/integration-profile/integration-profile.service.js.map +1 -1
  172. package/dist/integration-profile/integration-profile.utils.js +5 -5
  173. package/dist/integration-profile/integration-profile.utils.js.map +1 -1
  174. package/dist/integration-profile/testers/index.d.ts.map +1 -1
  175. package/dist/integration-profile/testers/index.js +2 -0
  176. package/dist/integration-profile/testers/index.js.map +1 -1
  177. package/dist/integration-profile/testers/shipping.testers.d.ts +3 -0
  178. package/dist/integration-profile/testers/shipping.testers.d.ts.map +1 -0
  179. package/dist/integration-profile/testers/shipping.testers.js +71 -0
  180. package/dist/integration-profile/testers/shipping.testers.js.map +1 -0
  181. package/dist/locale/locale.controller.d.ts +1 -1
  182. package/dist/locale/locale.service.d.ts +1 -1
  183. package/dist/mail/dto/send.dto.d.ts +6 -1
  184. package/dist/mail/dto/send.dto.d.ts.map +1 -1
  185. package/dist/mail/dto/send.dto.js.map +1 -1
  186. package/dist/mail/mail.service.d.ts +9 -0
  187. package/dist/mail/mail.service.d.ts.map +1 -1
  188. package/dist/mail/mail.service.js +34 -14
  189. package/dist/mail/mail.service.js.map +1 -1
  190. package/dist/mail-sent/mail-sent.controller.d.ts +1 -1
  191. package/dist/mail-sent/mail-sent.service.d.ts +1 -1
  192. package/dist/mail-sent/mail-sent.service.d.ts.map +1 -1
  193. package/dist/mail-sent/mail-sent.service.js +2 -1
  194. package/dist/mail-sent/mail-sent.service.js.map +1 -1
  195. package/dist/mail-sent/mail-sent.sort.d.ts +9 -0
  196. package/dist/mail-sent/mail-sent.sort.d.ts.map +1 -0
  197. package/dist/mail-sent/mail-sent.sort.js +19 -0
  198. package/dist/mail-sent/mail-sent.sort.js.map +1 -0
  199. package/dist/mcp/filters/mcp-challenge.filter.d.ts +20 -0
  200. package/dist/mcp/filters/mcp-challenge.filter.d.ts.map +1 -0
  201. package/dist/mcp/filters/mcp-challenge.filter.js +69 -0
  202. package/dist/mcp/filters/mcp-challenge.filter.js.map +1 -0
  203. package/dist/mcp/guards/mcp-auth.guard.d.ts +4 -1
  204. package/dist/mcp/guards/mcp-auth.guard.d.ts.map +1 -1
  205. package/dist/mcp/guards/mcp-auth.guard.js +19 -5
  206. package/dist/mcp/guards/mcp-auth.guard.js.map +1 -1
  207. package/dist/mcp/guards/mcp-permission.guard.d.ts +25 -3
  208. package/dist/mcp/guards/mcp-permission.guard.d.ts.map +1 -1
  209. package/dist/mcp/guards/mcp-permission.guard.js +46 -26
  210. package/dist/mcp/guards/mcp-permission.guard.js.map +1 -1
  211. package/dist/mcp/mcp-audit.service.d.ts +17 -0
  212. package/dist/mcp/mcp-audit.service.d.ts.map +1 -1
  213. package/dist/mcp/mcp-audit.service.js +32 -3
  214. package/dist/mcp/mcp-audit.service.js.map +1 -1
  215. package/dist/mcp/mcp-permission.service.d.ts +64 -0
  216. package/dist/mcp/mcp-permission.service.d.ts.map +1 -0
  217. package/dist/mcp/mcp-permission.service.js +178 -0
  218. package/dist/mcp/mcp-permission.service.js.map +1 -0
  219. package/dist/mcp/mcp-server.service.d.ts +3 -1
  220. package/dist/mcp/mcp-server.service.d.ts.map +1 -1
  221. package/dist/mcp/mcp-server.service.js +36 -15
  222. package/dist/mcp/mcp-server.service.js.map +1 -1
  223. package/dist/mcp/mcp.controller.d.ts.map +1 -1
  224. package/dist/mcp/mcp.controller.js +10 -2
  225. package/dist/mcp/mcp.controller.js.map +1 -1
  226. package/dist/mcp/mcp.module.d.ts.map +1 -1
  227. package/dist/mcp/mcp.module.js +5 -1
  228. package/dist/mcp/mcp.module.js.map +1 -1
  229. package/dist/mcp/tools/core-ai.mcp-tools.js +2 -9
  230. package/dist/mcp/tools/core-ai.mcp-tools.js.map +1 -1
  231. package/dist/mcp/tools/core-dashboard-items.mcp-tools.js +1 -1
  232. package/dist/mcp/tools/core-dashboard-items.mcp-tools.js.map +1 -1
  233. package/dist/mcp/tools/core-dashboard.mcp-tools.js +2 -9
  234. package/dist/mcp/tools/core-dashboard.mcp-tools.js.map +1 -1
  235. package/dist/mcp/tools/core-event-webhooks.mcp-tools.d.ts.map +1 -1
  236. package/dist/mcp/tools/core-event-webhooks.mcp-tools.js +2 -10
  237. package/dist/mcp/tools/core-event-webhooks.mcp-tools.js.map +1 -1
  238. package/dist/mcp/tools/core-files.mcp-tools.d.ts +17 -5
  239. package/dist/mcp/tools/core-files.mcp-tools.d.ts.map +1 -1
  240. package/dist/mcp/tools/core-files.mcp-tools.js +186 -24
  241. package/dist/mcp/tools/core-files.mcp-tools.js.map +1 -1
  242. package/dist/mcp/tools/core-integration-events.mcp-tools.js +2 -9
  243. package/dist/mcp/tools/core-integration-events.mcp-tools.js.map +1 -1
  244. package/dist/mcp/tools/core-mail-sent.mcp-tools.js +2 -14
  245. package/dist/mcp/tools/core-mail-sent.mcp-tools.js.map +1 -1
  246. package/dist/mcp/tools/core-mail.mcp-tools.js +2 -9
  247. package/dist/mcp/tools/core-mail.mcp-tools.js.map +1 -1
  248. package/dist/mcp/tools/core-menus.mcp-tools.d.ts.map +1 -1
  249. package/dist/mcp/tools/core-menus.mcp-tools.js +2 -10
  250. package/dist/mcp/tools/core-menus.mcp-tools.js.map +1 -1
  251. package/dist/mcp/tools/core-notifications.mcp-tools.d.ts.map +1 -1
  252. package/dist/mcp/tools/core-notifications.mcp-tools.js +0 -1
  253. package/dist/mcp/tools/core-notifications.mcp-tools.js.map +1 -1
  254. package/dist/mcp/tools/core-roles.mcp-tools.d.ts.map +1 -1
  255. package/dist/mcp/tools/core-roles.mcp-tools.js +2 -13
  256. package/dist/mcp/tools/core-roles.mcp-tools.js.map +1 -1
  257. package/dist/mcp/tools/core-routes.mcp-tools.d.ts.map +1 -1
  258. package/dist/mcp/tools/core-routes.mcp-tools.js +2 -11
  259. package/dist/mcp/tools/core-routes.mcp-tools.js.map +1 -1
  260. package/dist/mcp/tools/core-screens.mcp-tools.d.ts.map +1 -1
  261. package/dist/mcp/tools/core-screens.mcp-tools.js +2 -11
  262. package/dist/mcp/tools/core-screens.mcp-tools.js.map +1 -1
  263. package/dist/mcp/tools/core-sessions.mcp-tools.d.ts.map +1 -1
  264. package/dist/mcp/tools/core-sessions.mcp-tools.js +0 -2
  265. package/dist/mcp/tools/core-sessions.mcp-tools.js.map +1 -1
  266. package/dist/mcp/tools/core-settings.mcp-tools.d.ts.map +1 -1
  267. package/dist/mcp/tools/core-settings.mcp-tools.js +2 -10
  268. package/dist/mcp/tools/core-settings.mcp-tools.js.map +1 -1
  269. package/dist/mcp/tools/core-users.mcp-tools.js +2 -9
  270. package/dist/mcp/tools/core-users.mcp-tools.js.map +1 -1
  271. package/dist/mcp/tools/core-webhooks.mcp-tools.d.ts.map +1 -1
  272. package/dist/mcp/tools/core-webhooks.mcp-tools.js +2 -10
  273. package/dist/mcp/tools/core-webhooks.mcp-tools.js.map +1 -1
  274. package/dist/mcp/types/mcp-context.type.d.ts +9 -0
  275. package/dist/mcp/types/mcp-context.type.d.ts.map +1 -1
  276. package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
  277. package/dist/mcp-chat/mcp-chat.service.js +8 -0
  278. package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
  279. package/dist/meeting/meeting-provider.registry.d.ts +115 -0
  280. package/dist/meeting/meeting-provider.registry.d.ts.map +1 -1
  281. package/dist/meeting/meeting-provider.registry.js.map +1 -1
  282. package/dist/meeting/meeting.constants.d.ts +70 -0
  283. package/dist/meeting/meeting.constants.d.ts.map +1 -1
  284. package/dist/meeting/meeting.constants.js +9 -1
  285. package/dist/meeting/meeting.constants.js.map +1 -1
  286. package/dist/menu/menu.service.d.ts.map +1 -1
  287. package/dist/menu/menu.service.js +29 -9
  288. package/dist/menu/menu.service.js.map +1 -1
  289. package/dist/message-template/dto/update-message-template.dto.d.ts +18 -0
  290. package/dist/message-template/dto/update-message-template.dto.d.ts.map +1 -0
  291. package/dist/message-template/dto/update-message-template.dto.js +38 -0
  292. package/dist/message-template/dto/update-message-template.dto.js.map +1 -0
  293. package/dist/message-template/message-template-admin.service.d.ts +70 -0
  294. package/dist/message-template/message-template-admin.service.d.ts.map +1 -0
  295. package/dist/message-template/message-template-admin.service.js +226 -0
  296. package/dist/message-template/message-template-admin.service.js.map +1 -0
  297. package/dist/message-template/message-template.controller.d.ts +29 -0
  298. package/dist/message-template/message-template.controller.d.ts.map +1 -0
  299. package/dist/message-template/message-template.controller.js +101 -0
  300. package/dist/message-template/message-template.controller.js.map +1 -0
  301. package/dist/message-template/message-template.module.d.ts +10 -0
  302. package/dist/message-template/message-template.module.d.ts.map +1 -0
  303. package/dist/message-template/message-template.module.js +33 -0
  304. package/dist/message-template/message-template.module.js.map +1 -0
  305. package/dist/message-template/message-template.registry.d.ts +55 -0
  306. package/dist/message-template/message-template.registry.d.ts.map +1 -0
  307. package/dist/message-template/message-template.registry.js +64 -0
  308. package/dist/message-template/message-template.registry.js.map +1 -0
  309. package/dist/message-template/message-template.service.d.ts +59 -0
  310. package/dist/message-template/message-template.service.d.ts.map +1 -0
  311. package/dist/message-template/message-template.service.js +166 -0
  312. package/dist/message-template/message-template.service.js.map +1 -0
  313. package/dist/notification/notification.constants.d.ts +11 -0
  314. package/dist/notification/notification.constants.d.ts.map +1 -0
  315. package/dist/notification/notification.constants.js +14 -0
  316. package/dist/notification/notification.constants.js.map +1 -0
  317. package/dist/notification/notification.controller.d.ts +20 -20
  318. package/dist/notification/notification.module.d.ts.map +1 -1
  319. package/dist/notification/notification.module.js +3 -0
  320. package/dist/notification/notification.module.js.map +1 -1
  321. package/dist/notification/notification.service.d.ts +33 -21
  322. package/dist/notification/notification.service.d.ts.map +1 -1
  323. package/dist/notification/notification.service.js +36 -2
  324. package/dist/notification/notification.service.js.map +1 -1
  325. package/dist/oauth/oauth.controller.d.ts +4 -6
  326. package/dist/oauth/oauth.controller.d.ts.map +1 -1
  327. package/dist/oauth/oauth.controller.js +0 -1
  328. package/dist/oauth/oauth.controller.js.map +1 -1
  329. package/dist/oauth/oauth.errors.d.ts +15 -1
  330. package/dist/oauth/oauth.errors.d.ts.map +1 -1
  331. package/dist/oauth/oauth.errors.js +9 -1
  332. package/dist/oauth/oauth.errors.js.map +1 -1
  333. package/dist/oauth/oauth.module.d.ts.map +1 -1
  334. package/dist/oauth/oauth.module.js +6 -1
  335. package/dist/oauth/oauth.module.js.map +1 -1
  336. package/dist/oauth/oauth.service.d.ts +25 -5
  337. package/dist/oauth/oauth.service.d.ts.map +1 -1
  338. package/dist/oauth/oauth.service.js +63 -27
  339. package/dist/oauth/oauth.service.js.map +1 -1
  340. package/dist/oauth/providers/abstract.provider.d.ts +10 -0
  341. package/dist/oauth/providers/abstract.provider.d.ts.map +1 -1
  342. package/dist/oauth/providers/abstract.provider.js +39 -12
  343. package/dist/oauth/providers/abstract.provider.js.map +1 -1
  344. package/dist/oauth/providers/github.provider.d.ts +1 -1
  345. package/dist/oauth/providers/github.provider.d.ts.map +1 -1
  346. package/dist/oauth/providers/github.provider.js +48 -22
  347. package/dist/oauth/providers/github.provider.js.map +1 -1
  348. package/dist/oauth/providers/microsoft-entra-id.provider.d.ts.map +1 -1
  349. package/dist/oauth/providers/microsoft-entra-id.provider.js +6 -0
  350. package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
  351. package/dist/observability/monitored-cron.d.ts +29 -12
  352. package/dist/observability/monitored-cron.d.ts.map +1 -1
  353. package/dist/observability/monitored-cron.js +131 -46
  354. package/dist/observability/monitored-cron.js.map +1 -1
  355. package/dist/pairing/pairing.service.d.ts +6 -2
  356. package/dist/pairing/pairing.service.d.ts.map +1 -1
  357. package/dist/profile/dto/change-password.dto.d.ts.map +1 -1
  358. package/dist/profile/dto/change-password.dto.js +4 -6
  359. package/dist/profile/dto/change-password.dto.js.map +1 -1
  360. package/dist/profile/profile.controller.d.ts +1 -1
  361. package/dist/profile/profile.service.d.ts +1 -1
  362. package/dist/profile/profile.service.d.ts.map +1 -1
  363. package/dist/profile/profile.service.js +9 -3
  364. package/dist/profile/profile.service.js.map +1 -1
  365. package/dist/realtime/realtime-tick.coalescer.d.ts +34 -0
  366. package/dist/realtime/realtime-tick.coalescer.d.ts.map +1 -0
  367. package/dist/realtime/realtime-tick.coalescer.js +60 -0
  368. package/dist/realtime/realtime-tick.coalescer.js.map +1 -0
  369. package/dist/realtime/realtime.service.d.ts +2 -0
  370. package/dist/realtime/realtime.service.d.ts.map +1 -1
  371. package/dist/realtime/realtime.service.js +22 -2
  372. package/dist/realtime/realtime.service.js.map +1 -1
  373. package/dist/redis/redis-kv.service.d.ts +90 -0
  374. package/dist/redis/redis-kv.service.d.ts.map +1 -0
  375. package/dist/redis/redis-kv.service.js +247 -0
  376. package/dist/redis/redis-kv.service.js.map +1 -0
  377. package/dist/redis/redis-master.d.ts +29 -0
  378. package/dist/redis/redis-master.d.ts.map +1 -0
  379. package/dist/redis/redis-master.js +81 -0
  380. package/dist/redis/redis-master.js.map +1 -0
  381. package/dist/redis/redis-pubsub.d.ts +12 -0
  382. package/dist/redis/redis-pubsub.d.ts.map +1 -1
  383. package/dist/redis/redis-pubsub.js +25 -2
  384. package/dist/redis/redis-pubsub.js.map +1 -1
  385. package/dist/redis/redis.module.d.ts +11 -0
  386. package/dist/redis/redis.module.d.ts.map +1 -0
  387. package/dist/redis/redis.module.js +30 -0
  388. package/dist/redis/redis.module.js.map +1 -0
  389. package/dist/session/session.controller.d.ts +6 -2
  390. package/dist/session/session.controller.d.ts.map +1 -1
  391. package/dist/session/session.service.d.ts +118 -15
  392. package/dist/session/session.service.d.ts.map +1 -1
  393. package/dist/session/session.service.js +251 -77
  394. package/dist/session/session.service.js.map +1 -1
  395. package/dist/setting/setting.controller.d.ts +5 -0
  396. package/dist/setting/setting.controller.d.ts.map +1 -1
  397. package/dist/setting/setting.service.d.ts +7 -0
  398. package/dist/setting/setting.service.d.ts.map +1 -1
  399. package/dist/setting/setting.service.js +38 -3
  400. package/dist/setting/setting.service.js.map +1 -1
  401. package/dist/task/task.service.d.ts +17 -0
  402. package/dist/task/task.service.d.ts.map +1 -1
  403. package/dist/task/task.service.js +48 -13
  404. package/dist/task/task.service.js.map +1 -1
  405. package/dist/token/token.service.d.ts +19 -1
  406. package/dist/token/token.service.d.ts.map +1 -1
  407. package/dist/token/token.service.js +39 -2
  408. package/dist/token/token.service.js.map +1 -1
  409. package/dist/user/user-credential.util.d.ts +10 -0
  410. package/dist/user/user-credential.util.d.ts.map +1 -1
  411. package/dist/user/user-credential.util.js +14 -1
  412. package/dist/user/user-credential.util.js.map +1 -1
  413. package/dist/user/user.service.d.ts +12 -4
  414. package/dist/user/user.service.d.ts.map +1 -1
  415. package/dist/user/user.service.js +2 -11
  416. package/dist/user/user.service.js.map +1 -1
  417. package/dist/user/user.sort.d.ts +16 -0
  418. package/dist/user/user.sort.d.ts.map +1 -0
  419. package/dist/user/user.sort.js +25 -0
  420. package/dist/user/user.sort.js.map +1 -0
  421. package/dist/utils/abbreviate-name.d.ts +13 -0
  422. package/dist/utils/abbreviate-name.d.ts.map +1 -0
  423. package/dist/utils/abbreviate-name.js +42 -0
  424. package/dist/utils/abbreviate-name.js.map +1 -0
  425. package/dist/utils/date-range-query.dto.d.ts +11 -0
  426. package/dist/utils/date-range-query.dto.d.ts.map +1 -0
  427. package/dist/utils/date-range-query.dto.js +63 -0
  428. package/dist/utils/date-range-query.dto.js.map +1 -0
  429. package/dist/utils/date-range.d.ts +96 -0
  430. package/dist/utils/date-range.d.ts.map +1 -0
  431. package/dist/utils/date-range.js +172 -0
  432. package/dist/utils/date-range.js.map +1 -0
  433. package/dist/webhook-integration/webhook-utils.d.ts.map +1 -1
  434. package/dist/webhook-integration/webhook-utils.js +24 -0
  435. package/dist/webhook-integration/webhook-utils.js.map +1 -1
  436. package/dist/whatsapp-sender/whatsapp-recipients.d.ts +49 -0
  437. package/dist/whatsapp-sender/whatsapp-recipients.d.ts.map +1 -0
  438. package/dist/whatsapp-sender/whatsapp-recipients.js +66 -0
  439. package/dist/whatsapp-sender/whatsapp-recipients.js.map +1 -0
  440. package/dist/whatsapp-sender/whatsapp-sender.service.d.ts +25 -0
  441. package/dist/whatsapp-sender/whatsapp-sender.service.d.ts.map +1 -1
  442. package/dist/whatsapp-sender/whatsapp-sender.service.js +46 -5
  443. package/dist/whatsapp-sender/whatsapp-sender.service.js.map +1 -1
  444. package/hedhog/data/integration_provider.yaml +9 -0
  445. package/hedhog/data/integration_type.yaml +6 -0
  446. package/hedhog/data/locale.yaml +0 -1
  447. package/hedhog/data/mail.yaml +2 -2
  448. package/hedhog/data/mail_layout.yaml +6 -3
  449. package/hedhog/data/menu.yaml +25 -5
  450. package/hedhog/data/route.yaml +140 -0
  451. package/hedhog/data/setting_group.yaml +137 -3
  452. package/hedhog/frontend/app/access-log/page.tsx.ejs +47 -53
  453. package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +33 -8
  454. package/hedhog/frontend/app/account/components/mcp-tokens.tsx.ejs +54 -13
  455. package/hedhog/frontend/app/account/components/mfa-method-card.tsx.ejs +7 -1
  456. package/hedhog/frontend/app/account/components/recovery-codes-dialog.tsx.ejs +1 -0
  457. package/hedhog/frontend/app/account/components/tokens.tsx.ejs +47 -17
  458. package/hedhog/frontend/app/ai_agent/page.tsx.ejs +46 -5
  459. package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-form.tsx.ejs +101 -30
  460. package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-versions.tsx.ejs +24 -8
  461. package/hedhog/frontend/app/ai_instruction/page.tsx.ejs +80 -15
  462. package/hedhog/frontend/app/configurations/[slug]/components/image-optimization-action.tsx.ejs +166 -0
  463. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.test.tsx.ejs +145 -45
  464. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +99 -219
  465. package/hedhog/frontend/app/configurations/[slug]/page.tsx.ejs +19 -1
  466. package/hedhog/frontend/app/configurations/layout.tsx.ejs +19 -5
  467. package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +2 -8
  468. package/hedhog/frontend/app/dashboard/components/dynamic-widget.tsx.ejs +3 -0
  469. package/hedhog/frontend/app/dashboard/components/widget-wrapper.tsx.ejs +4 -0
  470. package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +43 -44
  471. package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +41 -3
  472. package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +42 -2
  473. package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +41 -3
  474. package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +39 -4
  475. package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +42 -3
  476. package/hedhog/frontend/app/incidents/page.tsx.ejs +60 -31
  477. package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +45 -5
  478. package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +92 -15
  479. package/hedhog/frontend/app/mail/log/page.tsx.ejs +109 -61
  480. package/hedhog/frontend/app/mail/template/page.tsx.ejs +125 -23
  481. package/hedhog/frontend/app/mcp_chat/chat-components.tsx.ejs +13 -15
  482. package/hedhog/frontend/app/mcp_chat/page.tsx.ejs +116 -110
  483. package/hedhog/frontend/app/message-templates/_components/message-template-sheet.tsx.ejs +245 -0
  484. package/hedhog/frontend/app/message-templates/_lib/types.ts.ejs +24 -0
  485. package/hedhog/frontend/app/message-templates/page.tsx.ejs +182 -0
  486. package/hedhog/frontend/app/pages/_components/page-wizard-sheet.tsx.ejs +1 -0
  487. package/hedhog/frontend/app/pages/page.tsx.ejs +94 -9
  488. package/hedhog/frontend/app/roles/menus.tsx.ejs +4 -4
  489. package/hedhog/frontend/app/roles/routes.tsx.ejs +4 -4
  490. package/hedhog/frontend/app/roles/users.tsx.ejs +4 -4
  491. package/hedhog/frontend/app/setting-list/page.tsx.ejs +71 -9
  492. package/hedhog/frontend/app/users/page.tsx.ejs +77 -67
  493. package/hedhog/frontend/messages/en.json +183 -36
  494. package/hedhog/frontend/messages/pt.json +183 -36
  495. package/hedhog/table/cron_lock.yaml +17 -0
  496. package/hedhog/table/file.yaml +13 -1
  497. package/hedhog/table/file_upload_chunk.yaml +23 -0
  498. package/hedhog/table/file_upload_ticket.yaml +67 -0
  499. package/hedhog/table/mcp_tool_call_log.yaml +30 -22
  500. package/hedhog/table/message_template.yaml +33 -0
  501. package/hedhog/table/setting.yaml +8 -0
  502. package/hedhog/table/user_session.yaml +36 -1
  503. package/package.json +7 -5
  504. package/src/ai/ai-agent.sort.ts +21 -0
  505. package/src/ai/ai.service.spec.ts +173 -1
  506. package/src/ai/ai.service.ts +64 -193
  507. package/src/ai-instruction/ai-instruction.service.ts +32 -28
  508. package/src/ai-instruction/ai-instruction.sort.ts +32 -0
  509. package/src/auth/auth.module.ts +8 -1
  510. package/src/auth/auth.service.spec.ts +108 -1
  511. package/src/auth/auth.service.ts +59 -5
  512. package/src/auth/guards/auth.guard.ts +9 -1
  513. package/src/authorize/authorize.controller.ts +126 -11
  514. package/src/authorize/authorize.errors.ts +55 -0
  515. package/src/authorize/authorize.module.ts +6 -1
  516. package/src/authorize/authorize.service.spec.ts +379 -7
  517. package/src/authorize/authorize.service.ts +309 -23
  518. package/src/authorize/dto/authorize-info.dto.ts +21 -3
  519. package/src/authorize/dto/consent.dto.ts +21 -3
  520. package/src/authorize/dto/token.dto.ts +24 -2
  521. package/src/authorize/oauth-exception.filter.ts +58 -0
  522. package/src/authorize/oauth-metadata.service.ts +67 -0
  523. package/src/authorize/well-known.controller.spec.ts +84 -0
  524. package/src/authorize/well-known.controller.ts +96 -0
  525. package/src/captcha/captcha.service.spec.ts +211 -4
  526. package/src/captcha/captcha.service.ts +127 -10
  527. package/src/core.module.ts +6 -0
  528. package/src/dashboard/dashboard-component-role/dashboard-component-role.service.ts +2 -3
  529. package/src/dashboard/dashboard-component-role/dashboard-component-role.sort.ts +18 -0
  530. package/src/dashboard/dashboard-item/dashboard-item.service.ts +2 -3
  531. package/src/dashboard/dashboard-item/dashboard-item.sort.ts +19 -0
  532. package/src/dashboard/dashboard-role/dashboard-role.service.ts +2 -3
  533. package/src/dashboard/dashboard-role/dashboard-role.sort.ts +16 -0
  534. package/src/file/file-upload-ticket.service.spec.ts +422 -0
  535. package/src/file/file-upload-ticket.service.ts +513 -0
  536. package/src/file/file.controller.ts +65 -0
  537. package/src/file/file.module.ts +14 -2
  538. package/src/file/file.service.spec.ts +94 -0
  539. package/src/file/file.service.ts +433 -53
  540. package/src/file/filename-encoding.util.spec.ts +54 -0
  541. package/src/file/filename-encoding.util.ts +67 -0
  542. package/src/file/image-optimization.service.spec.ts +156 -19
  543. package/src/file/image-optimization.service.ts +106 -21
  544. package/src/file/mcp-file-source.schema.ts +83 -0
  545. package/src/file/mcp-file-source.service.spec.ts +473 -0
  546. package/src/file/mcp-file-source.service.ts +445 -0
  547. package/src/file/mimetype-sniff.util.spec.ts +126 -0
  548. package/src/file/mimetype-sniff.util.ts +265 -0
  549. package/src/file/remote-fetch.util.spec.ts +117 -0
  550. package/src/file/remote-fetch.util.ts +330 -0
  551. package/src/impersonation/dto/start-impersonation.dto.ts +5 -2
  552. package/src/impersonation/impersonation.service.spec.ts +24 -1
  553. package/src/impersonation/impersonation.service.ts +14 -4
  554. package/src/incident/incident.controller.ts +9 -1
  555. package/src/incident/incident.service.ts +7 -0
  556. package/src/index.ts +17 -0
  557. package/src/integration-profile/integration-profile.secrets.ts +7 -0
  558. package/src/integration-profile/integration-profile.service.ts +7 -64
  559. package/src/integration-profile/integration-profile.utils.spec.ts +9 -0
  560. package/src/integration-profile/integration-profile.utils.ts +5 -5
  561. package/src/integration-profile/testers/index.ts +2 -0
  562. package/src/integration-profile/testers/remaining.testers.spec.ts +79 -0
  563. package/src/integration-profile/testers/shipping.testers.ts +94 -0
  564. package/src/mail/dto/send.dto.ts +6 -1
  565. package/src/mail/mail-template.spec.ts +58 -1
  566. package/src/mail/mail.service.spec.ts +112 -0
  567. package/src/mail/mail.service.ts +42 -4
  568. package/src/mail-sent/mail-sent.service.ts +3 -0
  569. package/src/mail-sent/mail-sent.sort.ts +19 -0
  570. package/src/mcp/filters/mcp-challenge.filter.spec.ts +76 -0
  571. package/src/mcp/filters/mcp-challenge.filter.ts +70 -0
  572. package/src/mcp/guards/mcp-auth.guard.spec.ts +51 -2
  573. package/src/mcp/guards/mcp-auth.guard.ts +24 -1
  574. package/src/mcp/guards/mcp-permission.guard.spec.ts +109 -15
  575. package/src/mcp/guards/mcp-permission.guard.ts +76 -51
  576. package/src/mcp/mcp-audit.service.spec.ts +107 -0
  577. package/src/mcp/mcp-audit.service.ts +37 -1
  578. package/src/mcp/mcp-permission.service.spec.ts +197 -0
  579. package/src/mcp/mcp-permission.service.ts +184 -0
  580. package/src/mcp/mcp-server.service.spec.ts +84 -2
  581. package/src/mcp/mcp-server.service.ts +111 -87
  582. package/src/mcp/mcp.controller.ts +8 -1
  583. package/src/mcp/mcp.module.ts +5 -1
  584. package/src/mcp/tools/core-ai.mcp-tools.ts +1 -1
  585. package/src/mcp/tools/core-dashboard-items.mcp-tools.ts +1 -1
  586. package/src/mcp/tools/core-dashboard.mcp-tools.ts +1 -1
  587. package/src/mcp/tools/core-event-webhooks.mcp-tools.ts +1 -2
  588. package/src/mcp/tools/core-files.mcp-tools.ts +202 -22
  589. package/src/mcp/tools/core-integration-events.mcp-tools.ts +1 -1
  590. package/src/mcp/tools/core-mail-sent.mcp-tools.ts +1 -1
  591. package/src/mcp/tools/core-mail.mcp-tools.ts +1 -1
  592. package/src/mcp/tools/core-menus.mcp-tools.ts +1 -2
  593. package/src/mcp/tools/core-notifications.mcp-tools.ts +0 -1
  594. package/src/mcp/tools/core-roles.mcp-tools.ts +1 -5
  595. package/src/mcp/tools/core-routes.mcp-tools.ts +1 -3
  596. package/src/mcp/tools/core-screens.mcp-tools.ts +1 -3
  597. package/src/mcp/tools/core-sessions.mcp-tools.ts +0 -2
  598. package/src/mcp/tools/core-settings.mcp-tools.ts +1 -2
  599. package/src/mcp/tools/core-users.mcp-tools.ts +1 -1
  600. package/src/mcp/tools/core-webhooks.mcp-tools.ts +1 -2
  601. package/src/mcp/types/mcp-context.type.ts +15 -6
  602. package/src/mcp-chat/mcp-chat.service.ts +8 -0
  603. package/src/meeting/meeting-provider.registry.ts +121 -0
  604. package/src/meeting/meeting.constants.ts +74 -0
  605. package/src/menu/menu.service.spec.ts +89 -0
  606. package/src/menu/menu.service.ts +29 -11
  607. package/src/message-template/dto/update-message-template.dto.ts +22 -0
  608. package/src/message-template/message-template-admin.service.spec.ts +163 -0
  609. package/src/message-template/message-template-admin.service.ts +239 -0
  610. package/src/message-template/message-template.controller.ts +52 -0
  611. package/src/message-template/message-template.module.ts +21 -0
  612. package/src/message-template/message-template.registry.spec.ts +62 -0
  613. package/src/message-template/message-template.registry.ts +125 -0
  614. package/src/message-template/message-template.service.spec.ts +149 -0
  615. package/src/message-template/message-template.service.ts +149 -0
  616. package/src/notification/notification.constants.ts +10 -0
  617. package/src/notification/notification.module.ts +3 -0
  618. package/src/notification/notification.service.spec.ts +75 -1
  619. package/src/notification/notification.service.ts +39 -1
  620. package/src/oauth/oauth.controller.ts +0 -1
  621. package/src/oauth/oauth.errors.ts +7 -0
  622. package/src/oauth/oauth.module.ts +6 -1
  623. package/src/oauth/oauth.service.spec.ts +58 -7
  624. package/src/oauth/oauth.service.ts +71 -32
  625. package/src/oauth/providers/abstract.provider.spec.ts +192 -0
  626. package/src/oauth/providers/abstract.provider.ts +46 -14
  627. package/src/oauth/providers/github.provider.ts +57 -34
  628. package/src/oauth/providers/microsoft-entra-id.provider.ts +8 -1
  629. package/src/observability/monitored-cron.spec.ts +164 -31
  630. package/src/observability/monitored-cron.ts +154 -62
  631. package/src/profile/dto/change-password.dto.ts +25 -7
  632. package/src/profile/profile.service.spec.ts +97 -0
  633. package/src/profile/profile.service.ts +9 -3
  634. package/src/realtime/realtime-tick.coalescer.spec.ts +92 -0
  635. package/src/realtime/realtime-tick.coalescer.ts +60 -0
  636. package/src/realtime/realtime.service.spec.ts +33 -0
  637. package/src/realtime/realtime.service.ts +28 -2
  638. package/src/redis/redis-kv.service.ts +261 -0
  639. package/src/redis/redis-master.spec.ts +111 -0
  640. package/src/redis/redis-master.ts +86 -0
  641. package/src/redis/redis-pubsub.ts +27 -3
  642. package/src/redis/redis.module.ts +17 -0
  643. package/src/session/session.service.spec.ts +121 -42
  644. package/src/session/session.service.ts +279 -82
  645. package/src/setting/setting.service.ts +44 -3
  646. package/src/task/task.service.spec.ts +41 -0
  647. package/src/task/task.service.ts +53 -13
  648. package/src/token/token.service.spec.ts +90 -0
  649. package/src/token/token.service.ts +48 -2
  650. package/src/user/user-credential.util.ts +14 -5
  651. package/src/user/user.service.ts +2 -12
  652. package/src/user/user.sort.ts +25 -0
  653. package/src/utils/abbreviate-name.spec.ts +50 -0
  654. package/src/utils/abbreviate-name.ts +44 -0
  655. package/src/utils/date-range-query.dto.ts +56 -0
  656. package/src/utils/date-range.spec.ts +175 -0
  657. package/src/utils/date-range.ts +246 -0
  658. package/src/webhook-integration/webhook-utils.spec.ts +67 -0
  659. package/src/webhook-integration/webhook-utils.ts +26 -0
  660. package/src/whatsapp-sender/whatsapp-recipients.spec.ts +89 -0
  661. package/src/whatsapp-sender/whatsapp-recipients.ts +83 -0
  662. package/src/whatsapp-sender/whatsapp-sender.service.spec.ts +112 -0
  663. package/src/whatsapp-sender/whatsapp-sender.service.ts +58 -5
  664. package/hedhog/query/device_pairing_request.sql +0 -46
  665. package/hedhog/query/impersonation_grant.sql +0 -71
  666. package/hedhog/query/oauth_authorize_pkce.sql +0 -58
  667. package/hedhog/query/oauth_mobile_state_token.sql +0 -30
  668. package/hedhog/query/oauth_web_ref_token.sql +0 -25
@@ -1,7 +1,8 @@
1
- import { describe, it, expect, jest, beforeEach } from '@jest/globals';
1
+ import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals';
2
2
 
3
3
  const withMonitor = jest.fn((_slug: string, callback: () => any) => callback());
4
4
  const startSpan = jest.fn((_opts: any, callback: () => any) => callback());
5
+ const startNewTrace = jest.fn((callback: () => any) => callback());
5
6
  const captureException = jest.fn();
6
7
  const withScope = jest.fn((cb: (scope: any) => void) => {
7
8
  const scope = { setTag: jest.fn() };
@@ -9,41 +10,83 @@ const withScope = jest.fn((cb: (scope: any) => void) => {
9
10
  return scope;
10
11
  });
11
12
 
12
- jest.mock('@sentry/nestjs', () => ({ withMonitor, startSpan, captureException, withScope }));
13
+ jest.mock('@sentry/nestjs', () => ({
14
+ withMonitor,
15
+ startSpan,
16
+ startNewTrace,
17
+ captureException,
18
+ withScope,
19
+ }));
13
20
 
14
21
  // eslint-disable-next-line @typescript-eslint/no-var-requires
15
22
  const { runMonitoredCron } = require('./monitored-cron');
16
23
 
17
- function fakePrisma(locked: boolean, overrides: { transactionThrows?: boolean } = {}) {
18
- const queryRaw = jest.fn().mockResolvedValue([{ locked }]);
19
- const transaction = jest.fn(async (fn: any) => {
20
- if (overrides.transactionThrows) throw new Error('transaction aborted');
21
- return fn({ $queryRaw: queryRaw });
24
+ type FakeOptions = {
25
+ /** `false` simula outro pod segurando a lease (nenhuma linha devolvida). */
26
+ won?: boolean;
27
+ claimThrows?: boolean;
28
+ releaseThrows?: boolean;
29
+ };
30
+
31
+ /**
32
+ * O Prisma chama a tagged template como `$queryRaw(strings, ...values)`, então os
33
+ * parâmetros do SQL chegam aqui na ordem em que aparecem na query: o claim manda
34
+ * [slug, holder, leaseSeconds] e o release manda [slug, holder].
35
+ */
36
+ function fakePrisma(options: FakeOptions = {}) {
37
+ const claims: any[][] = [];
38
+ const releases: any[][] = [];
39
+
40
+ const $queryRaw = jest.fn(async (_strings: any, ...values: any[]) => {
41
+ claims.push(values);
42
+ if (options.claimThrows) throw new Error('Unable to start a transaction in the given time.');
43
+ return options.won === false ? [] : [{ won: 1 }];
22
44
  });
23
- return { $transaction: transaction, $queryRaw: queryRaw };
45
+
46
+ const $executeRaw = jest.fn(async (_strings: any, ...values: any[]) => {
47
+ releases.push(values);
48
+ if (options.releaseThrows) throw new Error('release falhou');
49
+ return 1;
50
+ });
51
+
52
+ return { $queryRaw, $executeRaw, claims, releases };
24
53
  }
25
54
 
55
+ const BASE = { slug: 'meu-cron', schedule: '* * * * *' };
56
+
26
57
  describe('runMonitoredCron', () => {
58
+ const originalSlugs = process.env.SENTRY_CRON_MONITOR_SLUGS;
59
+
27
60
  beforeEach(() => {
28
61
  jest.clearAllMocks();
62
+ // O default de produção libera check-in só para um slug; os casos abaixo
63
+ // descrevem o comportamento com o monitor ligado, então valem para todos.
64
+ process.env.SENTRY_CRON_MONITOR_SLUGS = '*';
65
+ });
66
+
67
+ afterEach(() => {
68
+ if (originalSlugs === undefined) delete process.env.SENTRY_CRON_MONITOR_SLUGS;
69
+ else process.env.SENTRY_CRON_MONITOR_SLUGS = originalSlugs;
29
70
  });
30
71
 
31
72
  it('retorna undefined sem executar work() quando outro pod já ganhou o tick', async () => {
32
- const prisma = fakePrisma(false);
73
+ const prisma = fakePrisma({ won: false });
33
74
  const work = jest.fn();
34
75
 
35
- const result = await runMonitoredCron(prisma, { slug: 'meu-cron', schedule: '* * * * *' }, work);
76
+ const result = await runMonitoredCron(prisma, BASE, work);
36
77
 
37
78
  expect(result).toBeUndefined();
38
79
  expect(work).not.toHaveBeenCalled();
39
80
  expect(withMonitor).not.toHaveBeenCalled();
81
+ // Perder o tick não pode mexer na lease de quem venceu.
82
+ expect(prisma.$executeRaw).not.toHaveBeenCalled();
40
83
  });
41
84
 
42
85
  it('executa work() e devolve o resultado quando ganha o tick', async () => {
43
- const prisma = fakePrisma(true);
86
+ const prisma = fakePrisma();
44
87
  const work = jest.fn().mockResolvedValue({ ok: true });
45
88
 
46
- const result = await runMonitoredCron(prisma, { slug: 'meu-cron', schedule: '* * * * *' }, work);
89
+ const result = await runMonitoredCron(prisma, BASE, work);
47
90
 
48
91
  expect(work).toHaveBeenCalledTimes(1);
49
92
  expect(withMonitor).toHaveBeenCalledWith(
@@ -54,44 +97,61 @@ describe('runMonitoredCron', () => {
54
97
  expect(result).toEqual({ ok: true });
55
98
  });
56
99
 
57
- it('NUNCA relança quando work() falha — captura no Sentry e retorna undefined', async () => {
58
- const prisma = fakePrisma(true);
100
+ it('libera a lease com o MESMO holder que a tomou', async () => {
101
+ const prisma = fakePrisma();
102
+
103
+ await runMonitoredCron(prisma, BASE, jest.fn().mockResolvedValue(undefined));
104
+
105
+ const [claimSlug, claimHolder] = prisma.claims[0];
106
+ const [releaseSlug, releaseHolder] = prisma.releases[0];
107
+
108
+ expect(claimSlug).toBe('meu-cron');
109
+ expect(releaseSlug).toBe('meu-cron');
110
+ expect(releaseHolder).toBe(claimHolder);
111
+ });
112
+
113
+ it('usa um holder diferente a cada execução, para um release atrasado não apagar a lease do tick seguinte', async () => {
114
+ const prisma = fakePrisma();
115
+ const work = jest.fn().mockResolvedValue(undefined);
116
+
117
+ await runMonitoredCron(prisma, BASE, work);
118
+ await runMonitoredCron(prisma, BASE, work);
119
+
120
+ expect(prisma.claims[0][1]).not.toBe(prisma.claims[1][1]);
121
+ });
122
+
123
+ it('NUNCA relança quando work() falha — captura no Sentry, libera a lease e retorna undefined', async () => {
124
+ const prisma = fakePrisma();
59
125
  const boom = new Error('cron falhou');
60
126
  const work = jest.fn().mockRejectedValue(boom);
61
127
 
62
- await expect(
63
- runMonitoredCron(prisma, { slug: 'meu-cron', schedule: '* * * * *' }, work),
64
- ).resolves.toBeUndefined();
128
+ await expect(runMonitoredCron(prisma, BASE, work)).resolves.toBeUndefined();
65
129
 
66
130
  expect(captureException).toHaveBeenCalledWith(
67
131
  boom,
68
132
  expect.objectContaining({ mechanism: { handled: true, type: 'hedhog.cron' } }),
69
133
  );
134
+ // A lease não pode ficar presa até vencer só porque o trabalho deu erro.
135
+ expect(prisma.$executeRaw).toHaveBeenCalledTimes(1);
70
136
  });
71
137
 
72
- it('dimensiona o timeout/maxWait da transação a partir de maxRuntimeMinutes', async () => {
73
- const prisma = fakePrisma(true);
74
- const work = jest.fn().mockResolvedValue(undefined);
138
+ it('dimensiona a validade da lease a partir de maxRuntimeMinutes', async () => {
139
+ const prisma = fakePrisma();
75
140
 
76
141
  await runMonitoredCron(
77
142
  prisma,
78
- { slug: 'meu-cron', schedule: '* * * * *', maxRuntimeMinutes: 2 },
79
- work,
143
+ { ...BASE, maxRuntimeMinutes: 2 },
144
+ jest.fn().mockResolvedValue(undefined),
80
145
  );
81
146
 
82
- expect(prisma.$transaction).toHaveBeenCalledWith(
83
- expect.any(Function),
84
- { timeout: 2 * 60_000 + 30_000, maxWait: 20_000 },
85
- );
147
+ expect(prisma.claims[0][2]).toBe(2 * 60 + 30);
86
148
  });
87
149
 
88
- it('NUNCA relança quando a transação falha (ex.: pool esgotado) — captura no Sentry e retorna undefined', async () => {
89
- const prisma = fakePrisma(true, { transactionThrows: true });
150
+ it('NUNCA relança quando a eleição falha (ex.: banco indisponível) — captura no Sentry', async () => {
151
+ const prisma = fakePrisma({ claimThrows: true });
90
152
  const work = jest.fn();
91
153
 
92
- await expect(
93
- runMonitoredCron(prisma, { slug: 'meu-cron', schedule: '* * * * *' }, work),
94
- ).resolves.toBeUndefined();
154
+ await expect(runMonitoredCron(prisma, BASE, work)).resolves.toBeUndefined();
95
155
 
96
156
  expect(work).not.toHaveBeenCalled();
97
157
  expect(captureException).toHaveBeenCalledWith(
@@ -99,4 +159,77 @@ describe('runMonitoredCron', () => {
99
159
  expect.objectContaining({ mechanism: { handled: true, type: 'hedhog.cron' } }),
100
160
  );
101
161
  });
162
+
163
+ it('falha ao liberar a lease não derruba o cron nem mascara o resultado de work()', async () => {
164
+ const prisma = fakePrisma({ releaseThrows: true });
165
+ const work = jest.fn().mockResolvedValue({ ok: true });
166
+
167
+ await expect(runMonitoredCron(prisma, BASE, work)).resolves.toEqual({ ok: true });
168
+
169
+ expect(captureException).not.toHaveBeenCalled();
170
+ });
171
+
172
+ /**
173
+ * O plano do Sentry cobre UM monitor de cron e o projeto tem cinco. Check-in de
174
+ * slug sem cota é descartado na entrada, então o que se protege aqui é que a
175
+ * ausência dele não leve junto o trabalho, o span nem o relato de erro.
176
+ */
177
+ describe('allowlist de check-in (SENTRY_CRON_MONITOR_SLUGS)', () => {
178
+ it('emite check-in para slug listado', async () => {
179
+ process.env.SENTRY_CRON_MONITOR_SLUGS = 'outro-cron,meu-cron';
180
+ const prisma = fakePrisma();
181
+
182
+ await runMonitoredCron(prisma, BASE, jest.fn().mockResolvedValue({ ok: true }));
183
+
184
+ expect(withMonitor).toHaveBeenCalledTimes(1);
185
+ expect(startNewTrace).not.toHaveBeenCalled();
186
+ });
187
+
188
+ it('slug fora da lista roda work() e o span, sem check-in', async () => {
189
+ process.env.SENTRY_CRON_MONITOR_SLUGS = 'outro-cron';
190
+ const prisma = fakePrisma();
191
+ const work = jest.fn().mockResolvedValue({ ok: true });
192
+
193
+ const result = await runMonitoredCron(prisma, BASE, work);
194
+
195
+ expect(result).toEqual({ ok: true });
196
+ expect(work).toHaveBeenCalledTimes(1);
197
+ expect(withMonitor).not.toHaveBeenCalled();
198
+ // O trace continua isolado por execução, papel que o `isolateTrace` do
199
+ // withMonitor cumpre no outro ramo.
200
+ expect(startNewTrace).toHaveBeenCalledTimes(1);
201
+ expect(startSpan).toHaveBeenCalledWith(
202
+ expect.objectContaining({ name: 'meu-cron', op: 'cron.hedhog' }),
203
+ expect.any(Function),
204
+ );
205
+ });
206
+
207
+ it('slug fora da lista continua liberando a lease e reportando erro', async () => {
208
+ process.env.SENTRY_CRON_MONITOR_SLUGS = 'outro-cron';
209
+ const prisma = fakePrisma();
210
+ const boom = new Error('cron falhou');
211
+
212
+ await expect(
213
+ runMonitoredCron(prisma, BASE, jest.fn().mockRejectedValue(boom)),
214
+ ).resolves.toBeUndefined();
215
+
216
+ expect(captureException).toHaveBeenCalledWith(
217
+ boom,
218
+ expect.objectContaining({ mechanism: { handled: true, type: 'hedhog.cron' } }),
219
+ );
220
+ expect(prisma.$executeRaw).toHaveBeenCalledTimes(1);
221
+ });
222
+
223
+ it('sem a env, o default deixa passar só o sac-mailbox-poller', async () => {
224
+ delete process.env.SENTRY_CRON_MONITOR_SLUGS;
225
+ const work = jest.fn().mockResolvedValue(undefined);
226
+
227
+ await runMonitoredCron(fakePrisma(), { ...BASE, slug: 'sac-mailbox-poller' }, work);
228
+ expect(withMonitor).toHaveBeenCalledTimes(1);
229
+
230
+ await runMonitoredCron(fakePrisma(), BASE, work);
231
+ expect(withMonitor).toHaveBeenCalledTimes(1);
232
+ expect(work).toHaveBeenCalledTimes(2);
233
+ });
234
+ });
102
235
  });
@@ -1,4 +1,5 @@
1
1
  import * as Sentry from '@sentry/nestjs';
2
+ import { randomUUID } from 'node:crypto';
2
3
 
3
4
  export type MonitoredCronOptions = {
4
5
  /** Slug do monitor no Sentry (Crons). */
@@ -9,34 +10,103 @@ export type MonitoredCronOptions = {
9
10
  checkinMarginMinutes?: number;
10
11
  };
11
12
 
12
- /** Fallback só para dimensionar o timeout da transação (ver abaixo) quando um
13
+ /** Fallback só para dimensionar a validade da lease (ver abaixo) quando um
13
14
  * chamador futuro omitir `maxRuntimeMinutes` — os 5 crons atuais sempre informam. */
14
15
  const DEFAULT_MAX_RUNTIME_MINUTES = 5;
15
16
 
16
- /** Folga sobre `maxRuntimeMinutes` para o `timeout` da transação: cobre a
17
- * diferença entre o que o Sentry tolera (`maxRuntime`/`checkinMargin`, que só
18
- * marcam o check-in como perdido) e o que o Prisma de fato aborta. */
19
- const TRANSACTION_TIMEOUT_SLACK_MS = 30_000;
17
+ /** Folga sobre `maxRuntimeMinutes` na validade da lease: cobre a diferença entre o
18
+ * que o Sentry tolera (`maxRuntime`/`checkinMargin`, que só marcam o check-in como
19
+ * perdido) e o instante a partir do qual outro pod pode assumir o tick. */
20
+ const LEASE_SLACK_SECONDS = 30;
20
21
 
21
- /** Tempo esperando conexão do pool para abrir a transação — alinhado ao
22
- * `pool_timeout=20s` que packages/api-prisma/src/datasource-url.ts já aplica
23
- * quando PRISMA_CONNECTION_LIMIT está setado; o default do Prisma (2s) seria
24
- * mais apertado que esse teto que o projeto já escolheu deliberadamente. */
25
- const TRANSACTION_MAX_WAIT_MS = 20_000;
22
+ /**
23
+ * Slugs cujo check-in de Cron Monitoring é de fato emitido, em CSV. `*` libera
24
+ * todos.
25
+ *
26
+ * O plano do Sentry inclui UM monitor de cron e este projeto tem cinco: os quatro
27
+ * que sobram tinham o check-in descartado na entrada por cota ("Usage Exceeded"),
28
+ * e monitor que não recebe check-in não alarma — só marca "missed" para sempre no
29
+ * painel, que é pior do que não existir.
30
+ *
31
+ * A vaga fica com o cron de maior cadência (1 min), porque é o que detecta mais
32
+ * rápido a falha que SÓ o Cron Monitoring pega: o agendador parado em todos os
33
+ * pods. Falha com exceção dos outros quatro continua chegando pela captura no
34
+ * `catch` abaixo, com a tag `cron`.
35
+ */
36
+ const DEFAULT_CRON_MONITOR_SLUGS = 'sac-mailbox-poller';
37
+
38
+ /** Cache pelo valor bruto: a env não muda em runtime, mas o teste troca. */
39
+ let parsedSlugsRaw: string | undefined;
40
+ let parsedSlugs: Set<string> = new Set();
41
+
42
+ function emitsCheckIn(slug: string): boolean {
43
+ const raw = process.env.SENTRY_CRON_MONITOR_SLUGS ?? DEFAULT_CRON_MONITOR_SLUGS;
44
+
45
+ if (raw !== parsedSlugsRaw) {
46
+ parsedSlugsRaw = raw;
47
+ parsedSlugs = new Set(
48
+ raw
49
+ .split(',')
50
+ .map((entry) => entry.trim())
51
+ .filter(Boolean),
52
+ );
53
+ }
54
+
55
+ return parsedSlugs.has('*') || parsedSlugs.has(slug);
56
+ }
57
+
58
+ /**
59
+ * Identidade DESTA execução, não do pod: dois ticks seguidos no mesmo processo
60
+ * precisam de holders diferentes, senão um release atrasado do tick N apagaria a
61
+ * lease que o tick N+1 acabou de tomar.
62
+ */
63
+ function newHolder(): string {
64
+ return `${process.env.HOSTNAME ?? 'local'}:${randomUUID()}`;
65
+ }
66
+
67
+ /**
68
+ * Tenta ganhar o tick com uma ÚNICA statement: quem consegue gravar
69
+ * `locked_until` no futuro venceu, os demais não recebem linha nenhuma de volta.
70
+ * Concorrentes serializam no índice único de `slug` — o perdedor reavalia o
71
+ * `WHERE` já com a linha do vencedor, então nunca há dois vencedores.
72
+ *
73
+ * O relógio de referência é sempre o `now()` do Postgres, dos dois lados: nenhum
74
+ * instante é calculado em JS, então divergência de relógio entre pods não conta.
75
+ */
76
+ async function claimCronTick(
77
+ prisma: any,
78
+ slug: string,
79
+ holder: string,
80
+ leaseSeconds: number,
81
+ ): Promise<boolean> {
82
+ const rows: unknown[] = await prisma.$queryRaw`
83
+ INSERT INTO "cron_lock" ("slug", "holder", "locked_until")
84
+ VALUES (${slug}, ${holder}, now() + make_interval(secs => ${leaseSeconds}::double precision))
85
+ ON CONFLICT ("slug") DO UPDATE
86
+ SET "holder" = EXCLUDED."holder",
87
+ "locked_until" = EXCLUDED."locked_until"
88
+ WHERE "cron_lock"."locked_until" <= now()
89
+ RETURNING 1 AS won
90
+ `;
91
+
92
+ return rows.length > 0;
93
+ }
26
94
 
27
95
  /**
28
- * Tenta ganhar o tick. Roda DENTRO da transação de `runMonitoredCron` —
29
- * reaproveita o MESMO padrão de `pg_try_advisory_xact_lock` já usado em
30
- * cms-scheduled-publisher.service.ts e class-session-reminder.service.ts, mas
31
- * aqui generalizado: a variante `xact` só é liberada no commit/rollback DESSA
32
- * transação, então segurá-la aberta pelo tempo de `work()` é o que de fato
33
- * garante a exclusão mútua entre pods.
96
+ * Devolve o tick antes do fim da lease. Sem isso o `sac-mailbox-poller` (que roda
97
+ * a cada minuto com lease de 2min30) só voltaria a rodar de 3 em 3 minutos.
98
+ *
99
+ * O `holder` no WHERE é o que impede liberar a lease de outra execução: se esta
100
+ * aqui estourou o prazo e outro pod já assumiu, o UPDATE não casa e não faz nada.
34
101
  */
35
- async function claimCronTick(tx: any, slug: string): Promise<boolean> {
36
- const rows: { locked: boolean }[] = await tx.$queryRaw`
37
- SELECT pg_try_advisory_xact_lock(hashtext(${slug})) AS locked
102
+ async function releaseCronTick(prisma: any, slug: string, holder: string): Promise<void> {
103
+ await prisma.$executeRaw`
104
+ UPDATE "cron_lock"
105
+ SET "locked_until" = now()
106
+ WHERE "slug" = ${slug}
107
+ AND "holder" = ${holder}
108
+ AND "locked_until" > now()
38
109
  `;
39
- return rows[0]?.locked === true;
40
110
  }
41
111
 
42
112
  /**
@@ -46,61 +116,83 @@ async function claimCronTick(tx: any, slug: string): Promise<boolean> {
46
116
  * simultaneamente, o Sentry recebe 2×N check-ins por execução e estoura o
47
117
  * rate limit de 6/min por monitor (ver docs/sentry.md).
48
118
  *
49
- * A eleição e `work()` rodam na MESMA transação interativa — é o que faz o
50
- * `pg_try_advisory_xact_lock` valer como exclusão mútua de verdade. Uma
51
- * versão anterior comitava a eleição sozinha (fora de transação): o Postgres
52
- * liberava o lock no instante em que a query retornava, antes do trabalho
53
- * real começar, e pods suficientemente próximos no tempo (o que é comum,
54
- * já que o @Cron dispara no mesmo segundo em todos) "ganhavam" o mesmo tick
55
- * e mandavam check-ins duplicados — foi o que gerou o incidente do
56
- * sac-sla-sweep. `work()` deliberadamente NÃO recebe `tx` (continua fechando
57
- * sobre `this.prisma`/outros serviços do chamador): a conexão que segura o
58
- * lock fica ociosa (idle in transaction) durante o trabalho real, em vez de
59
- * segurar locks de linha por uma chamada HTTP potencialmente longa (ex.:
60
- * renovação de assinatura do Graph, até 10 min).
119
+ * A exclusão mútua é uma LEASE na tabela `cron_lock`, não um lock de banco.
120
+ * Já passamos por duas versões piores:
121
+ *
122
+ * 1. `pg_try_advisory_xact_lock` numa transação que comitava logo em seguida: o
123
+ * Postgres liberava o lock no instante em que a query retornava, antes do
124
+ * trabalho real começar, e pods próximos no tempo (o que é comum, já que o
125
+ * @Cron dispara no mesmo segundo em todos) "ganhavam" o mesmo tick — foi o
126
+ * que gerou o incidente do sac-sla-sweep.
127
+ * 2. O mesmo lock, mas com a transação segurada durante `work()` inteiro. A
128
+ * exclusão passou a valer, ao custo de manter uma conexão do pool em
129
+ * `idle in transaction` por até 10 minutos. Com PRISMA_CONNECTION_LIMIT=5 por
130
+ * pod e cinco crons disputando (um deles de minuto em minuto), o pool
131
+ * esgotava e o próprio cron morria em "Unable to start a transaction in the
132
+ * given time" — o mecanismo de observabilidade consumindo o pool de que ele
133
+ * precisava (Sentry API-1H).
134
+ *
135
+ * A lease resolve as duas: a eleição é uma statement só (conexão devolvida na
136
+ * hora, `work()` roda sem segurar nada) e a validade gravada em `locked_until`
137
+ * é o que mantém os outros pods fora até o dono terminar ou o prazo vencer.
138
+ *
139
+ * Trade-off assumido: se `work()` passar de `maxRuntimeMinutes` + 30s, a lease
140
+ * vence e outro pod pode iniciar uma execução concorrente. A versão anterior
141
+ * abortava a transação no timeout — nenhum dos dois é bom, mas este não leva o
142
+ * pool junto.
61
143
  *
62
144
  * Quando outro pod já ganhou o tick, resolve para `undefined` sem chamar
63
145
  * `work` — não é uma falha, é a mesma semântica de "outro worker já pegou o
64
146
  * job" que a fila customizada já usa.
147
+ *
148
+ * O check-in só é emitido para os slugs de `SENTRY_CRON_MONITOR_SLUGS` (ver
149
+ * `DEFAULT_CRON_MONITOR_SLUGS`): a cota do plano não cobre os cinco monitores, e
150
+ * check-in sem cota é descartado na entrada. Tudo o mais — lease, eleição, span,
151
+ * captura de exceção — é igual para todos os slugs.
65
152
  */
66
153
  export async function runMonitoredCron<T>(
67
154
  prisma: any,
68
155
  options: MonitoredCronOptions,
69
156
  work: () => Promise<T>,
70
157
  ): Promise<T | undefined> {
71
- const timeoutMs =
72
- (options.maxRuntimeMinutes ?? DEFAULT_MAX_RUNTIME_MINUTES) * 60_000 +
73
- TRANSACTION_TIMEOUT_SLACK_MS;
158
+ const leaseSeconds =
159
+ (options.maxRuntimeMinutes ?? DEFAULT_MAX_RUNTIME_MINUTES) * 60 + LEASE_SLACK_SECONDS;
160
+ const holder = newHolder();
74
161
 
75
162
  try {
76
- return await prisma.$transaction(
77
- async (tx: any) => {
78
- if (!(await claimCronTick(tx, options.slug))) return undefined;
79
-
80
- return Sentry.withMonitor(
81
- options.slug,
82
- () => Sentry.startSpan({ name: options.slug, op: 'cron.hedhog' }, work),
83
- {
84
- schedule: { type: 'crontab', value: options.schedule },
85
- timezone: 'America/Sao_Paulo',
86
- ...(options.maxRuntimeMinutes ? { maxRuntime: options.maxRuntimeMinutes } : {}),
87
- ...(options.checkinMarginMinutes ? { checkinMargin: options.checkinMarginMinutes } : {}),
88
- // Distingue cada execução do cron como um trace próprio, em vez de
89
- // herdar/continuar um trace pendurado do ciclo de poll anterior.
90
- isolateTrace: true,
91
- },
92
- );
93
- },
94
- { timeout: timeoutMs, maxWait: TRANSACTION_MAX_WAIT_MS },
95
- );
163
+ if (!(await claimCronTick(prisma, options.slug, holder, leaseSeconds))) return undefined;
164
+
165
+ try {
166
+ const runTraced = () => Sentry.startSpan({ name: options.slug, op: 'cron.hedhog' }, work);
167
+
168
+ // Sem cota de monitor, o check-in seria descartado na entrada — o trabalho,
169
+ // o span e o relato de erro continuam idênticos. `startNewTrace` faz aqui o
170
+ // que o `isolateTrace` do withMonitor faz no outro ramo.
171
+ if (!emitsCheckIn(options.slug)) {
172
+ return await Sentry.startNewTrace(runTraced);
173
+ }
174
+
175
+ return await Sentry.withMonitor(options.slug, runTraced, {
176
+ schedule: { type: 'crontab', value: options.schedule },
177
+ timezone: 'America/Sao_Paulo',
178
+ ...(options.maxRuntimeMinutes ? { maxRuntime: options.maxRuntimeMinutes } : {}),
179
+ ...(options.checkinMarginMinutes ? { checkinMargin: options.checkinMarginMinutes } : {}),
180
+ // Distingue cada execução do cron como um trace próprio, em vez de
181
+ // herdar/continuar um trace pendurado do ciclo de poll anterior.
182
+ isolateTrace: true,
183
+ });
184
+ } finally {
185
+ // Best-effort: uma falha ao devolver a lease não pode virar o erro do cron
186
+ // (o trabalho já foi feito) e nem mascarar o erro real de `work()`. No pior
187
+ // caso a lease expira sozinha em `leaseSeconds`.
188
+ await releaseCronTick(prisma, options.slug, holder).catch(() => undefined);
189
+ }
96
190
  } catch (error) {
97
- // withMonitor já registrou o check-in como 'error' antes de relançar (e um
98
- // erro de work() também derruba a transação por rollback — inofensivo,
99
- // já que nada é escrito via `tx`, só o lock é liberado). O @Cron do
100
- // chamador NUNCA deve rejeitar a partir daqui: a lib `cron` por baixo do
101
- // @nestjs/schedule não garante tratamento de uma rejeição vinda do
102
- // onTick, e o onUnhandledRejectionIntegration({mode:'strict'}) do
103
- // instrument.ts derrubaria o processo por um erro que já está capturado.
191
+ // withMonitor já registrou o check-in como 'error' antes de relançar. O @Cron
192
+ // do chamador NUNCA deve rejeitar a partir daqui: a lib `cron` por baixo do
193
+ // @nestjs/schedule não garante tratamento de uma rejeição vinda do onTick, e
194
+ // o onUnhandledRejectionIntegration({mode:'strict'}) do instrument.ts
195
+ // derrubaria o processo por um erro que já está capturado.
104
196
  Sentry.withScope((scope) => {
105
197
  scope.setTag('cron', options.slug);
106
198
  Sentry.captureException(error, { mechanism: { handled: true, type: 'hedhog.cron' } });
@@ -1,16 +1,34 @@
1
1
  import { getLocaleText } from '@hed-hog/api-locale';
2
- import { IsString, MinLength } from 'class-validator';
2
+ import { IsString } from 'class-validator';
3
+ import { getLocaleFromContext } from '../../utils/locale-context';
3
4
  import { IsStrongPasswordWithSettings } from '../../validators/is-strong-password-with-settings.validator';
4
5
 
5
6
  export class ChangePasswordDto {
6
- @IsStrongPasswordWithSettings({
7
- message: (args) => getLocaleText('profile.changePassword.currentPasswordIsString', args.value)
7
+ // Só precisa ser texto — é comparada ao hash já salvo, não precisa atender à
8
+ // política de senha ATUAL. `@IsStrongPasswordWithSettings` estava aqui por engano:
9
+ // qualquer conta com senha anterior a um endurecimento de política (mais símbolos
10
+ // exigidos, por exemplo) ficaria travada pra sempre, sem conseguir nunca mais trocar
11
+ // a própria senha — a política tem que valer pra senha NOVA, não pra que já existe.
12
+ //
13
+ // `message` usa `getLocaleFromContext()` (o mesmo AsyncLocalStorage que
14
+ // `IsStrongPasswordWithSettingsConstraint.defaultMessage()` lê) porque
15
+ // `ValidationArguments` não carrega o locale da requisição — só o valor do campo.
16
+ @IsString({
17
+ message: () =>
18
+ getLocaleText(
19
+ 'profile.changePassword.currentPasswordIsString',
20
+ getLocaleFromContext(),
21
+ 'Current password must be a string.',
22
+ ),
8
23
  })
9
24
  currentPassword: string;
10
25
 
11
- @IsString({
12
- message: (args) => getLocaleText('profile.changePassword.newPasswordIsString', args.value)
13
- })
14
- @MinLength(6)
26
+ // Antes só tinha `@IsString` + `MinLength(6)`: a nova senha não passava por
27
+ // NENHUMA regra de política (maiúscula, símbolo, senha vazada...) além do
28
+ // tamanho mínimo fixo. `IsStrongPasswordWithSettingsConstraint` já produz uma
29
+ // mensagem específica por regra via `defaultMessage()` (usa o mesmo
30
+ // `getLocaleFromContext()` acima), então não sobrescrevemos com `message` aqui —
31
+ // mesmo padrão de `CreateWithEmailAndPasswordDTO.password`.
32
+ @IsStrongPasswordWithSettings()
15
33
  newPassword: string;
16
34
  }
@@ -0,0 +1,97 @@
1
+ import { describe, it, expect, afterEach, jest } from '@jest/globals';
2
+ import { NotFoundException } from '@nestjs/common';
3
+
4
+ import { ProfileService } from './profile.service';
5
+
6
+ /**
7
+ * O que estes casos protegem é uma frase só: "esta conta tem senha" precisa
8
+ * significar a mesma coisa aqui e no resto do core.
9
+ *
10
+ * `hasPasswordCredential` (usado pelo "esqueci minha senha") sempre exigiu
11
+ * credencial **ativa**, e a revogação é o que o convite de acesso enterprise usa
12
+ * para trocar "autorize o acesso" por "crie sua conta e defina a senha". Estas
13
+ * duas leituras do perfil ignoravam `revoked_at`: `changePassword` aceitava a
14
+ * senha de uma credencial revogada e regravava o hash nela, e o `hasPassword` do
15
+ * `GET /profile` — que o app usa para decidir se pede senha — dizia que sim.
16
+ */
17
+
18
+ const makeDeps = () => ({
19
+ prisma: {
20
+ user: { findUnique: jest.fn() },
21
+ user_credential: { findFirst: jest.fn(), update: jest.fn() },
22
+ },
23
+ user: { registerUserActivity: jest.fn() },
24
+ jwt: {},
25
+ setting: {},
26
+ security: { validatePassword: jest.fn(), hashArgon2: jest.fn() },
27
+ challenge: { resolveAppKeyFromOrigin: jest.fn() },
28
+ token: {},
29
+ mail: { resolveRecipientLocale: jest.fn(), sendTemplatedMail: jest.fn() },
30
+ eventEmitter: { emit: jest.fn() },
31
+ });
32
+
33
+ const makeService = (deps = makeDeps()) => ({
34
+ service: new ProfileService(
35
+ deps.prisma as any,
36
+ deps.user as any,
37
+ deps.jwt as any,
38
+ deps.setting as any,
39
+ deps.security as any,
40
+ deps.challenge as any,
41
+ deps.token as any,
42
+ deps.mail as any,
43
+ deps.eventEmitter as any,
44
+ ),
45
+ deps,
46
+ });
47
+
48
+ describe('ProfileService', () => {
49
+ afterEach(() => jest.clearAllMocks());
50
+
51
+ describe('changePassword', () => {
52
+ it('procura apenas credencial de senha não revogada', async () => {
53
+ const { service, deps } = makeService();
54
+ deps.prisma.user_credential.findFirst.mockResolvedValue(null);
55
+
56
+ await expect(
57
+ service.changePassword('pt-BR', 7, {
58
+ currentPassword: 'antiga',
59
+ newPassword: 'nova-senha-forte',
60
+ } as any),
61
+ ).rejects.toThrow(NotFoundException);
62
+
63
+ expect(deps.prisma.user_credential.findFirst).toHaveBeenCalledWith({
64
+ where: { user_id: 7, type: 'password', revoked_at: null },
65
+ });
66
+ // Nada de validar hash revogado: sem credencial ativa o fluxo para antes.
67
+ expect(deps.security.validatePassword).not.toHaveBeenCalled();
68
+ });
69
+ });
70
+
71
+ describe('getProfile', () => {
72
+ it('conta apenas credencial de senha não revogada em hasPassword', async () => {
73
+ const { service, deps } = makeService();
74
+ deps.prisma.user.findUnique.mockResolvedValue({ id: 7, name: 'Aluno' });
75
+ deps.prisma.user_credential.findFirst.mockResolvedValue(null);
76
+ const count = jest.fn().mockResolvedValue(0);
77
+ (deps.prisma.user_credential as any).count = count;
78
+
79
+ const result = await service.getProfile(7);
80
+
81
+ expect(count).toHaveBeenCalledWith({
82
+ where: { user_id: 7, type: 'password', revoked_at: null },
83
+ });
84
+ expect(result).toEqual({ id: 7, name: 'Aluno', hasPassword: false });
85
+ });
86
+
87
+ it('devolve hasPassword true quando existe credencial ativa', async () => {
88
+ const { service, deps } = makeService();
89
+ deps.prisma.user.findUnique.mockResolvedValue({ id: 7, name: 'Aluno' });
90
+ (deps.prisma.user_credential as any).count = jest.fn().mockResolvedValue(1);
91
+
92
+ await expect(service.getProfile(7)).resolves.toMatchObject({
93
+ hasPassword: true,
94
+ });
95
+ });
96
+ });
97
+ });