@hed-hog/core 0.0.390 → 0.0.394

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 (528) hide show
  1. package/README.md +39 -0
  2. package/dist/access-log/access-log.controller.d.ts.map +1 -1
  3. package/dist/access-log/access-log.controller.js +1 -1
  4. package/dist/access-log/access-log.controller.js.map +1 -1
  5. package/dist/access-log/access-log.interceptor.d.ts.map +1 -1
  6. package/dist/access-log/access-log.interceptor.js +8 -5
  7. package/dist/access-log/access-log.interceptor.js.map +1 -1
  8. package/dist/access-log/access-log.service.d.ts +4 -0
  9. package/dist/access-log/access-log.service.d.ts.map +1 -1
  10. package/dist/access-log/access-log.service.js +31 -4
  11. package/dist/access-log/access-log.service.js.map +1 -1
  12. package/dist/access-log/dto/query-access-log.dto.d.ts +2 -0
  13. package/dist/access-log/dto/query-access-log.dto.d.ts.map +1 -1
  14. package/dist/access-log/dto/query-access-log.dto.js +5 -0
  15. package/dist/access-log/dto/query-access-log.dto.js.map +1 -1
  16. package/dist/ai/ai.controller.d.ts +12 -8
  17. package/dist/ai/ai.controller.d.ts.map +1 -1
  18. package/dist/ai/ai.controller.js +17 -2
  19. package/dist/ai/ai.controller.js.map +1 -1
  20. package/dist/ai/ai.module.d.ts.map +1 -1
  21. package/dist/ai/ai.module.js +3 -2
  22. package/dist/ai/ai.module.js.map +1 -1
  23. package/dist/ai/ai.service.d.ts +30 -1
  24. package/dist/ai/ai.service.d.ts.map +1 -1
  25. package/dist/ai/ai.service.js +97 -3
  26. package/dist/ai/ai.service.js.map +1 -1
  27. package/dist/ai/dto/richtext-edit-html.dto.d.ts +5 -0
  28. package/dist/ai/dto/richtext-edit-html.dto.d.ts.map +1 -0
  29. package/dist/ai/dto/richtext-edit-html.dto.js +27 -0
  30. package/dist/ai/dto/richtext-edit-html.dto.js.map +1 -0
  31. package/dist/ai/richtext-ai.prompts.d.ts +5 -0
  32. package/dist/ai/richtext-ai.prompts.d.ts.map +1 -0
  33. package/dist/ai/richtext-ai.prompts.js +57 -0
  34. package/dist/ai/richtext-ai.prompts.js.map +1 -0
  35. package/dist/ai/richtext-ai.service.d.ts +34 -0
  36. package/dist/ai/richtext-ai.service.d.ts.map +1 -0
  37. package/dist/ai/richtext-ai.service.js +95 -0
  38. package/dist/ai/richtext-ai.service.js.map +1 -0
  39. package/dist/ai-instruction/ai-instruction.controller.d.ts +5 -5
  40. package/dist/auth/auth.controller.d.ts +66 -7
  41. package/dist/auth/auth.controller.d.ts.map +1 -1
  42. package/dist/auth/auth.controller.js +67 -23
  43. package/dist/auth/auth.controller.js.map +1 -1
  44. package/dist/auth/auth.module.d.ts.map +1 -1
  45. package/dist/auth/auth.module.js +2 -0
  46. package/dist/auth/auth.module.js.map +1 -1
  47. package/dist/auth/auth.service.d.ts +136 -4
  48. package/dist/auth/auth.service.d.ts.map +1 -1
  49. package/dist/auth/auth.service.js +246 -48
  50. package/dist/auth/auth.service.js.map +1 -1
  51. package/dist/auth/dto/confirm-email.dto.d.ts +4 -0
  52. package/dist/auth/dto/confirm-email.dto.d.ts.map +1 -0
  53. package/dist/auth/dto/confirm-email.dto.js +22 -0
  54. package/dist/auth/dto/confirm-email.dto.js.map +1 -0
  55. package/dist/auth/dto/resend-confirmation.dto.d.ts +8 -0
  56. package/dist/auth/dto/resend-confirmation.dto.d.ts.map +1 -0
  57. package/dist/auth/dto/resend-confirmation.dto.js +36 -0
  58. package/dist/auth/dto/resend-confirmation.dto.js.map +1 -0
  59. package/dist/authorize/authorize.controller.d.ts +1 -1
  60. package/dist/authorize/authorize.controller.d.ts.map +1 -1
  61. package/dist/authorize/authorize.controller.js +4 -2
  62. package/dist/authorize/authorize.controller.js.map +1 -1
  63. package/dist/authorize/authorize.service.d.ts +7 -0
  64. package/dist/authorize/authorize.service.d.ts.map +1 -1
  65. package/dist/authorize/authorize.service.js +1 -1
  66. package/dist/authorize/authorize.service.js.map +1 -1
  67. package/dist/challenge/challenge.service.d.ts +18 -5
  68. package/dist/challenge/challenge.service.d.ts.map +1 -1
  69. package/dist/challenge/challenge.service.js +50 -3
  70. package/dist/challenge/challenge.service.js.map +1 -1
  71. package/dist/core.module.d.ts.map +1 -1
  72. package/dist/core.module.js +17 -0
  73. package/dist/core.module.js.map +1 -1
  74. package/dist/dashboard/dashboard/dashboard.controller.d.ts +6 -6
  75. package/dist/dashboard/dashboard/dashboard.service.d.ts +6 -6
  76. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +4 -4
  77. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +4 -4
  78. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +1 -1
  79. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +1 -1
  80. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +2 -2
  81. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +2 -2
  82. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +2 -2
  83. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +2 -2
  84. package/dist/file/file.controller.d.ts +8 -1
  85. package/dist/file/file.controller.d.ts.map +1 -1
  86. package/dist/file/file.controller.js +45 -7
  87. package/dist/file/file.controller.js.map +1 -1
  88. package/dist/file/file.service.d.ts +128 -1
  89. package/dist/file/file.service.d.ts.map +1 -1
  90. package/dist/file/file.service.js +319 -8
  91. package/dist/file/file.service.js.map +1 -1
  92. package/dist/file/image-optimization.service.d.ts +43 -0
  93. package/dist/file/image-optimization.service.d.ts.map +1 -1
  94. package/dist/file/image-optimization.service.js +154 -0
  95. package/dist/file/image-optimization.service.js.map +1 -1
  96. package/dist/file/provider/abstract.provider.d.ts +16 -0
  97. package/dist/file/provider/abstract.provider.d.ts.map +1 -1
  98. package/dist/file/provider/abstract.provider.js +4 -0
  99. package/dist/file/provider/abstract.provider.js.map +1 -1
  100. package/dist/file/provider/gcs.provider.d.ts +2 -0
  101. package/dist/file/provider/gcs.provider.d.ts.map +1 -1
  102. package/dist/file/provider/gcs.provider.js +10 -0
  103. package/dist/file/provider/gcs.provider.js.map +1 -1
  104. package/dist/file/provider/local.provider.d.ts +1 -0
  105. package/dist/file/provider/local.provider.d.ts.map +1 -1
  106. package/dist/file/provider/local.provider.js +13 -0
  107. package/dist/file/provider/local.provider.js.map +1 -1
  108. package/dist/file/provider/s3.provider.d.ts +5 -0
  109. package/dist/file/provider/s3.provider.d.ts.map +1 -1
  110. package/dist/file/provider/s3.provider.js +15 -0
  111. package/dist/file/provider/s3.provider.js.map +1 -1
  112. package/dist/file/provider/spaces.provider.d.ts +5 -0
  113. package/dist/file/provider/spaces.provider.d.ts.map +1 -1
  114. package/dist/file/provider/spaces.provider.js +16 -0
  115. package/dist/file/provider/spaces.provider.js.map +1 -1
  116. package/dist/impersonation/dto/redeem-impersonation.dto.d.ts +4 -0
  117. package/dist/impersonation/dto/redeem-impersonation.dto.d.ts.map +1 -0
  118. package/dist/impersonation/dto/redeem-impersonation.dto.js +23 -0
  119. package/dist/impersonation/dto/redeem-impersonation.dto.js.map +1 -0
  120. package/dist/impersonation/dto/start-impersonation.dto.d.ts +7 -0
  121. package/dist/impersonation/dto/start-impersonation.dto.d.ts.map +1 -0
  122. package/dist/impersonation/dto/start-impersonation.dto.js +27 -0
  123. package/dist/impersonation/dto/start-impersonation.dto.js.map +1 -0
  124. package/dist/impersonation/impersonation-restrictions.d.ts +26 -0
  125. package/dist/impersonation/impersonation-restrictions.d.ts.map +1 -0
  126. package/dist/impersonation/impersonation-restrictions.js +76 -0
  127. package/dist/impersonation/impersonation-restrictions.js.map +1 -0
  128. package/dist/impersonation/impersonation.controller.d.ts +90 -0
  129. package/dist/impersonation/impersonation.controller.d.ts.map +1 -0
  130. package/dist/impersonation/impersonation.controller.js +135 -0
  131. package/dist/impersonation/impersonation.controller.js.map +1 -0
  132. package/dist/impersonation/impersonation.module.d.ts +8 -0
  133. package/dist/impersonation/impersonation.module.d.ts.map +1 -0
  134. package/dist/impersonation/impersonation.module.js +44 -0
  135. package/dist/impersonation/impersonation.module.js.map +1 -0
  136. package/dist/impersonation/impersonation.service.d.ts +164 -0
  137. package/dist/impersonation/impersonation.service.d.ts.map +1 -0
  138. package/dist/impersonation/impersonation.service.js +408 -0
  139. package/dist/impersonation/impersonation.service.js.map +1 -0
  140. package/dist/index.d.ts +17 -0
  141. package/dist/index.d.ts.map +1 -1
  142. package/dist/index.js +19 -0
  143. package/dist/index.js.map +1 -1
  144. package/dist/integration/services/outbox-polling.coordinator.d.ts +2 -0
  145. package/dist/integration/services/outbox-polling.coordinator.d.ts.map +1 -1
  146. package/dist/integration/services/outbox-polling.coordinator.js +50 -30
  147. package/dist/integration/services/outbox-polling.coordinator.js.map +1 -1
  148. package/dist/integration/services/outbox.service.d.ts.map +1 -1
  149. package/dist/integration/services/outbox.service.js +5 -1
  150. package/dist/integration/services/outbox.service.js.map +1 -1
  151. package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -1
  152. package/dist/integration-profile/integration-profile.secrets.js +3 -0
  153. package/dist/integration-profile/integration-profile.secrets.js.map +1 -1
  154. package/dist/integration-profile/integration-profile.utils.d.ts +14 -0
  155. package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
  156. package/dist/integration-profile/integration-profile.utils.js +26 -0
  157. package/dist/integration-profile/integration-profile.utils.js.map +1 -1
  158. package/dist/integration-profile/testers/email.testers.d.ts.map +1 -1
  159. package/dist/integration-profile/testers/email.testers.js +28 -0
  160. package/dist/integration-profile/testers/email.testers.js.map +1 -1
  161. package/dist/language/en.json +11 -0
  162. package/dist/language/pt.json +11 -0
  163. package/dist/mail/dto/send.dto.d.ts +38 -1
  164. package/dist/mail/dto/send.dto.d.ts.map +1 -1
  165. package/dist/mail/dto/send.dto.js +10 -0
  166. package/dist/mail/dto/send.dto.js.map +1 -1
  167. package/dist/mail/mail.controller.d.ts +1 -0
  168. package/dist/mail/mail.controller.d.ts.map +1 -1
  169. package/dist/mail/mail.module.d.ts.map +1 -1
  170. package/dist/mail/mail.module.js +2 -0
  171. package/dist/mail/mail.module.js.map +1 -1
  172. package/dist/mail/mail.service.d.ts +48 -4
  173. package/dist/mail/mail.service.d.ts.map +1 -1
  174. package/dist/mail/mail.service.js +187 -35
  175. package/dist/mail/mail.service.js.map +1 -1
  176. package/dist/mail-delivery/mail-delivery-provision.service.d.ts +21 -0
  177. package/dist/mail-delivery/mail-delivery-provision.service.d.ts.map +1 -0
  178. package/dist/mail-delivery/mail-delivery-provision.service.js +64 -0
  179. package/dist/mail-delivery/mail-delivery-provision.service.js.map +1 -0
  180. package/dist/mail-delivery/mail-delivery.constants.d.ts +17 -0
  181. package/dist/mail-delivery/mail-delivery.constants.d.ts.map +1 -0
  182. package/dist/mail-delivery/mail-delivery.constants.js +31 -0
  183. package/dist/mail-delivery/mail-delivery.constants.js.map +1 -0
  184. package/dist/mail-delivery/mail-delivery.controller.d.ts +19 -0
  185. package/dist/mail-delivery/mail-delivery.controller.d.ts.map +1 -0
  186. package/dist/mail-delivery/mail-delivery.controller.js +53 -0
  187. package/dist/mail-delivery/mail-delivery.controller.js.map +1 -0
  188. package/dist/mail-delivery/mail-delivery.module.d.ts +7 -0
  189. package/dist/mail-delivery/mail-delivery.module.d.ts.map +1 -0
  190. package/dist/mail-delivery/mail-delivery.module.js +36 -0
  191. package/dist/mail-delivery/mail-delivery.module.js.map +1 -0
  192. package/dist/mail-delivery/mail-delivery.service.d.ts +38 -0
  193. package/dist/mail-delivery/mail-delivery.service.d.ts.map +1 -0
  194. package/dist/mail-delivery/mail-delivery.service.js +140 -0
  195. package/dist/mail-delivery/mail-delivery.service.js.map +1 -0
  196. package/dist/mail-delivery/ses-event.parser.d.ts +27 -0
  197. package/dist/mail-delivery/ses-event.parser.d.ts.map +1 -0
  198. package/dist/mail-delivery/ses-event.parser.js +131 -0
  199. package/dist/mail-delivery/ses-event.parser.js.map +1 -0
  200. package/dist/mail-delivery/ses-webhook.service.d.ts +58 -0
  201. package/dist/mail-delivery/ses-webhook.service.d.ts.map +1 -0
  202. package/dist/mail-delivery/ses-webhook.service.js +214 -0
  203. package/dist/mail-delivery/ses-webhook.service.js.map +1 -0
  204. package/dist/mail-delivery/sns-message.util.d.ts +38 -0
  205. package/dist/mail-delivery/sns-message.util.d.ts.map +1 -0
  206. package/dist/mail-delivery/sns-message.util.js +109 -0
  207. package/dist/mail-delivery/sns-message.util.js.map +1 -0
  208. package/dist/mail-sent/dto/query-mail-sent-list.dto.d.ts +5 -0
  209. package/dist/mail-sent/dto/query-mail-sent-list.dto.d.ts.map +1 -1
  210. package/dist/mail-sent/dto/query-mail-sent-list.dto.js +6 -0
  211. package/dist/mail-sent/dto/query-mail-sent-list.dto.js.map +1 -1
  212. package/dist/mail-sent/mail-sent.controller.d.ts +39 -0
  213. package/dist/mail-sent/mail-sent.controller.d.ts.map +1 -1
  214. package/dist/mail-sent/mail-sent.service.d.ts +39 -0
  215. package/dist/mail-sent/mail-sent.service.d.ts.map +1 -1
  216. package/dist/mail-sent/mail-sent.service.js +45 -4
  217. package/dist/mail-sent/mail-sent.service.js.map +1 -1
  218. package/dist/mailbox/mailbox-fetcher.registry.d.ts +100 -0
  219. package/dist/mailbox/mailbox-fetcher.registry.d.ts.map +1 -0
  220. package/dist/mailbox/mailbox-fetcher.registry.js +59 -0
  221. package/dist/mailbox/mailbox-fetcher.registry.js.map +1 -0
  222. package/dist/mailbox/mailbox.module.d.ts +8 -0
  223. package/dist/mailbox/mailbox.module.d.ts.map +1 -0
  224. package/dist/mailbox/mailbox.module.js +27 -0
  225. package/dist/mailbox/mailbox.module.js.map +1 -0
  226. package/dist/meeting/meeting-provider.registry.d.ts +155 -0
  227. package/dist/meeting/meeting-provider.registry.d.ts.map +1 -0
  228. package/dist/meeting/meeting-provider.registry.js +53 -0
  229. package/dist/meeting/meeting-provider.registry.js.map +1 -0
  230. package/dist/meeting/meeting.constants.d.ts +47 -0
  231. package/dist/meeting/meeting.constants.d.ts.map +1 -0
  232. package/dist/meeting/meeting.constants.js +21 -0
  233. package/dist/meeting/meeting.constants.js.map +1 -0
  234. package/dist/meeting/meeting.module.d.ts +7 -0
  235. package/dist/meeting/meeting.module.d.ts.map +1 -0
  236. package/dist/meeting/meeting.module.js +26 -0
  237. package/dist/meeting/meeting.module.js.map +1 -0
  238. package/dist/middlewares/locale-injection.middleware.d.ts +12 -0
  239. package/dist/middlewares/locale-injection.middleware.d.ts.map +1 -1
  240. package/dist/middlewares/locale-injection.middleware.js +20 -4
  241. package/dist/middlewares/locale-injection.middleware.js.map +1 -1
  242. package/dist/oauth/providers/google.provider.d.ts +7 -0
  243. package/dist/oauth/providers/google.provider.d.ts.map +1 -1
  244. package/dist/oauth/providers/google.provider.js +43 -13
  245. package/dist/oauth/providers/google.provider.js.map +1 -1
  246. package/dist/observability/monitored-cron.d.ts +34 -0
  247. package/dist/observability/monitored-cron.d.ts.map +1 -0
  248. package/dist/observability/monitored-cron.js +118 -0
  249. package/dist/observability/monitored-cron.js.map +1 -0
  250. package/dist/pairing/dto/approve-reverse-pairing.dto.d.ts +12 -0
  251. package/dist/pairing/dto/approve-reverse-pairing.dto.d.ts.map +1 -0
  252. package/dist/pairing/dto/approve-reverse-pairing.dto.js +30 -0
  253. package/dist/pairing/dto/approve-reverse-pairing.dto.js.map +1 -0
  254. package/dist/pairing/dto/claim-pairing.dto.d.ts +11 -0
  255. package/dist/pairing/dto/claim-pairing.dto.d.ts.map +1 -0
  256. package/dist/pairing/dto/claim-pairing.dto.js +35 -0
  257. package/dist/pairing/dto/claim-pairing.dto.js.map +1 -0
  258. package/dist/pairing/dto/confirm-pairing.dto.d.ts +10 -0
  259. package/dist/pairing/dto/confirm-pairing.dto.d.ts.map +1 -0
  260. package/dist/pairing/dto/confirm-pairing.dto.js +24 -0
  261. package/dist/pairing/dto/confirm-pairing.dto.js.map +1 -0
  262. package/dist/pairing/dto/poll-reverse-pairing.dto.d.ts +5 -0
  263. package/dist/pairing/dto/poll-reverse-pairing.dto.d.ts.map +1 -0
  264. package/dist/pairing/dto/poll-reverse-pairing.dto.js +24 -0
  265. package/dist/pairing/dto/poll-reverse-pairing.dto.js.map +1 -0
  266. package/dist/pairing/dto/start-pairing.dto.d.ts +11 -0
  267. package/dist/pairing/dto/start-pairing.dto.d.ts.map +1 -0
  268. package/dist/pairing/dto/start-pairing.dto.js +30 -0
  269. package/dist/pairing/dto/start-pairing.dto.js.map +1 -0
  270. package/dist/pairing/dto/start-reverse-pairing.dto.d.ts +24 -0
  271. package/dist/pairing/dto/start-reverse-pairing.dto.d.ts.map +1 -0
  272. package/dist/pairing/dto/start-reverse-pairing.dto.js +47 -0
  273. package/dist/pairing/dto/start-reverse-pairing.dto.js.map +1 -0
  274. package/dist/pairing/pairing.controller.d.ts +118 -0
  275. package/dist/pairing/pairing.controller.d.ts.map +1 -0
  276. package/dist/pairing/pairing.controller.js +296 -0
  277. package/dist/pairing/pairing.controller.js.map +1 -0
  278. package/dist/pairing/pairing.module.d.ts +10 -0
  279. package/dist/pairing/pairing.module.d.ts.map +1 -0
  280. package/dist/pairing/pairing.module.js +40 -0
  281. package/dist/pairing/pairing.module.js.map +1 -0
  282. package/dist/pairing/pairing.service.d.ts +238 -0
  283. package/dist/pairing/pairing.service.d.ts.map +1 -0
  284. package/dist/pairing/pairing.service.js +495 -0
  285. package/dist/pairing/pairing.service.js.map +1 -0
  286. package/dist/pdf/pdf.service.d.ts +3 -6
  287. package/dist/pdf/pdf.service.d.ts.map +1 -1
  288. package/dist/pdf/pdf.service.js +8 -20
  289. package/dist/pdf/pdf.service.js.map +1 -1
  290. package/dist/profile/profile.controller.d.ts +11 -0
  291. package/dist/profile/profile.controller.d.ts.map +1 -1
  292. package/dist/profile/profile.service.d.ts +11 -0
  293. package/dist/profile/profile.service.d.ts.map +1 -1
  294. package/dist/profile/profile.service.js +20 -11
  295. package/dist/profile/profile.service.js.map +1 -1
  296. package/dist/redis/redis-pubsub.d.ts.map +1 -1
  297. package/dist/redis/redis-pubsub.js +44 -0
  298. package/dist/redis/redis-pubsub.js.map +1 -1
  299. package/dist/role/guards/role.guard.d.ts +5 -0
  300. package/dist/role/guards/role.guard.d.ts.map +1 -1
  301. package/dist/role/guards/role.guard.js +41 -39
  302. package/dist/role/guards/role.guard.js.map +1 -1
  303. package/dist/session/session.controller.d.ts +2 -0
  304. package/dist/session/session.controller.d.ts.map +1 -1
  305. package/dist/session/session.service.d.ts +39 -0
  306. package/dist/session/session.service.d.ts.map +1 -1
  307. package/dist/session/session.service.js +91 -8
  308. package/dist/session/session.service.js.map +1 -1
  309. package/dist/setting/brand-asset.service.d.ts +48 -0
  310. package/dist/setting/brand-asset.service.d.ts.map +1 -0
  311. package/dist/setting/brand-asset.service.js +130 -0
  312. package/dist/setting/brand-asset.service.js.map +1 -0
  313. package/dist/setting/setting.module.d.ts.map +1 -1
  314. package/dist/setting/setting.module.js +3 -2
  315. package/dist/setting/setting.module.js.map +1 -1
  316. package/dist/setting/setting.service.d.ts +33 -0
  317. package/dist/setting/setting.service.d.ts.map +1 -1
  318. package/dist/setting/setting.service.js +88 -0
  319. package/dist/setting/setting.service.js.map +1 -1
  320. package/dist/token/token.service.d.ts +23 -2
  321. package/dist/token/token.service.d.ts.map +1 -1
  322. package/dist/token/token.service.js +39 -4
  323. package/dist/token/token.service.js.map +1 -1
  324. package/dist/user/user.controller.d.ts +8 -22
  325. package/dist/user/user.controller.d.ts.map +1 -1
  326. package/dist/user/user.service.d.ts +44 -24
  327. package/dist/user/user.service.d.ts.map +1 -1
  328. package/dist/user/user.service.js +89 -6
  329. package/dist/user/user.service.js.map +1 -1
  330. package/dist/user-device/user-device.controller.d.ts +54 -0
  331. package/dist/user-device/user-device.controller.d.ts.map +1 -0
  332. package/dist/user-device/user-device.controller.js +103 -0
  333. package/dist/user-device/user-device.controller.js.map +1 -0
  334. package/dist/user-device/user-device.module.d.ts +3 -0
  335. package/dist/user-device/user-device.module.d.ts.map +1 -0
  336. package/dist/user-device/user-device.module.js +26 -0
  337. package/dist/user-device/user-device.module.js.map +1 -0
  338. package/dist/user-device/user-device.service.d.ts +71 -0
  339. package/dist/user-device/user-device.service.d.ts.map +1 -0
  340. package/dist/user-device/user-device.service.js +158 -0
  341. package/dist/user-device/user-device.service.js.map +1 -0
  342. package/dist/utils/device-key.d.ts +23 -0
  343. package/dist/utils/device-key.d.ts.map +1 -0
  344. package/dist/utils/device-key.js +31 -0
  345. package/dist/utils/device-key.js.map +1 -0
  346. package/dist/utils/geo-ip.d.ts +38 -0
  347. package/dist/utils/geo-ip.d.ts.map +1 -0
  348. package/dist/utils/geo-ip.js +122 -0
  349. package/dist/utils/geo-ip.js.map +1 -0
  350. package/dist/utils/locale-context.d.ts +10 -7
  351. package/dist/utils/locale-context.d.ts.map +1 -1
  352. package/dist/utils/locale-context.js +14 -8
  353. package/dist/utils/locale-context.js.map +1 -1
  354. package/dist/utils/security-event-vars.d.ts +56 -0
  355. package/dist/utils/security-event-vars.d.ts.map +1 -0
  356. package/dist/utils/security-event-vars.js +109 -0
  357. package/dist/utils/security-event-vars.js.map +1 -0
  358. package/dist/utils/user-agent.d.ts +19 -0
  359. package/dist/utils/user-agent.d.ts.map +1 -0
  360. package/dist/utils/user-agent.js +36 -0
  361. package/dist/utils/user-agent.js.map +1 -0
  362. package/dist/webhook-integration/webhook-integration.service.d.ts +5 -0
  363. package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
  364. package/dist/webhook-integration/webhook-integration.service.js +50 -4
  365. package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
  366. package/dist/webhook-integration/webhook-protocol.registry.d.ts +6 -0
  367. package/dist/webhook-integration/webhook-protocol.registry.d.ts.map +1 -1
  368. package/dist/webhook-integration/webhook-protocol.registry.js.map +1 -1
  369. package/hedhog/data/integration_provider.yaml +9 -0
  370. package/hedhog/data/mail.yaml +198 -62
  371. package/hedhog/data/mail_layout.yaml +43 -33
  372. package/hedhog/data/menu.yaml +2 -0
  373. package/hedhog/data/role.yaml +16 -0
  374. package/hedhog/data/route.yaml +172 -3
  375. package/hedhog/data/setting_group.yaml +178 -2
  376. package/hedhog/data/setting_subgroup.yaml +8 -0
  377. package/hedhog/frontend/app/_lib/hooks/use-core-access.test.tsx.ejs +125 -0
  378. package/hedhog/frontend/app/_lib/hooks/use-core-access.ts.ejs +29 -0
  379. package/hedhog/frontend/app/_lib/hooks/use-impersonation-access.test.ts.ejs +71 -0
  380. package/hedhog/frontend/app/_lib/hooks/use-impersonation-access.ts.ejs +32 -0
  381. package/hedhog/frontend/app/access-log/page.tsx.ejs +96 -22
  382. package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +17 -1
  383. package/hedhog/frontend/app/account/components/known-devices.tsx.ejs +89 -0
  384. package/hedhog/frontend/app/account/components/profile-form.tsx.ejs +2 -1
  385. package/hedhog/frontend/app/account/components/user-devices-list.tsx.ejs +186 -0
  386. package/hedhog/frontend/app/account/devices/page.tsx.ejs +5 -0
  387. package/hedhog/frontend/app/account/layout.tsx.ejs +2 -0
  388. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.test.tsx.ejs +298 -0
  389. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +376 -1236
  390. package/hedhog/frontend/app/mail/log/page.tsx.ejs +210 -3
  391. package/hedhog/frontend/app/mail/template/page.tsx.ejs +190 -132
  392. package/hedhog/frontend/app/users/active-session.tsx.ejs +58 -11
  393. package/hedhog/frontend/app/users/page.tsx.ejs +69 -10
  394. package/hedhog/frontend/app/users/simulate-access-dialog.test.tsx.ejs +202 -0
  395. package/hedhog/frontend/app/users/simulate-access-dialog.tsx.ejs +170 -0
  396. package/hedhog/frontend/app/users/user-activity-timeline.tsx.ejs +19 -0
  397. package/hedhog/frontend/app/users/user-devices.tsx.ejs +89 -0
  398. package/hedhog/frontend/app/users/user-logs.tsx.ejs +13 -1
  399. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +46 -24
  400. package/hedhog/frontend/messages/en.json +146 -48
  401. package/hedhog/frontend/messages/pt.json +146 -48
  402. package/hedhog/table/access_log.yaml +7 -0
  403. package/hedhog/table/file.yaml +8 -0
  404. package/hedhog/table/mail_delivery_event.yaml +76 -0
  405. package/hedhog/table/mail_sent.yaml +23 -0
  406. package/hedhog/table/user_activity.yaml +9 -0
  407. package/hedhog/table/user_device.yaml +52 -0
  408. package/hedhog/table/user_session.yaml +20 -1
  409. package/package.json +6 -5
  410. package/src/access-log/access-log.controller.ts +1 -0
  411. package/src/access-log/access-log.interceptor.ts +5 -1
  412. package/src/access-log/access-log.service.spec.ts +33 -2
  413. package/src/access-log/access-log.service.ts +38 -0
  414. package/src/access-log/dto/query-access-log.dto.ts +5 -0
  415. package/src/ai/ai.controller.ts +9 -0
  416. package/src/ai/ai.module.ts +3 -2
  417. package/src/ai/ai.service.ts +150 -13
  418. package/src/ai/dto/richtext-edit-html.dto.ts +11 -0
  419. package/src/ai/richtext-ai.prompts.ts +57 -0
  420. package/src/ai/richtext-ai.service.ts +109 -0
  421. package/src/auth/auth.controller.ts +47 -8
  422. package/src/auth/auth.module.ts +2 -0
  423. package/src/auth/auth.service.spec.ts +383 -6
  424. package/src/auth/auth.service.ts +345 -56
  425. package/src/auth/dto/confirm-email.dto.ts +7 -0
  426. package/src/auth/dto/resend-confirmation.dto.ts +20 -0
  427. package/src/authorize/authorize.controller.ts +2 -0
  428. package/src/authorize/authorize.service.spec.ts +23 -0
  429. package/src/authorize/authorize.service.ts +8 -0
  430. package/src/challenge/challenge.service.spec.ts +73 -0
  431. package/src/challenge/challenge.service.ts +62 -3
  432. package/src/core.module.ts +19 -0
  433. package/src/file/file.controller.ts +54 -4
  434. package/src/file/file.service.spec.ts +275 -105
  435. package/src/file/file.service.ts +409 -14
  436. package/src/file/image-optimization.service.spec.ts +92 -0
  437. package/src/file/image-optimization.service.ts +202 -0
  438. package/src/file/provider/abstract.provider.ts +23 -0
  439. package/src/file/provider/gcs.provider.ts +17 -0
  440. package/src/file/provider/local.provider.ts +21 -0
  441. package/src/file/provider/s3.provider.ts +25 -0
  442. package/src/file/provider/spaces.provider.ts +26 -0
  443. package/src/impersonation/dto/redeem-impersonation.dto.ts +8 -0
  444. package/src/impersonation/dto/start-impersonation.dto.ts +13 -0
  445. package/src/impersonation/impersonation-restrictions.ts +84 -0
  446. package/src/impersonation/impersonation.controller.ts +109 -0
  447. package/src/impersonation/impersonation.module.ts +31 -0
  448. package/src/impersonation/impersonation.service.spec.ts +427 -0
  449. package/src/impersonation/impersonation.service.ts +577 -0
  450. package/src/index.ts +21 -0
  451. package/src/integration/services/outbox-polling.coordinator.spec.ts +90 -0
  452. package/src/integration/services/outbox-polling.coordinator.ts +62 -40
  453. package/src/integration/services/outbox.service.spec.ts +8 -0
  454. package/src/integration/services/outbox.service.ts +19 -13
  455. package/src/integration-profile/integration-profile.secrets.ts +3 -0
  456. package/src/integration-profile/integration-profile.utils.ts +38 -0
  457. package/src/integration-profile/testers/email.testers.ts +34 -1
  458. package/src/language/en.json +11 -0
  459. package/src/language/pt.json +11 -0
  460. package/src/mail/dto/send.dto.ts +46 -2
  461. package/src/mail/mail-template.spec.ts +79 -2
  462. package/src/mail/mail.module.ts +2 -0
  463. package/src/mail/mail.service.spec.ts +307 -6
  464. package/src/mail/mail.service.ts +227 -30
  465. package/src/mail-delivery/mail-delivery-provision.service.ts +66 -0
  466. package/src/mail-delivery/mail-delivery.constants.ts +31 -0
  467. package/src/mail-delivery/mail-delivery.controller.ts +38 -0
  468. package/src/mail-delivery/mail-delivery.module.ts +23 -0
  469. package/src/mail-delivery/mail-delivery.service.spec.ts +136 -0
  470. package/src/mail-delivery/mail-delivery.service.ts +152 -0
  471. package/src/mail-delivery/ses-event.parser.spec.ts +140 -0
  472. package/src/mail-delivery/ses-event.parser.ts +191 -0
  473. package/src/mail-delivery/ses-webhook.service.spec.ts +170 -0
  474. package/src/mail-delivery/ses-webhook.service.ts +240 -0
  475. package/src/mail-delivery/sns-message.util.spec.ts +163 -0
  476. package/src/mail-delivery/sns-message.util.ts +125 -0
  477. package/src/mail-sent/dto/query-mail-sent-list.dto.ts +15 -0
  478. package/src/mail-sent/mail-sent.service.ts +46 -2
  479. package/src/mailbox/mailbox-fetcher.registry.ts +132 -0
  480. package/src/mailbox/mailbox.module.ts +14 -0
  481. package/src/meeting/meeting-provider.registry.ts +193 -0
  482. package/src/meeting/meeting.constants.ts +53 -0
  483. package/src/meeting/meeting.module.ts +13 -0
  484. package/src/middlewares/locale-injection.middleware.ts +25 -6
  485. package/src/oauth/providers/google.provider.ts +49 -15
  486. package/src/observability/monitored-cron.spec.ts +102 -0
  487. package/src/observability/monitored-cron.ts +110 -0
  488. package/src/pairing/dto/approve-reverse-pairing.dto.ts +20 -0
  489. package/src/pairing/dto/claim-pairing.dto.ts +19 -0
  490. package/src/pairing/dto/confirm-pairing.dto.ts +14 -0
  491. package/src/pairing/dto/poll-reverse-pairing.dto.ts +9 -0
  492. package/src/pairing/dto/start-pairing.dto.ts +19 -0
  493. package/src/pairing/dto/start-reverse-pairing.dto.ts +45 -0
  494. package/src/pairing/pairing.controller.ts +246 -0
  495. package/src/pairing/pairing.module.ts +27 -0
  496. package/src/pairing/pairing.service.spec.ts +462 -0
  497. package/src/pairing/pairing.service.ts +614 -0
  498. package/src/pdf/pdf.service.ts +6 -24
  499. package/src/profile/profile.service.ts +26 -8
  500. package/src/redis/redis-pubsub.ts +11 -0
  501. package/src/role/guards/role.guard.spec.ts +75 -0
  502. package/src/role/guards/role.guard.ts +64 -46
  503. package/src/session/session.service.spec.ts +81 -0
  504. package/src/session/session.service.ts +93 -6
  505. package/src/setting/brand-asset.service.ts +137 -0
  506. package/src/setting/setting.module.ts +3 -2
  507. package/src/setting/setting.service.spec.ts +79 -1
  508. package/src/setting/setting.service.ts +109 -1
  509. package/src/token/token.service.spec.ts +62 -0
  510. package/src/token/token.service.ts +57 -3
  511. package/src/user/user.service.spec.ts +33 -2
  512. package/src/user/user.service.ts +108 -6
  513. package/src/user-device/user-device.controller.ts +77 -0
  514. package/src/user-device/user-device.module.ts +13 -0
  515. package/src/user-device/user-device.service.spec.ts +234 -0
  516. package/src/user-device/user-device.service.ts +168 -0
  517. package/src/utils/device-key.spec.ts +57 -0
  518. package/src/utils/device-key.ts +32 -0
  519. package/src/utils/geo-ip.spec.ts +134 -0
  520. package/src/utils/geo-ip.ts +93 -0
  521. package/src/utils/locale-context.spec.ts +27 -1
  522. package/src/utils/locale-context.ts +15 -12
  523. package/src/utils/security-event-vars.spec.ts +161 -0
  524. package/src/utils/security-event-vars.ts +153 -0
  525. package/src/utils/user-agent.spec.ts +37 -0
  526. package/src/utils/user-agent.ts +46 -0
  527. package/src/webhook-integration/webhook-integration.service.ts +57 -3
  528. package/src/webhook-integration/webhook-protocol.registry.ts +6 -0
@@ -0,0 +1,136 @@
1
+ import { MailDeliveryService } from './mail-delivery.service';
2
+
3
+ describe('MailDeliveryService', () => {
4
+ let prisma: any;
5
+ let service: MailDeliveryService;
6
+
7
+ const messageId = '010001a0152e404c-d20f2613-b278-4738-bb2c-a5901a8e1a94-000000';
8
+ const mail = { timestamp: '2026-08-18T14:02:27.000Z', messageId };
9
+
10
+ beforeEach(() => {
11
+ prisma = {
12
+ mail_sent: {
13
+ findFirst: jest.fn().mockResolvedValue({ id: 767 }),
14
+ updateMany: jest.fn().mockResolvedValue({ count: 1 }),
15
+ },
16
+ mail_delivery_event: { create: jest.fn().mockResolvedValue({ id: 1 }) },
17
+ };
18
+ service = new MailDeliveryService(prisma);
19
+ });
20
+
21
+ it('liga o evento ao envio pelo provider_message_id', async () => {
22
+ const result = await service.ingestSesNotification({
23
+ notificationType: 'Delivery',
24
+ mail,
25
+ delivery: { recipients: ['joao@hcode.com.br'], smtpResponse: '250 ok' },
26
+ });
27
+
28
+ expect(prisma.mail_sent.findFirst).toHaveBeenCalledWith(
29
+ expect.objectContaining({ where: { provider_message_id: messageId } }),
30
+ );
31
+ expect(prisma.mail_delivery_event.create).toHaveBeenCalledWith(
32
+ expect.objectContaining({
33
+ data: expect.objectContaining({
34
+ mail_sent_id: 767,
35
+ provider_message_id: messageId,
36
+ recipient_email: 'joao@hcode.com.br',
37
+ event_type: 'delivery',
38
+ }),
39
+ }),
40
+ );
41
+ expect(result).toEqual({ events: 1, stored: 1, matched: 1 });
42
+ });
43
+
44
+ it('grava o evento mesmo sem envio correspondente', async () => {
45
+ // E justamente a evidencia de que algo saiu por fora do Hub; descartar seria
46
+ // apagar o unico rastro.
47
+ prisma.mail_sent.findFirst.mockResolvedValue(null);
48
+
49
+ const result = await service.ingestSesNotification({
50
+ notificationType: 'Bounce',
51
+ mail,
52
+ bounce: { bouncedRecipients: [{ emailAddress: 'x@y.com' }] },
53
+ });
54
+
55
+ expect(prisma.mail_delivery_event.create).toHaveBeenCalledWith(
56
+ expect.objectContaining({
57
+ data: expect.objectContaining({ mail_sent_id: null }),
58
+ }),
59
+ );
60
+ expect(result).toEqual({ events: 1, stored: 1, matched: 0 });
61
+ });
62
+
63
+ it('trata a reentrega do SNS como caminho normal, nao como erro', async () => {
64
+ prisma.mail_delivery_event.create.mockRejectedValue({ code: 'P2002' });
65
+
66
+ const result = await service.ingestSesNotification({
67
+ notificationType: 'Delivery',
68
+ mail,
69
+ delivery: { recipients: ['joao@hcode.com.br'] },
70
+ });
71
+
72
+ expect(result).toEqual({ events: 1, stored: 0, matched: 1 });
73
+ });
74
+
75
+ it('marca o envio como bounced', async () => {
76
+ await service.ingestSesNotification({
77
+ notificationType: 'Bounce',
78
+ mail,
79
+ bounce: {
80
+ bounceType: 'Permanent',
81
+ bouncedRecipients: [{ emailAddress: 'joao@hcode.com.br' }],
82
+ },
83
+ });
84
+
85
+ expect(prisma.mail_sent.updateMany).toHaveBeenCalledWith(
86
+ expect.objectContaining({
87
+ data: expect.objectContaining({ delivery_status: 'bounced' }),
88
+ }),
89
+ );
90
+ });
91
+
92
+ it('so sobrescreve status mais fracos que o novo', async () => {
93
+ // Um Delivery atrasado nao pode apagar um Complaint: marcar como spam depois
94
+ // de receber e o caso comum, nao a excecao.
95
+ await service.ingestSesNotification({
96
+ notificationType: 'Delivery',
97
+ mail,
98
+ delivery: { recipients: ['joao@hcode.com.br'] },
99
+ });
100
+
101
+ const where = prisma.mail_sent.updateMany.mock.calls[0][0].where;
102
+ expect(where.OR).toEqual([
103
+ { delivery_status: null },
104
+ { delivery_status: { in: [] } },
105
+ ]);
106
+ });
107
+
108
+ it('complaint sobrescreve qualquer desfecho anterior', async () => {
109
+ await service.ingestSesNotification({
110
+ notificationType: 'Complaint',
111
+ mail,
112
+ complaint: { complainedRecipients: [{ emailAddress: 'joao@hcode.com.br' }] },
113
+ });
114
+
115
+ const call = prisma.mail_sent.updateMany.mock.calls[0][0];
116
+ expect(call.data.delivery_status).toBe('complained');
117
+ expect(call.where.OR[1].delivery_status.in).toEqual(
118
+ expect.arrayContaining(['delivered', 'bounced', 'rejected']),
119
+ );
120
+ });
121
+
122
+ it('evento que nao decide entrega nao mexe no status', async () => {
123
+ // Send, Open e Click dizem o que aconteceu com a mensagem, nao se ela chegou.
124
+ await service.ingestSesNotification({ eventType: 'Open', mail });
125
+
126
+ expect(prisma.mail_delivery_event.create).toHaveBeenCalled();
127
+ expect(prisma.mail_sent.updateMany).not.toHaveBeenCalled();
128
+ });
129
+
130
+ it('ignora notificacao irreconhecivel sem estourar', async () => {
131
+ const result = await service.ingestSesNotification({ foo: 'bar' });
132
+
133
+ expect(result).toEqual({ events: 0, stored: 0, matched: 0 });
134
+ expect(prisma.mail_delivery_event.create).not.toHaveBeenCalled();
135
+ });
136
+ });
@@ -0,0 +1,152 @@
1
+ import { PrismaService } from '@hed-hog/api-prisma';
2
+ import { Inject, Injectable, Logger, forwardRef } from '@nestjs/common';
3
+ import {
4
+ MAIL_DELIVERY_EVENT_STATUS,
5
+ MAIL_DELIVERY_STATUS_RANK,
6
+ } from './mail-delivery.constants';
7
+ import { SesDeliveryEvent, parseSesNotification } from './ses-event.parser';
8
+
9
+ export type MailDeliveryIngestResult = {
10
+ events: number;
11
+ stored: number;
12
+ matched: number;
13
+ };
14
+
15
+ /**
16
+ * Grava o que o provedor diz depois de aceitar a mensagem.
17
+ *
18
+ * Ate existir isto, "enviado" no log de e-mails significava apenas "o provedor
19
+ * aceitou". Tres mensagens do chamado 202608-000020 sairam com MessageId de
20
+ * sucesso do SES e nenhuma chegou a caixa alguma - e nao havia nada no Hub que
21
+ * pudesse mostrar isso.
22
+ */
23
+ @Injectable()
24
+ export class MailDeliveryService {
25
+ private readonly logger = new Logger(MailDeliveryService.name);
26
+
27
+ constructor(
28
+ @Inject(forwardRef(() => PrismaService))
29
+ private readonly prisma: PrismaService,
30
+ ) {}
31
+
32
+ /** Processa uma notificacao ja desembrulhada do envelope do SNS. */
33
+ async ingestSesNotification(payload: any): Promise<MailDeliveryIngestResult> {
34
+ const events = parseSesNotification(payload);
35
+ if (!events.length) {
36
+ this.logger.warn(
37
+ `Ignoring SES notification without a recognizable event type or messageId.`,
38
+ );
39
+ return { events: 0, stored: 0, matched: 0 };
40
+ }
41
+
42
+ let stored = 0;
43
+ let matched = 0;
44
+
45
+ for (const event of events) {
46
+ const mailSentId = await this.findMailSentId(event.providerMessageId);
47
+ if (mailSentId) matched += 1;
48
+
49
+ const created = await this.storeEvent(event, mailSentId, payload);
50
+ if (created) stored += 1;
51
+ }
52
+
53
+ // O status consolidado e por mensagem, nao por destinatario: e o que a
54
+ // listagem mostra numa linha so.
55
+ await this.applyStatus(events[0].providerMessageId, events);
56
+
57
+ return { events: events.length, stored, matched };
58
+ }
59
+
60
+ private async findMailSentId(providerMessageId: string): Promise<number | null> {
61
+ const row = await this.prisma.mail_sent.findFirst({
62
+ where: { provider_message_id: providerMessageId },
63
+ select: { id: true },
64
+ orderBy: { id: 'desc' },
65
+ });
66
+ return row?.id ?? null;
67
+ }
68
+
69
+ /**
70
+ * Devolve `false` quando o evento ja existia.
71
+ *
72
+ * O SNS reentrega quando a resposta demora, entao a colisao no indice de
73
+ * dedupe e o caminho normal - nao um erro a ser propagado.
74
+ */
75
+ private async storeEvent(
76
+ event: SesDeliveryEvent,
77
+ mailSentId: number | null,
78
+ payload: any,
79
+ ): Promise<boolean> {
80
+ try {
81
+ await this.prisma.mail_delivery_event.create({
82
+ data: {
83
+ mail_sent_id: mailSentId,
84
+ provider_message_id: event.providerMessageId,
85
+ recipient_email: event.recipientEmail,
86
+ event_type: event.eventType,
87
+ bounce_type: event.bounceType,
88
+ bounce_subtype: event.bounceSubtype,
89
+ diagnostic_code: event.diagnosticCode,
90
+ reporting_mta: event.reportingMta,
91
+ payload: payload ?? undefined,
92
+ occurred_at: event.occurredAt,
93
+ },
94
+ });
95
+
96
+ if (!mailSentId) {
97
+ // Evento de um envio que nao esta no mail_sent: guardado de proposito,
98
+ // porque e justamente a evidencia de que algo saiu por fora.
99
+ this.logger.warn(
100
+ `Delivery event "${event.eventType}" for unknown messageId ${event.providerMessageId}`,
101
+ );
102
+ }
103
+
104
+ return true;
105
+ } catch (error) {
106
+ if ((error as any)?.code === 'P2002') return false;
107
+ this.logger.error(
108
+ `Failed to store delivery event for ${event.providerMessageId}: ${
109
+ (error as Error)?.message
110
+ }`,
111
+ );
112
+ return false;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Consolida o desfecho em `mail_sent.delivery_status`.
118
+ *
119
+ * So sobe na escala: uma reclamacao que chega depois da entrega nao pode ser
120
+ * apagada por um `Delivery` atrasado - e o destinatario marcar como spam
121
+ * depois de receber e exatamente o caso comum.
122
+ */
123
+ private async applyStatus(
124
+ providerMessageId: string,
125
+ events: SesDeliveryEvent[],
126
+ ): Promise<void> {
127
+ let best: string | null = null;
128
+ for (const event of events) {
129
+ const status = MAIL_DELIVERY_EVENT_STATUS[event.eventType];
130
+ if (!status) continue;
131
+ if (!best || MAIL_DELIVERY_STATUS_RANK[status] > MAIL_DELIVERY_STATUS_RANK[best]) {
132
+ best = status;
133
+ }
134
+ }
135
+
136
+ // Send, Open, Click e afins nao mudam o desfecho da entrega.
137
+ if (!best) return;
138
+
139
+ const rank = MAIL_DELIVERY_STATUS_RANK[best];
140
+ const weaker = Object.entries(MAIL_DELIVERY_STATUS_RANK)
141
+ .filter(([, value]) => value < rank)
142
+ .map(([key]) => key);
143
+
144
+ await this.prisma.mail_sent.updateMany({
145
+ where: {
146
+ provider_message_id: providerMessageId,
147
+ OR: [{ delivery_status: null }, { delivery_status: { in: weaker } }],
148
+ },
149
+ data: { delivery_status: best, delivery_updated_at: new Date() },
150
+ });
151
+ }
152
+ }
@@ -0,0 +1,140 @@
1
+ import { parseSesNotification } from './ses-event.parser';
2
+
3
+ describe('parseSesNotification', () => {
4
+ const mail = {
5
+ timestamp: '2026-08-18T14:02:27.000Z',
6
+ messageId: '010001a0152e404c-d20f2613-b278-4738-bb2c-a5901a8e1a94-000000',
7
+ source: 'hcode@hcode.com.br',
8
+ destination: ['joao@hcode.com.br'],
9
+ };
10
+
11
+ it('extrai o diagnostic code de cada destinatario que voltou', () => {
12
+ // O diagnosticCode e a resposta literal do servidor destinatario: e ele que
13
+ // responde "por que nao chegou".
14
+ const events = parseSesNotification({
15
+ notificationType: 'Bounce',
16
+ mail,
17
+ bounce: {
18
+ bounceType: 'Permanent',
19
+ bounceSubType: 'General',
20
+ timestamp: '2026-08-18T14:02:30.000Z',
21
+ reportingMTA: 'dns; a8-70.smtp-out.amazonses.com',
22
+ bouncedRecipients: [
23
+ {
24
+ emailAddress: 'joao@hcode.com.br',
25
+ status: '5.1.1',
26
+ diagnosticCode: 'smtp; 550 5.1.1 User unknown',
27
+ },
28
+ { emailAddress: 'ronaldo@hcode.com.br', status: '5.2.2' },
29
+ ],
30
+ },
31
+ });
32
+
33
+ expect(events).toHaveLength(2);
34
+ expect(events[0]).toMatchObject({
35
+ providerMessageId: mail.messageId,
36
+ recipientEmail: 'joao@hcode.com.br',
37
+ eventType: 'bounce',
38
+ bounceType: 'Permanent',
39
+ bounceSubtype: 'General',
40
+ diagnosticCode: 'smtp; 550 5.1.1 User unknown',
41
+ reportingMta: 'dns; a8-70.smtp-out.amazonses.com',
42
+ });
43
+ expect(events[0].occurredAt.toISOString()).toBe('2026-08-18T14:02:30.000Z');
44
+ // Sem diagnosticCode, o status ainda diz alguma coisa.
45
+ expect(events[1].diagnosticCode).toBe('5.2.2');
46
+ });
47
+
48
+ it('registra o bounce mesmo sem destinatario listado', () => {
49
+ const events = parseSesNotification({
50
+ notificationType: 'Bounce',
51
+ mail,
52
+ bounce: { bounceType: 'Transient', bouncedRecipients: [] },
53
+ });
54
+
55
+ expect(events).toHaveLength(1);
56
+ expect(events[0].recipientEmail).toBeNull();
57
+ expect(events[0].eventType).toBe('bounce');
58
+ });
59
+
60
+ it('expande a entrega por destinatario e guarda a resposta SMTP', () => {
61
+ const events = parseSesNotification({
62
+ notificationType: 'Delivery',
63
+ mail,
64
+ delivery: {
65
+ timestamp: '2026-08-18T14:02:29.000Z',
66
+ recipients: ['joao@hcode.com.br', 'ronaldo@hcode.com.br'],
67
+ smtpResponse: '250 2.6.0 Message received',
68
+ reportingMTA: 'a8-70.smtp-out.amazonses.com',
69
+ },
70
+ });
71
+
72
+ expect(events.map((e) => e.recipientEmail)).toEqual([
73
+ 'joao@hcode.com.br',
74
+ 'ronaldo@hcode.com.br',
75
+ ]);
76
+ expect(events[0].eventType).toBe('delivery');
77
+ expect(events[0].diagnosticCode).toBe('250 2.6.0 Message received');
78
+ });
79
+
80
+ it('le a reclamacao e o tipo de feedback', () => {
81
+ const events = parseSesNotification({
82
+ notificationType: 'Complaint',
83
+ mail,
84
+ complaint: {
85
+ complainedRecipients: [{ emailAddress: 'joao@hcode.com.br' }],
86
+ complaintFeedbackType: 'abuse',
87
+ timestamp: '2026-08-18T15:00:00.000Z',
88
+ },
89
+ });
90
+
91
+ expect(events).toHaveLength(1);
92
+ expect(events[0]).toMatchObject({
93
+ eventType: 'complaint',
94
+ recipientEmail: 'joao@hcode.com.br',
95
+ diagnosticCode: 'abuse',
96
+ });
97
+ });
98
+
99
+ it('aceita o formato de configuration set (eventType)', () => {
100
+ // Qual formato chega depende de como as notificacoes foram ligadas no
101
+ // console da AWS - uma escolha que nem sempre e nossa.
102
+ const events = parseSesNotification({
103
+ eventType: 'Reject',
104
+ mail,
105
+ reject: { reason: 'Bad content' },
106
+ });
107
+
108
+ expect(events).toHaveLength(1);
109
+ expect(events[0]).toMatchObject({
110
+ eventType: 'reject',
111
+ recipientEmail: 'joao@hcode.com.br',
112
+ diagnosticCode: 'Bad content',
113
+ });
114
+ });
115
+
116
+ it('guarda um tipo desconhecido em vez de descartar', () => {
117
+ const events = parseSesNotification({ eventType: 'DeliveryDelay', mail });
118
+
119
+ expect(events).toHaveLength(1);
120
+ expect(events[0].eventType).toBe('deliverydelay');
121
+ });
122
+
123
+ it('ignora payload sem tipo ou sem messageId', () => {
124
+ expect(parseSesNotification({ mail })).toEqual([]);
125
+ expect(parseSesNotification({ notificationType: 'Bounce', mail: {} })).toEqual([]);
126
+ expect(parseSesNotification(null)).toEqual([]);
127
+ });
128
+
129
+ it('nao produz Invalid Date quando o timestamp vem quebrado', () => {
130
+ // occurred_at e NOT NULL e entra na chave de dedupe: um Invalid Date
131
+ // derrubaria a ingestao do lote inteiro.
132
+ const events = parseSesNotification({
133
+ notificationType: 'Delivery',
134
+ mail: { ...mail, timestamp: 'nao e data' },
135
+ delivery: { recipients: ['joao@hcode.com.br'] },
136
+ });
137
+
138
+ expect(Number.isNaN(events[0].occurredAt.getTime())).toBe(false);
139
+ });
140
+ });
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Um evento de entrega ja normalizado, independente do formato em que o SES o
3
+ * enviou.
4
+ */
5
+ export type SesDeliveryEvent = {
6
+ providerMessageId: string;
7
+ recipientEmail: string | null;
8
+ /** minusculo: delivery | bounce | complaint | reject | send | open | click | ... */
9
+ eventType: string;
10
+ bounceType: string | null;
11
+ bounceSubtype: string | null;
12
+ diagnosticCode: string | null;
13
+ reportingMta: string | null;
14
+ occurredAt: Date;
15
+ };
16
+
17
+ /**
18
+ * O SES publica o mesmo fato em dois formatos, e qual deles chega depende de
19
+ * como as notificacoes foram ligadas:
20
+ *
21
+ * - notificacoes por identidade (o caminho simples): `notificationType`
22
+ * - configuration set com event destination: `eventType`
23
+ *
24
+ * Suportar os dois e barato e evita que a escolha feita no console da AWS - que
25
+ * nem sempre e nossa - quebre a ingestao.
26
+ */
27
+ export function parseSesNotification(payload: any): SesDeliveryEvent[] {
28
+ const rawType = String(payload?.notificationType ?? payload?.eventType ?? '').trim();
29
+ if (!rawType) return [];
30
+
31
+ const providerMessageId = String(payload?.mail?.messageId ?? '').trim();
32
+ if (!providerMessageId) return [];
33
+
34
+ const type = rawType.toLowerCase();
35
+ const mailTimestamp = payload?.mail?.timestamp;
36
+
37
+ switch (type) {
38
+ case 'bounce': {
39
+ const bounce = payload.bounce ?? {};
40
+ const recipients = Array.isArray(bounce.bouncedRecipients)
41
+ ? bounce.bouncedRecipients
42
+ : [];
43
+ const occurredAt = toDate(bounce.timestamp ?? mailTimestamp);
44
+
45
+ // Um bounce sem destinatario listado ainda e um bounce: registrar sem
46
+ // e-mail e melhor que descartar a unica evidencia de que a mensagem voltou.
47
+ if (!recipients.length) {
48
+ return [
49
+ {
50
+ providerMessageId,
51
+ recipientEmail: null,
52
+ eventType: type,
53
+ bounceType: str(bounce.bounceType),
54
+ bounceSubtype: str(bounce.bounceSubType),
55
+ diagnosticCode: null,
56
+ reportingMta: str(bounce.reportingMTA),
57
+ occurredAt,
58
+ },
59
+ ];
60
+ }
61
+
62
+ return recipients.map((recipient: any) => ({
63
+ providerMessageId,
64
+ recipientEmail: str(recipient?.emailAddress),
65
+ eventType: type,
66
+ bounceType: str(bounce.bounceType),
67
+ bounceSubtype: str(bounce.bounceSubType),
68
+ // A resposta literal do servidor destinatario - o campo que responde
69
+ // "por que nao chegou".
70
+ diagnosticCode: str(recipient?.diagnosticCode) ?? str(recipient?.status),
71
+ reportingMta: str(bounce.reportingMTA),
72
+ occurredAt,
73
+ }));
74
+ }
75
+
76
+ case 'complaint': {
77
+ const complaint = payload.complaint ?? {};
78
+ const recipients = Array.isArray(complaint.complainedRecipients)
79
+ ? complaint.complainedRecipients
80
+ : [];
81
+ const occurredAt = toDate(complaint.timestamp ?? mailTimestamp);
82
+ const feedback = str(complaint.complaintFeedbackType);
83
+
84
+ if (!recipients.length) {
85
+ return [
86
+ buildSimple(providerMessageId, null, type, occurredAt, feedback, null),
87
+ ];
88
+ }
89
+
90
+ return recipients.map((recipient: any) =>
91
+ buildSimple(
92
+ providerMessageId,
93
+ str(recipient?.emailAddress),
94
+ type,
95
+ occurredAt,
96
+ feedback,
97
+ null,
98
+ ),
99
+ );
100
+ }
101
+
102
+ case 'delivery': {
103
+ const delivery = payload.delivery ?? {};
104
+ const recipients = Array.isArray(delivery.recipients) ? delivery.recipients : [];
105
+ const occurredAt = toDate(delivery.timestamp ?? mailTimestamp);
106
+
107
+ if (!recipients.length) {
108
+ return [
109
+ buildSimple(
110
+ providerMessageId,
111
+ null,
112
+ type,
113
+ occurredAt,
114
+ str(delivery.smtpResponse),
115
+ str(delivery.reportingMTA),
116
+ ),
117
+ ];
118
+ }
119
+
120
+ return recipients.map((recipient: any) =>
121
+ buildSimple(
122
+ providerMessageId,
123
+ str(recipient),
124
+ type,
125
+ occurredAt,
126
+ str(delivery.smtpResponse),
127
+ str(delivery.reportingMTA),
128
+ ),
129
+ );
130
+ }
131
+
132
+ default: {
133
+ // Send, Reject, Open, Click, DeliveryDelay, RenderingFailure e o que a AWS
134
+ // acrescentar depois. Guardar o desconhecido e o que faz um tipo novo
135
+ // aparecer no log em vez de sumir.
136
+ const destinations = Array.isArray(payload?.mail?.destination)
137
+ ? payload.mail.destination
138
+ : [];
139
+ const occurredAt = toDate(mailTimestamp);
140
+ const detail =
141
+ str(payload?.reject?.reason) ??
142
+ str(payload?.failure?.errorMessage) ??
143
+ str(payload?.deliveryDelay?.delayType);
144
+
145
+ if (!destinations.length) {
146
+ return [
147
+ buildSimple(providerMessageId, null, type, occurredAt, detail, null),
148
+ ];
149
+ }
150
+
151
+ return destinations.map((address: any) =>
152
+ buildSimple(providerMessageId, str(address), type, occurredAt, detail, null),
153
+ );
154
+ }
155
+ }
156
+ }
157
+
158
+ function buildSimple(
159
+ providerMessageId: string,
160
+ recipientEmail: string | null,
161
+ eventType: string,
162
+ occurredAt: Date,
163
+ diagnosticCode: string | null,
164
+ reportingMta: string | null,
165
+ ): SesDeliveryEvent {
166
+ return {
167
+ providerMessageId,
168
+ recipientEmail,
169
+ eventType,
170
+ bounceType: null,
171
+ bounceSubtype: null,
172
+ diagnosticCode,
173
+ reportingMta,
174
+ occurredAt,
175
+ };
176
+ }
177
+
178
+ function str(value: unknown): string | null {
179
+ const text = String(value ?? '').trim();
180
+ return text || null;
181
+ }
182
+
183
+ /**
184
+ * Data do provedor. Cai para agora quando o timestamp vem ausente ou invalido -
185
+ * a coluna e NOT NULL e faz parte da chave de dedupe, entao um `Invalid Date`
186
+ * derrubaria a ingestao inteira do lote.
187
+ */
188
+ function toDate(value: unknown): Date {
189
+ const parsed = new Date(String(value ?? ''));
190
+ return Number.isNaN(parsed.getTime()) ? new Date() : parsed;
191
+ }