@open-mercato/core 0.6.5-develop.4384.1.ce2ec6eaaa → 0.6.5-develop.4393.1.de282b5dfd

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 (533) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/channel_ingest_dead_letter/index.js +25 -0
  3. package/dist/generated/entities/channel_ingest_dead_letter/index.js.map +7 -0
  4. package/dist/generated/entities/channel_thread_mapping/index.js +25 -0
  5. package/dist/generated/entities/channel_thread_mapping/index.js.map +7 -0
  6. package/dist/generated/entities/channel_thread_token/index.js +17 -0
  7. package/dist/generated/entities/channel_thread_token/index.js.map +7 -0
  8. package/dist/generated/entities/communication_channel/index.js +43 -0
  9. package/dist/generated/entities/communication_channel/index.js.map +7 -0
  10. package/dist/generated/entities/customer_interaction/index.js +4 -0
  11. package/dist/generated/entities/customer_interaction/index.js.map +2 -2
  12. package/dist/generated/entities/external_conversation/index.js +25 -0
  13. package/dist/generated/entities/external_conversation/index.js.map +7 -0
  14. package/dist/generated/entities/external_message/index.js +25 -0
  15. package/dist/generated/entities/external_message/index.js.map +7 -0
  16. package/dist/generated/entities/integration_credentials/index.js +3 -1
  17. package/dist/generated/entities/integration_credentials/index.js.map +2 -2
  18. package/dist/generated/entities/message/index.js +2 -0
  19. package/dist/generated/entities/message/index.js.map +2 -2
  20. package/dist/generated/entities/message_channel_link/index.js +33 -0
  21. package/dist/generated/entities/message_channel_link/index.js.map +7 -0
  22. package/dist/generated/entities/message_reaction/index.js +25 -0
  23. package/dist/generated/entities/message_reaction/index.js.map +7 -0
  24. package/dist/generated/entities.ids.generated.js +11 -0
  25. package/dist/generated/entities.ids.generated.js.map +2 -2
  26. package/dist/generated/entity-fields-registry.js +117 -0
  27. package/dist/generated/entity-fields-registry.js.map +2 -2
  28. package/dist/helpers/integration/authFixtures.js +2 -1
  29. package/dist/helpers/integration/authFixtures.js.map +2 -2
  30. package/dist/helpers/integration/communicationChannelsFixtures.js +58 -0
  31. package/dist/helpers/integration/communicationChannelsFixtures.js.map +7 -0
  32. package/dist/modules/communication_channels/acl.js +47 -0
  33. package/dist/modules/communication_channels/acl.js.map +7 -0
  34. package/dist/modules/communication_channels/api/delete/channels/[id]/route.js +133 -0
  35. package/dist/modules/communication_channels/api/delete/channels/[id]/route.js.map +7 -0
  36. package/dist/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.js +113 -0
  37. package/dist/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.js.map +7 -0
  38. package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js +138 -0
  39. package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js.map +7 -0
  40. package/dist/modules/communication_channels/api/get/channels/[id]/route.js +93 -0
  41. package/dist/modules/communication_channels/api/get/channels/[id]/route.js.map +7 -0
  42. package/dist/modules/communication_channels/api/get/channels/route.js +96 -0
  43. package/dist/modules/communication_channels/api/get/channels/route.js.map +7 -0
  44. package/dist/modules/communication_channels/api/get/me/channels/route.js +82 -0
  45. package/dist/modules/communication_channels/api/get/me/channels/route.js.map +7 -0
  46. package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js +274 -0
  47. package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js.map +7 -0
  48. package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js +168 -0
  49. package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js.map +7 -0
  50. package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js +143 -0
  51. package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js.map +7 -0
  52. package/dist/modules/communication_channels/api/post/channels/[id]/push/register/route.js +127 -0
  53. package/dist/modules/communication_channels/api/post/channels/[id]/push/register/route.js.map +7 -0
  54. package/dist/modules/communication_channels/api/post/channels/[id]/set-primary/route.js +99 -0
  55. package/dist/modules/communication_channels/api/post/channels/[id]/set-primary/route.js.map +7 -0
  56. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +197 -0
  57. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +7 -0
  58. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +124 -0
  59. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +7 -0
  60. package/dist/modules/communication_channels/api/post/messages/[messageId]/reactions/route.js +120 -0
  61. package/dist/modules/communication_channels/api/post/messages/[messageId]/reactions/route.js.map +7 -0
  62. package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +157 -0
  63. package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +7 -0
  64. package/dist/modules/communication_channels/api/post/send-as-user/route.js +115 -0
  65. package/dist/modules/communication_channels/api/post/send-as-user/route.js.map +7 -0
  66. package/dist/modules/communication_channels/api/post/test-seed/route.js +217 -0
  67. package/dist/modules/communication_channels/api/post/test-seed/route.js.map +7 -0
  68. package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js +175 -0
  69. package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js.map +7 -0
  70. package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js +123 -0
  71. package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js.map +7 -0
  72. package/dist/modules/communication_channels/api/put/threads/[threadId]/assign/route.js +117 -0
  73. package/dist/modules/communication_channels/api/put/threads/[threadId]/assign/route.js.map +7 -0
  74. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js +180 -0
  75. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js.map +7 -0
  76. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.js +36 -0
  77. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.js.map +7 -0
  78. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +107 -0
  79. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +7 -0
  80. package/dist/modules/communication_channels/backend/communication_channels/channels/page.meta.js +38 -0
  81. package/dist/modules/communication_channels/backend/communication_channels/channels/page.meta.js.map +7 -0
  82. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +727 -0
  83. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +7 -0
  84. package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js +38 -0
  85. package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js.map +7 -0
  86. package/dist/modules/communication_channels/commands/connect-credential-channel.js +154 -0
  87. package/dist/modules/communication_channels/commands/connect-credential-channel.js.map +7 -0
  88. package/dist/modules/communication_channels/commands/delete-channel.js +137 -0
  89. package/dist/modules/communication_channels/commands/delete-channel.js.map +7 -0
  90. package/dist/modules/communication_channels/commands/deliver-outbound-message.js +400 -0
  91. package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +7 -0
  92. package/dist/modules/communication_channels/commands/disconnect-channel.js +163 -0
  93. package/dist/modules/communication_channels/commands/disconnect-channel.js.map +7 -0
  94. package/dist/modules/communication_channels/commands/ingest-inbound-message.js +413 -0
  95. package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +7 -0
  96. package/dist/modules/communication_channels/commands/interceptors.js +68 -0
  97. package/dist/modules/communication_channels/commands/interceptors.js.map +7 -0
  98. package/dist/modules/communication_channels/commands/process-inbound-reaction.js +198 -0
  99. package/dist/modules/communication_channels/commands/process-inbound-reaction.js.map +7 -0
  100. package/dist/modules/communication_channels/commands/push-register.js +146 -0
  101. package/dist/modules/communication_channels/commands/push-register.js.map +7 -0
  102. package/dist/modules/communication_channels/commands/push-renew.js +23 -0
  103. package/dist/modules/communication_channels/commands/push-renew.js.map +7 -0
  104. package/dist/modules/communication_channels/commands/push-unregister.js +108 -0
  105. package/dist/modules/communication_channels/commands/push-unregister.js.map +7 -0
  106. package/dist/modules/communication_channels/commands/queue-import-history.js +113 -0
  107. package/dist/modules/communication_channels/commands/queue-import-history.js.map +7 -0
  108. package/dist/modules/communication_channels/commands/reassign-conversation.js +193 -0
  109. package/dist/modules/communication_channels/commands/reassign-conversation.js.map +7 -0
  110. package/dist/modules/communication_channels/commands/set-primary-channel.js +114 -0
  111. package/dist/modules/communication_channels/commands/set-primary-channel.js.map +7 -0
  112. package/dist/modules/communication_channels/commands/toggle-outbound-reaction.js +260 -0
  113. package/dist/modules/communication_channels/commands/toggle-outbound-reaction.js.map +7 -0
  114. package/dist/modules/communication_channels/data/enrichers.js +286 -0
  115. package/dist/modules/communication_channels/data/enrichers.js.map +7 -0
  116. package/dist/modules/communication_channels/data/entities.js +447 -0
  117. package/dist/modules/communication_channels/data/entities.js.map +7 -0
  118. package/dist/modules/communication_channels/data/extensions.js +67 -0
  119. package/dist/modules/communication_channels/data/extensions.js.map +7 -0
  120. package/dist/modules/communication_channels/data/validators.js +123 -0
  121. package/dist/modules/communication_channels/data/validators.js.map +7 -0
  122. package/dist/modules/communication_channels/di.js +35 -0
  123. package/dist/modules/communication_channels/di.js.map +7 -0
  124. package/dist/modules/communication_channels/encryption.js +12 -0
  125. package/dist/modules/communication_channels/encryption.js.map +7 -0
  126. package/dist/modules/communication_channels/events.js +124 -0
  127. package/dist/modules/communication_channels/events.js.map +7 -0
  128. package/dist/modules/communication_channels/index.js +20 -0
  129. package/dist/modules/communication_channels/index.js.map +7 -0
  130. package/dist/modules/communication_channels/lib/access-control.js +43 -0
  131. package/dist/modules/communication_channels/lib/access-control.js.map +7 -0
  132. package/dist/modules/communication_channels/lib/adapter-compat.js +36 -0
  133. package/dist/modules/communication_channels/lib/adapter-compat.js.map +7 -0
  134. package/dist/modules/communication_channels/lib/adapter-registry-singleton.js +22 -0
  135. package/dist/modules/communication_channels/lib/adapter-registry-singleton.js.map +7 -0
  136. package/dist/modules/communication_channels/lib/adapter.js +1 -0
  137. package/dist/modules/communication_channels/lib/adapter.js.map +7 -0
  138. package/dist/modules/communication_channels/lib/connect-channel.js +95 -0
  139. package/dist/modules/communication_channels/lib/connect-channel.js.map +7 -0
  140. package/dist/modules/communication_channels/lib/contact-resolver.js +79 -0
  141. package/dist/modules/communication_channels/lib/contact-resolver.js.map +7 -0
  142. package/dist/modules/communication_channels/lib/credential-refresh.js +97 -0
  143. package/dist/modules/communication_channels/lib/credential-refresh.js.map +7 -0
  144. package/dist/modules/communication_channels/lib/dead-letter.js +62 -0
  145. package/dist/modules/communication_channels/lib/dead-letter.js.map +7 -0
  146. package/dist/modules/communication_channels/lib/email-capabilities.js +47 -0
  147. package/dist/modules/communication_channels/lib/email-capabilities.js.map +7 -0
  148. package/dist/modules/communication_channels/lib/email-contact.js +14 -0
  149. package/dist/modules/communication_channels/lib/email-contact.js.map +7 -0
  150. package/dist/modules/communication_channels/lib/email-mime.js +259 -0
  151. package/dist/modules/communication_channels/lib/email-mime.js.map +7 -0
  152. package/dist/modules/communication_channels/lib/error-classification.js +101 -0
  153. package/dist/modules/communication_channels/lib/error-classification.js.map +7 -0
  154. package/dist/modules/communication_channels/lib/gmail-pubsub-jwt.js +185 -0
  155. package/dist/modules/communication_channels/lib/gmail-pubsub-jwt.js.map +7 -0
  156. package/dist/modules/communication_channels/lib/mutation-guards.js +114 -0
  157. package/dist/modules/communication_channels/lib/mutation-guards.js.map +7 -0
  158. package/dist/modules/communication_channels/lib/oauth-client-config.js +32 -0
  159. package/dist/modules/communication_channels/lib/oauth-client-config.js.map +7 -0
  160. package/dist/modules/communication_channels/lib/oauth-state.js +128 -0
  161. package/dist/modules/communication_channels/lib/oauth-state.js.map +7 -0
  162. package/dist/modules/communication_channels/lib/oauth-token.js +45 -0
  163. package/dist/modules/communication_channels/lib/oauth-token.js.map +7 -0
  164. package/dist/modules/communication_channels/lib/pg-errors.js +11 -0
  165. package/dist/modules/communication_channels/lib/pg-errors.js.map +7 -0
  166. package/dist/modules/communication_channels/lib/provider-health.js +24 -0
  167. package/dist/modules/communication_channels/lib/provider-health.js.map +7 -0
  168. package/dist/modules/communication_channels/lib/push-state.js +19 -0
  169. package/dist/modules/communication_channels/lib/push-state.js.map +7 -0
  170. package/dist/modules/communication_channels/lib/queue.js +54 -0
  171. package/dist/modules/communication_channels/lib/queue.js.map +7 -0
  172. package/dist/modules/communication_channels/lib/reaction-processor-types.js +5 -0
  173. package/dist/modules/communication_channels/lib/reaction-processor-types.js.map +7 -0
  174. package/dist/modules/communication_channels/lib/reaction-semantics.js +11 -0
  175. package/dist/modules/communication_channels/lib/reaction-semantics.js.map +7 -0
  176. package/dist/modules/communication_channels/lib/registry.js +67 -0
  177. package/dist/modules/communication_channels/lib/registry.js.map +7 -0
  178. package/dist/modules/communication_channels/lib/route-mutation-guard.js +43 -0
  179. package/dist/modules/communication_channels/lib/route-mutation-guard.js.map +7 -0
  180. package/dist/modules/communication_channels/lib/sanitize-channel-html.js +96 -0
  181. package/dist/modules/communication_channels/lib/sanitize-channel-html.js.map +7 -0
  182. package/dist/modules/communication_channels/lib/send-as-user.js +194 -0
  183. package/dist/modules/communication_channels/lib/send-as-user.js.map +7 -0
  184. package/dist/modules/communication_channels/lib/system-user.js +22 -0
  185. package/dist/modules/communication_channels/lib/system-user.js.map +7 -0
  186. package/dist/modules/communication_channels/lib/test-seed.js +68 -0
  187. package/dist/modules/communication_channels/lib/test-seed.js.map +7 -0
  188. package/dist/modules/communication_channels/lib/thread-matcher.js +263 -0
  189. package/dist/modules/communication_channels/lib/thread-matcher.js.map +7 -0
  190. package/dist/modules/communication_channels/lib/thread-token.js +219 -0
  191. package/dist/modules/communication_channels/lib/thread-token.js.map +7 -0
  192. package/dist/modules/communication_channels/lib/use-connect-channel.js +61 -0
  193. package/dist/modules/communication_channels/lib/use-connect-channel.js.map +7 -0
  194. package/dist/modules/communication_channels/migrations/Migration20260526134719_communication_channels.js +50 -0
  195. package/dist/modules/communication_channels/migrations/Migration20260526134719_communication_channels.js.map +7 -0
  196. package/dist/modules/communication_channels/migrations/Migration20260527195446_communication_channels.js +19 -0
  197. package/dist/modules/communication_channels/migrations/Migration20260527195446_communication_channels.js.map +7 -0
  198. package/dist/modules/communication_channels/migrations/Migration20260529231848_communication_channels.js +13 -0
  199. package/dist/modules/communication_channels/migrations/Migration20260529231848_communication_channels.js.map +7 -0
  200. package/dist/modules/communication_channels/migrations/Migration20260531120000_communication_channels.js +17 -0
  201. package/dist/modules/communication_channels/migrations/Migration20260531120000_communication_channels.js.map +7 -0
  202. package/dist/modules/communication_channels/notifications.client.js +51 -0
  203. package/dist/modules/communication_channels/notifications.client.js.map +7 -0
  204. package/dist/modules/communication_channels/notifications.handlers.js +53 -0
  205. package/dist/modules/communication_channels/notifications.handlers.js.map +7 -0
  206. package/dist/modules/communication_channels/notifications.js +56 -0
  207. package/dist/modules/communication_channels/notifications.js.map +7 -0
  208. package/dist/modules/communication_channels/setup.js +105 -0
  209. package/dist/modules/communication_channels/setup.js.map +7 -0
  210. package/dist/modules/communication_channels/subscribers/channel-requires-reauth-notification.js +71 -0
  211. package/dist/modules/communication_channels/subscribers/channel-requires-reauth-notification.js.map +7 -0
  212. package/dist/modules/communication_channels/subscribers/outbound-bridge.js +103 -0
  213. package/dist/modules/communication_channels/subscribers/outbound-bridge.js.map +7 -0
  214. package/dist/modules/communication_channels/subscribers/user-deleted-cascade.js +51 -0
  215. package/dist/modules/communication_channels/subscribers/user-deleted-cascade.js.map +7 -0
  216. package/dist/modules/communication_channels/widgets/components.js +7 -0
  217. package/dist/modules/communication_channels/widgets/components.js.map +7 -0
  218. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.client.js +18 -0
  219. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.client.js.map +7 -0
  220. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.js +30 -0
  221. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.js.map +7 -0
  222. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.js +185 -0
  223. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.js.map +7 -0
  224. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.js +17 -0
  225. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.js.map +7 -0
  226. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js +44 -0
  227. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js.map +7 -0
  228. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js +17 -0
  229. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js.map +7 -0
  230. package/dist/modules/communication_channels/widgets/injection/profile-channels-menu/widget.js +23 -0
  231. package/dist/modules/communication_channels/widgets/injection/profile-channels-menu/widget.js.map +7 -0
  232. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.client.js +141 -0
  233. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.client.js.map +7 -0
  234. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.js +17 -0
  235. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.js.map +7 -0
  236. package/dist/modules/communication_channels/widgets/injection-table.js +38 -0
  237. package/dist/modules/communication_channels/widgets/injection-table.js.map +7 -0
  238. package/dist/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.js +25 -0
  239. package/dist/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.js.map +7 -0
  240. package/dist/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.js +19 -0
  241. package/dist/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.js.map +7 -0
  242. package/dist/modules/communication_channels/widgets/notifications/index.js +7 -0
  243. package/dist/modules/communication_channels/widgets/notifications/index.js.map +7 -0
  244. package/dist/modules/communication_channels/workers/channel-import-history.js +185 -0
  245. package/dist/modules/communication_channels/workers/channel-import-history.js.map +7 -0
  246. package/dist/modules/communication_channels/workers/gmail-history-sync.js +154 -0
  247. package/dist/modules/communication_channels/workers/gmail-history-sync.js.map +7 -0
  248. package/dist/modules/communication_channels/workers/gmail-renew-watch.js +95 -0
  249. package/dist/modules/communication_channels/workers/gmail-renew-watch.js.map +7 -0
  250. package/dist/modules/communication_channels/workers/inbound-processor.js +56 -0
  251. package/dist/modules/communication_channels/workers/inbound-processor.js.map +7 -0
  252. package/dist/modules/communication_channels/workers/outbound-delivery.js +85 -0
  253. package/dist/modules/communication_channels/workers/outbound-delivery.js.map +7 -0
  254. package/dist/modules/communication_channels/workers/poll-channel.js +240 -0
  255. package/dist/modules/communication_channels/workers/poll-channel.js.map +7 -0
  256. package/dist/modules/communication_channels/workers/poll-tick.js +132 -0
  257. package/dist/modules/communication_channels/workers/poll-tick.js.map +7 -0
  258. package/dist/modules/communication_channels/workers/reaction-processor.js +192 -0
  259. package/dist/modules/communication_channels/workers/reaction-processor.js.map +7 -0
  260. package/dist/modules/customers/acl.js +18 -0
  261. package/dist/modules/customers/acl.js.map +2 -2
  262. package/dist/modules/customers/api/activities/route.js +9 -0
  263. package/dist/modules/customers/api/activities/route.js.map +2 -2
  264. package/dist/modules/customers/api/companies/[id]/route.js +18 -7
  265. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  266. package/dist/modules/customers/api/interactions/[id]/visibility/route.js +151 -0
  267. package/dist/modules/customers/api/interactions/[id]/visibility/route.js.map +7 -0
  268. package/dist/modules/customers/api/interactions/counts/route.js +6 -0
  269. package/dist/modules/customers/api/interactions/counts/route.js.map +2 -2
  270. package/dist/modules/customers/api/interactions/route.js +26 -7
  271. package/dist/modules/customers/api/interactions/route.js.map +2 -2
  272. package/dist/modules/customers/api/people/[id]/email-threads/route.js +82 -0
  273. package/dist/modules/customers/api/people/[id]/email-threads/route.js.map +7 -0
  274. package/dist/modules/customers/api/people/[id]/emails/route.js +157 -0
  275. package/dist/modules/customers/api/people/[id]/emails/route.js.map +7 -0
  276. package/dist/modules/customers/api/people/[id]/route.js +12 -4
  277. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  278. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +10 -0
  279. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  280. package/dist/modules/customers/commands/deals.js +46 -5
  281. package/dist/modules/customers/commands/deals.js.map +2 -2
  282. package/dist/modules/customers/commands/interactions.js +16 -0
  283. package/dist/modules/customers/commands/interactions.js.map +2 -2
  284. package/dist/modules/customers/components/detail/ActivityCard.js +32 -0
  285. package/dist/modules/customers/components/detail/ActivityCard.js.map +2 -2
  286. package/dist/modules/customers/components/detail/ComposeEmailDialog.js +242 -0
  287. package/dist/modules/customers/components/detail/ComposeEmailDialog.js.map +7 -0
  288. package/dist/modules/customers/components/detail/DealForm.js +2 -1
  289. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  290. package/dist/modules/customers/components/detail/DealsSection.js +10 -0
  291. package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
  292. package/dist/modules/customers/components/detail/EmailCardActions.js +179 -0
  293. package/dist/modules/customers/components/detail/EmailCardActions.js.map +7 -0
  294. package/dist/modules/customers/components/detail/EmailReplyForwardActions.js +52 -0
  295. package/dist/modules/customers/components/detail/EmailReplyForwardActions.js.map +7 -0
  296. package/dist/modules/customers/components/detail/PersonDetailTabs.js +7 -1
  297. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  298. package/dist/modules/customers/components/detail/PersonEmailThreadsTab.js +366 -0
  299. package/dist/modules/customers/components/detail/PersonEmailThreadsTab.js.map +7 -0
  300. package/dist/modules/customers/data/enrichers.js +133 -2
  301. package/dist/modules/customers/data/enrichers.js.map +2 -2
  302. package/dist/modules/customers/data/entities.js +18 -0
  303. package/dist/modules/customers/data/entities.js.map +2 -2
  304. package/dist/modules/customers/data/extensions.js +16 -0
  305. package/dist/modules/customers/data/extensions.js.map +7 -0
  306. package/dist/modules/customers/encryption.js +11 -0
  307. package/dist/modules/customers/encryption.js.map +2 -2
  308. package/dist/modules/customers/events.js +4 -1
  309. package/dist/modules/customers/events.js.map +2 -2
  310. package/dist/modules/customers/lib/findPeopleByAddresses.js +64 -0
  311. package/dist/modules/customers/lib/findPeopleByAddresses.js.map +7 -0
  312. package/dist/modules/customers/lib/kysely.js.map +2 -2
  313. package/dist/modules/customers/lib/link-channel-message-handler.js +303 -0
  314. package/dist/modules/customers/lib/link-channel-message-handler.js.map +7 -0
  315. package/dist/modules/customers/lib/personEmailThreads.js +205 -0
  316. package/dist/modules/customers/lib/personEmailThreads.js.map +7 -0
  317. package/dist/modules/customers/lib/visibilityFilter.js +51 -0
  318. package/dist/modules/customers/lib/visibilityFilter.js.map +7 -0
  319. package/dist/modules/customers/migrations/Migration20260527012240_customers.js +20 -0
  320. package/dist/modules/customers/migrations/Migration20260527012240_customers.js.map +7 -0
  321. package/dist/modules/customers/setup.js +2 -1
  322. package/dist/modules/customers/setup.js.map +2 -2
  323. package/dist/modules/customers/subscribers/link-channel-message-received.js +12 -0
  324. package/dist/modules/customers/subscribers/link-channel-message-received.js.map +7 -0
  325. package/dist/modules/customers/subscribers/link-channel-message-sent.js +12 -0
  326. package/dist/modules/customers/subscribers/link-channel-message-sent.js.map +7 -0
  327. package/dist/modules/integrations/data/entities.js +8 -1
  328. package/dist/modules/integrations/data/entities.js.map +2 -2
  329. package/dist/modules/integrations/lib/credentials-service.js +29 -14
  330. package/dist/modules/integrations/lib/credentials-service.js.map +2 -2
  331. package/dist/modules/integrations/migrations/Migration20260526154136_integrations.js +15 -0
  332. package/dist/modules/integrations/migrations/Migration20260526154136_integrations.js.map +7 -0
  333. package/dist/modules/messages/commands/messages.js +70 -8
  334. package/dist/modules/messages/commands/messages.js.map +2 -2
  335. package/dist/modules/messages/components/ComposeMessagePageClient.js +24 -13
  336. package/dist/modules/messages/components/ComposeMessagePageClient.js.map +2 -2
  337. package/dist/modules/messages/components/MessageDetailPageClient.js +39 -2
  338. package/dist/modules/messages/components/MessageDetailPageClient.js.map +2 -2
  339. package/dist/modules/messages/components/MessagesInboxPageClient.js +1 -0
  340. package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
  341. package/dist/modules/messages/data/entities.js +8 -1
  342. package/dist/modules/messages/data/entities.js.map +2 -2
  343. package/dist/modules/messages/migrations/Migration20260531130000.js +15 -0
  344. package/dist/modules/messages/migrations/Migration20260531130000.js.map +7 -0
  345. package/dist/modules/messages/widgets/injection-table.js +7 -0
  346. package/dist/modules/messages/widgets/injection-table.js.map +7 -0
  347. package/generated/entities/channel_ingest_dead_letter/index.ts +11 -0
  348. package/generated/entities/channel_thread_mapping/index.ts +11 -0
  349. package/generated/entities/channel_thread_token/index.ts +7 -0
  350. package/generated/entities/communication_channel/index.ts +20 -0
  351. package/generated/entities/customer_interaction/index.ts +2 -0
  352. package/generated/entities/external_conversation/index.ts +11 -0
  353. package/generated/entities/external_message/index.ts +11 -0
  354. package/generated/entities/integration_credentials/index.ts +1 -0
  355. package/generated/entities/message/index.ts +1 -0
  356. package/generated/entities/message_channel_link/index.ts +15 -0
  357. package/generated/entities/message_reaction/index.ts +11 -0
  358. package/generated/entities.ids.generated.ts +11 -0
  359. package/generated/entity-fields-registry.ts +117 -0
  360. package/package.json +9 -7
  361. package/src/helpers/integration/authFixtures.ts +4 -1
  362. package/src/helpers/integration/communicationChannelsFixtures.ts +124 -0
  363. package/src/modules/communication_channels/acl.ts +43 -0
  364. package/src/modules/communication_channels/api/delete/channels/[id]/route.ts +163 -0
  365. package/src/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.ts +143 -0
  366. package/src/modules/communication_channels/api/get/channels/[id]/health/route.ts +173 -0
  367. package/src/modules/communication_channels/api/get/channels/[id]/route.ts +111 -0
  368. package/src/modules/communication_channels/api/get/channels/route.ts +109 -0
  369. package/src/modules/communication_channels/api/get/me/channels/route.ts +100 -0
  370. package/src/modules/communication_channels/api/get/oauth/[provider]/callback/route.ts +355 -0
  371. package/src/modules/communication_channels/api/post/channels/[id]/import-history/route.ts +206 -0
  372. package/src/modules/communication_channels/api/post/channels/[id]/poll-now/route.ts +174 -0
  373. package/src/modules/communication_channels/api/post/channels/[id]/push/register/route.ts +158 -0
  374. package/src/modules/communication_channels/api/post/channels/[id]/set-primary/route.ts +114 -0
  375. package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +241 -0
  376. package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +134 -0
  377. package/src/modules/communication_channels/api/post/messages/[messageId]/reactions/route.ts +143 -0
  378. package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +192 -0
  379. package/src/modules/communication_channels/api/post/send-as-user/route.ts +125 -0
  380. package/src/modules/communication_channels/api/post/test-seed/route.ts +267 -0
  381. package/src/modules/communication_channels/api/post/webhook/[provider]/route.ts +227 -0
  382. package/src/modules/communication_channels/api/post/webhooks/gmail/route.ts +161 -0
  383. package/src/modules/communication_channels/api/put/threads/[threadId]/assign/route.ts +132 -0
  384. package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.ts +34 -0
  385. package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.tsx +250 -0
  386. package/src/modules/communication_channels/backend/communication_channels/channels/page.meta.ts +36 -0
  387. package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +137 -0
  388. package/src/modules/communication_channels/backend/profile/communication-channels/page.meta.ts +36 -0
  389. package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +907 -0
  390. package/src/modules/communication_channels/commands/connect-credential-channel.ts +243 -0
  391. package/src/modules/communication_channels/commands/delete-channel.ts +193 -0
  392. package/src/modules/communication_channels/commands/deliver-outbound-message.ts +579 -0
  393. package/src/modules/communication_channels/commands/disconnect-channel.ts +241 -0
  394. package/src/modules/communication_channels/commands/ingest-inbound-message.ts +602 -0
  395. package/src/modules/communication_channels/commands/interceptors.ts +104 -0
  396. package/src/modules/communication_channels/commands/process-inbound-reaction.ts +265 -0
  397. package/src/modules/communication_channels/commands/push-register.ts +203 -0
  398. package/src/modules/communication_channels/commands/push-renew.ts +49 -0
  399. package/src/modules/communication_channels/commands/push-unregister.ts +168 -0
  400. package/src/modules/communication_channels/commands/queue-import-history.ts +180 -0
  401. package/src/modules/communication_channels/commands/reassign-conversation.ts +273 -0
  402. package/src/modules/communication_channels/commands/set-primary-channel.ts +154 -0
  403. package/src/modules/communication_channels/commands/toggle-outbound-reaction.ts +347 -0
  404. package/src/modules/communication_channels/data/enrichers.ts +413 -0
  405. package/src/modules/communication_channels/data/entities.ts +546 -0
  406. package/src/modules/communication_channels/data/extensions.ts +76 -0
  407. package/src/modules/communication_channels/data/validators.ts +138 -0
  408. package/src/modules/communication_channels/di.ts +40 -0
  409. package/src/modules/communication_channels/encryption.ts +44 -0
  410. package/src/modules/communication_channels/events.ts +122 -0
  411. package/src/modules/communication_channels/i18n/de.json +138 -0
  412. package/src/modules/communication_channels/i18n/en.json +138 -0
  413. package/src/modules/communication_channels/i18n/es.json +138 -0
  414. package/src/modules/communication_channels/i18n/pl.json +138 -0
  415. package/src/modules/communication_channels/index.ts +19 -0
  416. package/src/modules/communication_channels/lib/access-control.ts +110 -0
  417. package/src/modules/communication_channels/lib/adapter-compat.ts +57 -0
  418. package/src/modules/communication_channels/lib/adapter-registry-singleton.ts +35 -0
  419. package/src/modules/communication_channels/lib/adapter.ts +605 -0
  420. package/src/modules/communication_channels/lib/connect-channel.ts +163 -0
  421. package/src/modules/communication_channels/lib/contact-resolver.ts +162 -0
  422. package/src/modules/communication_channels/lib/credential-refresh.ts +197 -0
  423. package/src/modules/communication_channels/lib/dead-letter.ts +87 -0
  424. package/src/modules/communication_channels/lib/email-capabilities.ts +60 -0
  425. package/src/modules/communication_channels/lib/email-contact.ts +17 -0
  426. package/src/modules/communication_channels/lib/email-mime.ts +425 -0
  427. package/src/modules/communication_channels/lib/error-classification.ts +144 -0
  428. package/src/modules/communication_channels/lib/gmail-pubsub-jwt.ts +278 -0
  429. package/src/modules/communication_channels/lib/mutation-guards.ts +215 -0
  430. package/src/modules/communication_channels/lib/oauth-client-config.ts +79 -0
  431. package/src/modules/communication_channels/lib/oauth-state.ts +228 -0
  432. package/src/modules/communication_channels/lib/oauth-token.ts +81 -0
  433. package/src/modules/communication_channels/lib/pg-errors.ts +12 -0
  434. package/src/modules/communication_channels/lib/provider-health.ts +47 -0
  435. package/src/modules/communication_channels/lib/push-state.ts +38 -0
  436. package/src/modules/communication_channels/lib/queue.ts +66 -0
  437. package/src/modules/communication_channels/lib/reaction-processor-types.ts +51 -0
  438. package/src/modules/communication_channels/lib/reaction-semantics.ts +48 -0
  439. package/src/modules/communication_channels/lib/registry.ts +99 -0
  440. package/src/modules/communication_channels/lib/route-mutation-guard.ts +68 -0
  441. package/src/modules/communication_channels/lib/sanitize-channel-html.ts +129 -0
  442. package/src/modules/communication_channels/lib/send-as-user.ts +284 -0
  443. package/src/modules/communication_channels/lib/system-user.ts +74 -0
  444. package/src/modules/communication_channels/lib/test-seed.ts +140 -0
  445. package/src/modules/communication_channels/lib/thread-matcher.ts +430 -0
  446. package/src/modules/communication_channels/lib/thread-token.ts +355 -0
  447. package/src/modules/communication_channels/lib/use-connect-channel.ts +73 -0
  448. package/src/modules/communication_channels/migrations/.snapshot-open-mercato.json +2142 -0
  449. package/src/modules/communication_channels/migrations/Migration20260526134719_communication_channels.ts +55 -0
  450. package/src/modules/communication_channels/migrations/Migration20260527195446_communication_channels.ts +20 -0
  451. package/src/modules/communication_channels/migrations/Migration20260529231848_communication_channels.ts +13 -0
  452. package/src/modules/communication_channels/migrations/Migration20260531120000_communication_channels.ts +24 -0
  453. package/src/modules/communication_channels/notifications.client.ts +50 -0
  454. package/src/modules/communication_channels/notifications.handlers.ts +86 -0
  455. package/src/modules/communication_channels/notifications.ts +52 -0
  456. package/src/modules/communication_channels/setup.ts +158 -0
  457. package/src/modules/communication_channels/subscribers/channel-requires-reauth-notification.ts +118 -0
  458. package/src/modules/communication_channels/subscribers/outbound-bridge.ts +175 -0
  459. package/src/modules/communication_channels/subscribers/user-deleted-cascade.ts +100 -0
  460. package/src/modules/communication_channels/widgets/components.ts +36 -0
  461. package/src/modules/communication_channels/widgets/injection/channel-badge/widget.client.tsx +38 -0
  462. package/src/modules/communication_channels/widgets/injection/channel-badge/widget.ts +51 -0
  463. package/src/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.tsx +278 -0
  464. package/src/modules/communication_channels/widgets/injection/channel-info-panel/widget.ts +24 -0
  465. package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.tsx +63 -0
  466. package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.ts +29 -0
  467. package/src/modules/communication_channels/widgets/injection/profile-channels-menu/widget.ts +34 -0
  468. package/src/modules/communication_channels/widgets/injection/reaction-bar/widget.client.tsx +177 -0
  469. package/src/modules/communication_channels/widgets/injection/reaction-bar/widget.ts +26 -0
  470. package/src/modules/communication_channels/widgets/injection-table.ts +47 -0
  471. package/src/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.tsx +48 -0
  472. package/src/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.tsx +45 -0
  473. package/src/modules/communication_channels/widgets/notifications/index.ts +2 -0
  474. package/src/modules/communication_channels/workers/channel-import-history.ts +252 -0
  475. package/src/modules/communication_channels/workers/gmail-history-sync.ts +223 -0
  476. package/src/modules/communication_channels/workers/gmail-renew-watch.ts +141 -0
  477. package/src/modules/communication_channels/workers/inbound-processor.ts +114 -0
  478. package/src/modules/communication_channels/workers/outbound-delivery.ts +155 -0
  479. package/src/modules/communication_channels/workers/poll-channel.ts +391 -0
  480. package/src/modules/communication_channels/workers/poll-tick.ts +210 -0
  481. package/src/modules/communication_channels/workers/reaction-processor.ts +264 -0
  482. package/src/modules/customers/acl.ts +18 -0
  483. package/src/modules/customers/api/activities/route.ts +13 -0
  484. package/src/modules/customers/api/companies/[id]/route.ts +21 -1
  485. package/src/modules/customers/api/interactions/[id]/visibility/route.ts +179 -0
  486. package/src/modules/customers/api/interactions/counts/route.ts +10 -0
  487. package/src/modules/customers/api/interactions/route.ts +51 -5
  488. package/src/modules/customers/api/people/[id]/email-threads/route.ts +92 -0
  489. package/src/modules/customers/api/people/[id]/emails/route.ts +184 -0
  490. package/src/modules/customers/api/people/[id]/route.ts +17 -2
  491. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +11 -1
  492. package/src/modules/customers/commands/deals.ts +65 -6
  493. package/src/modules/customers/commands/interactions.ts +30 -0
  494. package/src/modules/customers/components/detail/ActivityCard.tsx +48 -0
  495. package/src/modules/customers/components/detail/ComposeEmailDialog.tsx +329 -0
  496. package/src/modules/customers/components/detail/DealForm.tsx +2 -1
  497. package/src/modules/customers/components/detail/DealsSection.tsx +26 -0
  498. package/src/modules/customers/components/detail/EmailCardActions.tsx +258 -0
  499. package/src/modules/customers/components/detail/EmailReplyForwardActions.tsx +53 -0
  500. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +8 -1
  501. package/src/modules/customers/components/detail/PersonEmailThreadsTab.tsx +448 -0
  502. package/src/modules/customers/data/enrichers.ts +252 -1
  503. package/src/modules/customers/data/entities.ts +46 -1
  504. package/src/modules/customers/data/extensions.ts +26 -0
  505. package/src/modules/customers/encryption.ts +11 -0
  506. package/src/modules/customers/events.ts +4 -0
  507. package/src/modules/customers/i18n/de.json +41 -0
  508. package/src/modules/customers/i18n/en.json +41 -0
  509. package/src/modules/customers/i18n/es.json +41 -0
  510. package/src/modules/customers/i18n/pl.json +41 -0
  511. package/src/modules/customers/lib/findPeopleByAddresses.ts +107 -0
  512. package/src/modules/customers/lib/kysely.ts +16 -0
  513. package/src/modules/customers/lib/link-channel-message-handler.ts +571 -0
  514. package/src/modules/customers/lib/personEmailThreads.ts +325 -0
  515. package/src/modules/customers/lib/visibilityFilter.ts +152 -0
  516. package/src/modules/customers/migrations/.snapshot-open-mercato.json +61 -0
  517. package/src/modules/customers/migrations/Migration20260527012240_customers.ts +23 -0
  518. package/src/modules/customers/setup.ts +1 -0
  519. package/src/modules/customers/subscribers/link-channel-message-received.ts +21 -0
  520. package/src/modules/customers/subscribers/link-channel-message-sent.ts +21 -0
  521. package/src/modules/integrations/AGENTS.md +9 -0
  522. package/src/modules/integrations/data/entities.ts +21 -1
  523. package/src/modules/integrations/lib/credentials-service.ts +49 -13
  524. package/src/modules/integrations/migrations/.snapshot-open-mercato.json +26 -1
  525. package/src/modules/integrations/migrations/Migration20260526154136_integrations.ts +15 -0
  526. package/src/modules/messages/commands/messages.ts +101 -8
  527. package/src/modules/messages/components/ComposeMessagePageClient.tsx +17 -0
  528. package/src/modules/messages/components/MessageDetailPageClient.tsx +43 -0
  529. package/src/modules/messages/components/MessagesInboxPageClient.tsx +4 -0
  530. package/src/modules/messages/data/entities.ts +11 -0
  531. package/src/modules/messages/migrations/.snapshot-open-mercato.json +18 -0
  532. package/src/modules/messages/migrations/Migration20260531130000.ts +15 -0
  533. package/src/modules/messages/widgets/injection-table.ts +29 -0
@@ -0,0 +1,260 @@
1
+ import { z } from "zod";
2
+ import { registerCommand } from "@open-mercato/shared/lib/commands";
3
+ import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
+ import { emitCommunicationChannelsEvent } from "../events.js";
5
+ import { Message } from "../../messages/data/entities.js";
6
+ import {
7
+ CommunicationChannel,
8
+ ChannelThreadMapping,
9
+ MessageChannelLink,
10
+ MessageReaction
11
+ } from "../data/entities.js";
12
+ import { COMMUNICATION_CHANNELS_QUEUES, getCommunicationChannelsQueue } from "../lib/queue.js";
13
+ import {
14
+ allowsMultipleReactionsPerUser
15
+ } from "../lib/reaction-semantics.js";
16
+ import { isUniqueViolation } from "../lib/pg-errors.js";
17
+ const toggleOutboundReactionSchema = z.object({
18
+ messageId: z.string().uuid(),
19
+ emoji: z.string().min(1).max(64),
20
+ action: z.enum(["add", "remove"]),
21
+ /** Reaction id (required for remove only). */
22
+ reactionId: z.string().uuid().optional(),
23
+ reactedByUserId: z.string().uuid(),
24
+ scope: z.object({
25
+ tenantId: z.string().uuid(),
26
+ organizationId: z.string().uuid().nullable()
27
+ })
28
+ });
29
+ const COMMUNICATION_CHANNELS_TOGGLE_OUTBOUND_REACTION_COMMAND_ID = "communication_channels.reaction.toggle_outbound";
30
+ const toggleOutboundReactionCommand = {
31
+ id: COMMUNICATION_CHANNELS_TOGGLE_OUTBOUND_REACTION_COMMAND_ID,
32
+ async execute(rawInput, ctx) {
33
+ const input = toggleOutboundReactionSchema.parse(rawInput);
34
+ const em = ctx.container.resolve("em").fork();
35
+ const dscope = {
36
+ tenantId: input.scope.tenantId,
37
+ organizationId: input.scope.organizationId ?? null
38
+ };
39
+ const message = await findOneWithDecryption(
40
+ em,
41
+ Message,
42
+ {
43
+ id: input.messageId,
44
+ tenantId: input.scope.tenantId,
45
+ organizationId: input.scope.organizationId ?? null,
46
+ deletedAt: null
47
+ },
48
+ void 0,
49
+ dscope
50
+ );
51
+ if (!message) {
52
+ return { status: "no_channel_link", reason: "message not found" };
53
+ }
54
+ const channelLink = await findOneWithDecryption(
55
+ em,
56
+ MessageChannelLink,
57
+ {
58
+ messageId: message.id,
59
+ tenantId: input.scope.tenantId,
60
+ organizationId: input.scope.organizationId ?? null
61
+ },
62
+ void 0,
63
+ dscope
64
+ );
65
+ if (!channelLink) {
66
+ return { status: "no_channel_link", reason: "message is not channel-linked" };
67
+ }
68
+ const mapping = await findOneWithDecryption(
69
+ em,
70
+ ChannelThreadMapping,
71
+ {
72
+ messageThreadId: message.threadId ?? message.id,
73
+ tenantId: input.scope.tenantId,
74
+ organizationId: input.scope.organizationId ?? null
75
+ },
76
+ void 0,
77
+ dscope
78
+ );
79
+ if (!mapping) {
80
+ return { status: "no_channel_link", reason: "no thread mapping for channel resolution" };
81
+ }
82
+ const resolvedChannel = await findOneWithDecryption(
83
+ em,
84
+ CommunicationChannel,
85
+ {
86
+ id: mapping.channelId,
87
+ tenantId: input.scope.tenantId,
88
+ organizationId: input.scope.organizationId ?? null,
89
+ deletedAt: null
90
+ },
91
+ void 0,
92
+ dscope
93
+ );
94
+ if (!resolvedChannel) {
95
+ return { status: "no_channel_link", reason: "channel not resolved" };
96
+ }
97
+ if (resolvedChannel.userId != null && resolvedChannel.userId !== input.reactedByUserId) {
98
+ return { status: "not_owner", reason: "channel is owned by another user" };
99
+ }
100
+ const capabilities = resolvedChannel.capabilities ?? null;
101
+ if (input.action === "add") {
102
+ let replaced = 0;
103
+ if (!allowsMultipleReactionsPerUser(capabilities)) {
104
+ const prior = await findWithDecryption(
105
+ em,
106
+ MessageReaction,
107
+ {
108
+ messageId: message.id,
109
+ reactedByUserId: input.reactedByUserId,
110
+ tenantId: input.scope.tenantId,
111
+ organizationId: input.scope.organizationId ?? null
112
+ },
113
+ void 0,
114
+ dscope
115
+ );
116
+ replaced = prior.length;
117
+ for (const row of prior) em.remove(row);
118
+ if (replaced > 0) await em.flush();
119
+ }
120
+ let reaction2;
121
+ try {
122
+ reaction2 = em.create(MessageReaction, {
123
+ messageId: message.id,
124
+ emoji: input.emoji,
125
+ reactedByUserId: input.reactedByUserId,
126
+ reactedByExternalId: null,
127
+ providerKey: channelLink.providerKey,
128
+ tenantId: input.scope.tenantId,
129
+ organizationId: input.scope.organizationId ?? null
130
+ });
131
+ em.persist(reaction2);
132
+ await em.flush();
133
+ } catch (err) {
134
+ if (isUniqueViolation(err)) {
135
+ return { status: "noop", reason: "duplicate reaction from same user with same emoji" };
136
+ }
137
+ throw err;
138
+ }
139
+ let enqueued2 = false;
140
+ if (typeof resolvedChannel.id === "string") {
141
+ const job = {
142
+ kind: "outbound_send",
143
+ providerKey: channelLink.providerKey,
144
+ channelId: resolvedChannel.id,
145
+ messageId: message.id,
146
+ reactionId: reaction2.id,
147
+ emoji: input.emoji,
148
+ conversationId: channelLink.channelMetadata?.["thread_id"],
149
+ scope: {
150
+ tenantId: input.scope.tenantId,
151
+ organizationId: input.scope.organizationId
152
+ },
153
+ attempt: 1
154
+ };
155
+ const queue = getCommunicationChannelsQueue(COMMUNICATION_CHANNELS_QUEUES.reactions);
156
+ await queue.enqueue(job);
157
+ enqueued2 = true;
158
+ }
159
+ await emitCommunicationChannelsEvent(
160
+ "communication_channels.reaction.added",
161
+ {
162
+ reactionId: reaction2.id,
163
+ messageId: message.id,
164
+ channelLinkId: channelLink.id,
165
+ channelId: resolvedChannel.id,
166
+ providerKey: channelLink.providerKey,
167
+ channelType: channelLink.channelType,
168
+ emoji: input.emoji,
169
+ reactedByUserId: input.reactedByUserId,
170
+ allowsMultiplePerUser: allowsMultipleReactionsPerUser(capabilities),
171
+ tenantId: input.scope.tenantId,
172
+ organizationId: input.scope.organizationId ?? null
173
+ },
174
+ { persistent: true }
175
+ );
176
+ return {
177
+ status: "added",
178
+ reactionId: reaction2.id,
179
+ messageId: message.id,
180
+ emoji: input.emoji,
181
+ enqueued: enqueued2,
182
+ replaced
183
+ };
184
+ }
185
+ if (!input.reactionId) {
186
+ return { status: "noop", reason: "reactionId required for remove" };
187
+ }
188
+ const reaction = await findOneWithDecryption(
189
+ em,
190
+ MessageReaction,
191
+ {
192
+ id: input.reactionId,
193
+ messageId: message.id,
194
+ tenantId: input.scope.tenantId,
195
+ organizationId: input.scope.organizationId ?? null
196
+ },
197
+ void 0,
198
+ dscope
199
+ );
200
+ if (!reaction) {
201
+ return { status: "noop", reason: "reaction not found" };
202
+ }
203
+ if (reaction.reactedByUserId !== input.reactedByUserId) {
204
+ return { status: "noop", reason: "reaction not owned by current user" };
205
+ }
206
+ const externalReactionId = reaction.externalReactionId ?? null;
207
+ em.remove(reaction);
208
+ await em.flush();
209
+ let enqueued = false;
210
+ if (typeof resolvedChannel.id === "string") {
211
+ const job = {
212
+ kind: "outbound_remove",
213
+ providerKey: channelLink.providerKey,
214
+ channelId: resolvedChannel.id,
215
+ messageId: message.id,
216
+ emoji: input.emoji,
217
+ externalReactionId,
218
+ conversationId: channelLink.channelMetadata?.["thread_id"],
219
+ scope: {
220
+ tenantId: input.scope.tenantId,
221
+ organizationId: input.scope.organizationId
222
+ },
223
+ attempt: 1
224
+ };
225
+ const queue = getCommunicationChannelsQueue(COMMUNICATION_CHANNELS_QUEUES.reactions);
226
+ await queue.enqueue(job);
227
+ enqueued = true;
228
+ }
229
+ await emitCommunicationChannelsEvent(
230
+ "communication_channels.reaction.removed",
231
+ {
232
+ messageId: message.id,
233
+ channelLinkId: channelLink.id,
234
+ channelId: resolvedChannel.id,
235
+ providerKey: channelLink.providerKey,
236
+ channelType: channelLink.channelType,
237
+ emoji: input.emoji,
238
+ reactedByUserId: input.reactedByUserId,
239
+ deletedCount: 1,
240
+ tenantId: input.scope.tenantId,
241
+ organizationId: input.scope.organizationId ?? null
242
+ },
243
+ { persistent: true }
244
+ );
245
+ return {
246
+ status: "removed",
247
+ messageId: message.id,
248
+ emoji: input.emoji,
249
+ enqueued,
250
+ deleted: 1
251
+ };
252
+ }
253
+ };
254
+ registerCommand(toggleOutboundReactionCommand);
255
+ var toggle_outbound_reaction_default = toggleOutboundReactionCommand;
256
+ export {
257
+ COMMUNICATION_CHANNELS_TOGGLE_OUTBOUND_REACTION_COMMAND_ID,
258
+ toggle_outbound_reaction_default as default
259
+ };
260
+ //# sourceMappingURL=toggle-outbound-reaction.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/communication_channels/commands/toggle-outbound-reaction.ts"],
4
+ "sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { emitCommunicationChannelsEvent } from '../events'\nimport { Message } from '../../messages/data/entities'\nimport {\n CommunicationChannel,\n ChannelThreadMapping,\n MessageChannelLink,\n MessageReaction,\n} from '../data/entities'\nimport { COMMUNICATION_CHANNELS_QUEUES, getCommunicationChannelsQueue } from '../lib/queue'\nimport type { ReactionProcessorPayload } from '../lib/reaction-processor-types'\nimport {\n allowsMultipleReactionsPerUser,\n} from '../lib/reaction-semantics'\nimport type { ChannelCapabilities } from '../lib/adapter'\nimport { isUniqueViolation } from '../lib/pg-errors'\n\nconst toggleOutboundReactionSchema = z.object({\n messageId: z.string().uuid(),\n emoji: z.string().min(1).max(64),\n action: z.enum(['add', 'remove']),\n /** Reaction id (required for remove only). */\n reactionId: z.string().uuid().optional(),\n reactedByUserId: z.string().uuid(),\n scope: z.object({\n tenantId: z.string().uuid(),\n organizationId: z.string().uuid().nullable(),\n }),\n})\n\nexport type ToggleOutboundReactionInput = z.infer<typeof toggleOutboundReactionSchema>\n\nexport type ToggleOutboundReactionResult =\n | { status: 'no_channel_link'; reason: string }\n | { status: 'not_owner'; reason: string }\n | {\n status: 'added'\n reactionId: string\n messageId: string\n emoji: string\n enqueued: boolean\n replaced: number\n }\n | {\n status: 'removed'\n messageId: string\n emoji: string\n enqueued: boolean\n deleted: number\n }\n | { status: 'noop'; reason: string }\n\nexport const COMMUNICATION_CHANNELS_TOGGLE_OUTBOUND_REACTION_COMMAND_ID =\n 'communication_channels.reaction.toggle_outbound'\n\n/**\n * Combined outbound add/remove command.\n *\n * For UX responsiveness, the local mutation (insert/delete `MessageReaction`)\n * happens synchronously and is what the API handler returns. The provider-side\n * effect (calling `adapter.sendReaction?` / `removeReaction?`) is enqueued to\n * the reactions queue and processed asynchronously by the reaction worker.\n *\n * For `add`:\n * - Validates the message is channel-linked (otherwise returns `no_channel_link`).\n * - Applies single-vs-multi semantics:\n * - WhatsApp-style (multiReactionPerUser=false): deletes prior reactions\n * from the same internal user on the same message, then inserts the new one.\n * - Slack-style (multiReactionPerUser=true): inserts; duplicates blocked\n * by the unique constraint.\n * - Enqueues an `outbound_send` job carrying the new reaction id.\n * - Emits `communication_channels.reaction.added` synchronously (optimistic).\n *\n * For `remove`:\n * - Looks up the `MessageReaction` row (validates ownership: reactedByUserId\n * must match).\n * - Deletes the row locally.\n * - Enqueues an `outbound_remove` job carrying the emoji + (if known) the\n * external reaction id.\n * - Emits `communication_channels.reaction.removed`.\n */\nconst toggleOutboundReactionCommand: CommandHandler<\n ToggleOutboundReactionInput,\n ToggleOutboundReactionResult\n> = {\n id: COMMUNICATION_CHANNELS_TOGGLE_OUTBOUND_REACTION_COMMAND_ID,\n async execute(rawInput, ctx) {\n const input = toggleOutboundReactionSchema.parse(rawInput) as ToggleOutboundReactionInput\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const dscope = {\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId ?? null,\n }\n\n // (a) Resolve the platform Message + channel link.\n const message = await findOneWithDecryption(\n em,\n Message,\n {\n id: input.messageId,\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId ?? null,\n deletedAt: null,\n },\n undefined,\n dscope,\n )\n if (!message) {\n return { status: 'no_channel_link', reason: 'message not found' }\n }\n const channelLink = await findOneWithDecryption(\n em,\n MessageChannelLink,\n {\n messageId: message.id,\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId ?? null,\n },\n undefined,\n dscope,\n )\n if (!channelLink) {\n return { status: 'no_channel_link', reason: 'message is not channel-linked' }\n }\n // Resolve the channel deterministically from the thread\u2192channel mapping.\n // We MUST NOT fall back to an arbitrary channel matching only\n // (tenant, org, providerKey): for a tenant with several same-provider\n // mailboxes owned by different users that would react from the wrong\n // user's account.\n const mapping = await findOneWithDecryption(\n em,\n ChannelThreadMapping,\n {\n messageThreadId: message.threadId ?? message.id,\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId ?? null,\n },\n undefined,\n dscope,\n )\n if (!mapping) {\n return { status: 'no_channel_link', reason: 'no thread mapping for channel resolution' }\n }\n const resolvedChannel = await findOneWithDecryption(\n em,\n CommunicationChannel,\n {\n id: mapping.channelId,\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId ?? null,\n deletedAt: null,\n },\n undefined,\n dscope,\n )\n if (!resolvedChannel) {\n return { status: 'no_channel_link', reason: 'channel not resolved' }\n }\n // Per-user ownership gate. The reaction is delivered to the provider using\n // the RESOLVED CHANNEL OWNER's credentials (workers/reaction-processor.ts),\n // so a non-owner reacting would post a reaction FROM someone else's connected\n // account (impersonation). Only the channel owner may react from a per-user\n // channel; tenant-wide channels (userId == null \u2014 shared WhatsApp/Slack) stay\n // reactable by any authorized caller. Mirrors set-primary-channel's not_owner\n // guard. Applies to both add and remove.\n if (resolvedChannel.userId != null && resolvedChannel.userId !== input.reactedByUserId) {\n return { status: 'not_owner', reason: 'channel is owned by another user' }\n }\n const capabilities = (resolvedChannel.capabilities as ChannelCapabilities | null) ?? null\n\n if (input.action === 'add') {\n let replaced = 0\n if (!allowsMultipleReactionsPerUser(capabilities)) {\n const prior = await findWithDecryption(\n em,\n MessageReaction,\n {\n messageId: message.id,\n reactedByUserId: input.reactedByUserId,\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId ?? null,\n },\n undefined,\n dscope,\n )\n replaced = prior.length\n for (const row of prior) em.remove(row)\n if (replaced > 0) await em.flush()\n }\n\n let reaction: MessageReaction\n try {\n reaction = em.create(MessageReaction, {\n messageId: message.id,\n emoji: input.emoji,\n reactedByUserId: input.reactedByUserId,\n reactedByExternalId: null,\n providerKey: channelLink.providerKey,\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId ?? null,\n })\n em.persist(reaction)\n await em.flush()\n } catch (err) {\n if (isUniqueViolation(err)) {\n return { status: 'noop', reason: 'duplicate reaction from same user with same emoji' }\n }\n throw err\n }\n\n // Enqueue outbound job.\n let enqueued = false\n if (typeof resolvedChannel.id === 'string') {\n const job: ReactionProcessorPayload = {\n kind: 'outbound_send',\n providerKey: channelLink.providerKey,\n channelId: resolvedChannel.id,\n messageId: message.id,\n reactionId: reaction.id,\n emoji: input.emoji,\n conversationId:\n (channelLink.channelMetadata as Record<string, unknown> | null)?.['thread_id'] as\n | string\n | undefined,\n scope: {\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId,\n },\n attempt: 1,\n }\n const queue = getCommunicationChannelsQueue(COMMUNICATION_CHANNELS_QUEUES.reactions)\n await queue.enqueue(job as unknown as Record<string, unknown>)\n enqueued = true\n }\n\n await emitCommunicationChannelsEvent(\n 'communication_channels.reaction.added',\n {\n reactionId: reaction.id,\n messageId: message.id,\n channelLinkId: channelLink.id,\n channelId: resolvedChannel.id,\n providerKey: channelLink.providerKey,\n channelType: channelLink.channelType,\n emoji: input.emoji,\n reactedByUserId: input.reactedByUserId,\n allowsMultiplePerUser: allowsMultipleReactionsPerUser(capabilities),\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId ?? null,\n },\n { persistent: true },\n )\n\n return {\n status: 'added',\n reactionId: reaction.id,\n messageId: message.id,\n emoji: input.emoji,\n enqueued,\n replaced,\n }\n }\n\n // input.action === 'remove'\n if (!input.reactionId) {\n return { status: 'noop', reason: 'reactionId required for remove' }\n }\n const reaction = await findOneWithDecryption(\n em,\n MessageReaction,\n {\n id: input.reactionId,\n messageId: message.id,\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId ?? null,\n },\n undefined,\n dscope,\n )\n if (!reaction) {\n return { status: 'noop', reason: 'reaction not found' }\n }\n if (reaction.reactedByUserId !== input.reactedByUserId) {\n return { status: 'noop', reason: 'reaction not owned by current user' }\n }\n const externalReactionId = reaction.externalReactionId ?? null\n em.remove(reaction)\n await em.flush()\n\n let enqueued = false\n if (typeof resolvedChannel.id === 'string') {\n const job: ReactionProcessorPayload = {\n kind: 'outbound_remove',\n providerKey: channelLink.providerKey,\n channelId: resolvedChannel.id,\n messageId: message.id,\n emoji: input.emoji,\n externalReactionId,\n conversationId:\n (channelLink.channelMetadata as Record<string, unknown> | null)?.['thread_id'] as\n | string\n | undefined,\n scope: {\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId,\n },\n attempt: 1,\n }\n const queue = getCommunicationChannelsQueue(COMMUNICATION_CHANNELS_QUEUES.reactions)\n await queue.enqueue(job as unknown as Record<string, unknown>)\n enqueued = true\n }\n\n await emitCommunicationChannelsEvent(\n 'communication_channels.reaction.removed',\n {\n messageId: message.id,\n channelLinkId: channelLink.id,\n channelId: resolvedChannel.id,\n providerKey: channelLink.providerKey,\n channelType: channelLink.channelType,\n emoji: input.emoji,\n reactedByUserId: input.reactedByUserId,\n deletedCount: 1,\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId ?? null,\n },\n { persistent: true },\n )\n\n return {\n status: 'removed',\n messageId: message.id,\n emoji: input.emoji,\n enqueued,\n deleted: 1,\n }\n },\n}\n\nregisterCommand(toggleOutboundReactionCommand)\n\nexport default toggleOutboundReactionCommand\n"],
5
+ "mappings": "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",
6
+ "names": ["reaction", "enqueued"]
7
+ }
@@ -0,0 +1,286 @@
1
+ import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
2
+ import {
3
+ CommunicationChannel,
4
+ ExternalConversation,
5
+ MessageChannelLink,
6
+ MessageReaction
7
+ } from "./entities.js";
8
+ async function resolveChannelsByMessageId(em, links, tenantId, organizationId) {
9
+ if (links.length === 0) return /* @__PURE__ */ new Map();
10
+ const conversationIds = Array.from(
11
+ new Set(
12
+ links.map((l) => l.externalConversationId).filter(
13
+ (id) => typeof id === "string" && id.length > 0
14
+ )
15
+ )
16
+ );
17
+ if (conversationIds.length === 0) return /* @__PURE__ */ new Map();
18
+ const dscope = { tenantId, organizationId };
19
+ const conversations = await findWithDecryption(
20
+ em,
21
+ ExternalConversation,
22
+ { id: { $in: conversationIds }, tenantId, organizationId },
23
+ void 0,
24
+ dscope
25
+ );
26
+ const channelIdByConversation = /* @__PURE__ */ new Map();
27
+ for (const conv of conversations) {
28
+ channelIdByConversation.set(conv.id, conv.channelId);
29
+ }
30
+ const channelIds = Array.from(new Set(Array.from(channelIdByConversation.values())));
31
+ if (channelIds.length === 0) return /* @__PURE__ */ new Map();
32
+ const channels = await findWithDecryption(
33
+ em,
34
+ CommunicationChannel,
35
+ { id: { $in: channelIds }, tenantId, organizationId, deletedAt: null },
36
+ void 0,
37
+ dscope
38
+ );
39
+ const channelsById = new Map(
40
+ channels.map((c) => [c.id, c])
41
+ );
42
+ const result = /* @__PURE__ */ new Map();
43
+ for (const link of links) {
44
+ const channelId = link.externalConversationId ? channelIdByConversation.get(link.externalConversationId) : void 0;
45
+ const channel = channelId ? channelsById.get(channelId) : void 0;
46
+ if (channel) result.set(link.messageId, channel);
47
+ }
48
+ return result;
49
+ }
50
+ function ctxEm(ctx) {
51
+ return ctx.em;
52
+ }
53
+ const messageChannelEnricher = {
54
+ id: "communication_channels.message-channel",
55
+ targetEntity: "messages.message",
56
+ features: ["communication_channels.view"],
57
+ priority: 30,
58
+ timeout: 1500,
59
+ fallback: { _channel: null },
60
+ critical: false,
61
+ async enrichOne(record, ctx) {
62
+ const [out] = await this.enrichMany([record], ctx);
63
+ return out;
64
+ },
65
+ async enrichMany(records, ctx) {
66
+ if (records.length === 0) return records;
67
+ const messageIds = records.map((r) => r.id);
68
+ const em = ctxEm(ctx);
69
+ const dscope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId ?? null };
70
+ const links = await findWithDecryption(
71
+ em,
72
+ MessageChannelLink,
73
+ {
74
+ messageId: { $in: messageIds },
75
+ tenantId: ctx.tenantId,
76
+ organizationId: ctx.organizationId ?? null
77
+ },
78
+ // Result set is already bounded by the `messageId $in messageIds` filter
79
+ // (the host list endpoint caps the page at 100), matching the other
80
+ // enrichers — no separate row limit needed.
81
+ void 0,
82
+ dscope
83
+ );
84
+ const channelByMessageId = await resolveChannelsByMessageId(
85
+ em,
86
+ links,
87
+ ctx.tenantId,
88
+ ctx.organizationId ?? null
89
+ );
90
+ const linksByMessage = /* @__PURE__ */ new Map();
91
+ for (const link of links) linksByMessage.set(link.messageId, link);
92
+ return records.map((r) => {
93
+ const link = linksByMessage.get(r.id);
94
+ if (!link) return { ...r, _channel: null };
95
+ const channel = channelByMessageId.get(r.id);
96
+ const enrichment = {
97
+ providerKey: link.providerKey,
98
+ channelType: link.channelType,
99
+ direction: link.direction,
100
+ deliveryStatus: link.deliveryStatus ?? null,
101
+ capabilities: channel?.capabilities ?? null
102
+ };
103
+ return { ...r, _channel: enrichment };
104
+ });
105
+ }
106
+ };
107
+ const messageChannelPayloadEnricher = {
108
+ id: "communication_channels.message-channel-payload",
109
+ targetEntity: "messages.message",
110
+ features: ["communication_channels.view"],
111
+ priority: 20,
112
+ timeout: 1500,
113
+ fallback: { _channelPayload: null },
114
+ critical: false,
115
+ async enrichOne(record, ctx) {
116
+ const [out] = await this.enrichMany([record], ctx);
117
+ return out;
118
+ },
119
+ async enrichMany(records, ctx) {
120
+ if (records.length === 0) return records;
121
+ const messageIds = records.map((r) => r.id);
122
+ const em = ctxEm(ctx);
123
+ const dscope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId ?? null };
124
+ const links = await findWithDecryption(
125
+ em,
126
+ MessageChannelLink,
127
+ {
128
+ messageId: { $in: messageIds },
129
+ tenantId: ctx.tenantId,
130
+ organizationId: ctx.organizationId ?? null
131
+ },
132
+ void 0,
133
+ dscope
134
+ );
135
+ const byMessage = /* @__PURE__ */ new Map();
136
+ for (const link of links) byMessage.set(link.messageId, link);
137
+ return records.map((r) => {
138
+ const link = byMessage.get(r.id);
139
+ if (!link) return { ...r, _channelPayload: null };
140
+ const enrichment = {
141
+ channelContentType: link.channelContentType ?? null,
142
+ channelPayload: link.channelPayload ?? null,
143
+ interactiveState: link.interactiveState ?? null,
144
+ channelMetadata: link.channelMetadata ?? null
145
+ };
146
+ return { ...r, _channelPayload: enrichment };
147
+ });
148
+ }
149
+ };
150
+ const messageReactionsEnricher = {
151
+ id: "communication_channels.message-reactions",
152
+ targetEntity: "messages.message",
153
+ features: ["communication_channels.view"],
154
+ priority: 25,
155
+ timeout: 1500,
156
+ fallback: { _reactions: [] },
157
+ critical: false,
158
+ async enrichOne(record, ctx) {
159
+ const [out] = await this.enrichMany([record], ctx);
160
+ return out;
161
+ },
162
+ async enrichMany(records, ctx) {
163
+ if (records.length === 0) return records;
164
+ const messageIds = records.map((r) => r.id);
165
+ const em = ctxEm(ctx);
166
+ const dscope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId ?? null };
167
+ const reactions = await findWithDecryption(
168
+ em,
169
+ MessageReaction,
170
+ {
171
+ messageId: { $in: messageIds },
172
+ tenantId: ctx.tenantId,
173
+ organizationId: ctx.organizationId ?? null
174
+ },
175
+ void 0,
176
+ dscope
177
+ );
178
+ const byMessage = /* @__PURE__ */ new Map();
179
+ for (const reaction of reactions) {
180
+ const existing = byMessage.get(reaction.messageId) ?? [];
181
+ existing.push(reaction);
182
+ byMessage.set(reaction.messageId, existing);
183
+ }
184
+ return records.map((r) => {
185
+ const rows = byMessage.get(r.id) ?? [];
186
+ const grouped = groupReactions(rows, ctx.userId);
187
+ return { ...r, _reactions: grouped };
188
+ });
189
+ }
190
+ };
191
+ function groupReactions(rows, currentUserId) {
192
+ const map = /* @__PURE__ */ new Map();
193
+ for (const row of rows) {
194
+ const key = row.emoji;
195
+ if (!map.has(key)) {
196
+ map.set(key, { emoji: key, count: 0, users: [], reactedByMe: false, myReactionId: null });
197
+ }
198
+ const group = map.get(key);
199
+ group.count += 1;
200
+ group.users.push({
201
+ userId: row.reactedByUserId ?? null,
202
+ externalId: row.reactedByExternalId ?? null,
203
+ displayName: row.reactedByDisplayName ?? null,
204
+ providerKey: row.providerKey ?? null
205
+ });
206
+ if (row.reactedByUserId && row.reactedByUserId === currentUserId) {
207
+ group.reactedByMe = true;
208
+ if (!group.myReactionId) group.myReactionId = row.id ?? null;
209
+ }
210
+ }
211
+ return Array.from(map.values()).sort((a, b) => b.count - a.count);
212
+ }
213
+ const conversationContactEnricher = {
214
+ id: "communication_channels.conversation-contact",
215
+ targetEntity: "messages.message",
216
+ features: ["communication_channels.view"],
217
+ priority: 15,
218
+ timeout: 2e3,
219
+ fallback: { _channelContact: null },
220
+ critical: false,
221
+ async enrichOne(record, ctx) {
222
+ const [out] = await this.enrichMany([record], ctx);
223
+ return out;
224
+ },
225
+ async enrichMany(records, ctx) {
226
+ if (records.length === 0) return records;
227
+ const messageIds = records.map((r) => r.id);
228
+ const em = ctxEm(ctx);
229
+ const dscope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId ?? null };
230
+ const links = await findWithDecryption(
231
+ em,
232
+ MessageChannelLink,
233
+ {
234
+ messageId: { $in: messageIds },
235
+ tenantId: ctx.tenantId,
236
+ organizationId: ctx.organizationId ?? null
237
+ },
238
+ void 0,
239
+ dscope
240
+ );
241
+ const conversationIds = Array.from(
242
+ new Set(links.map((l) => l.externalConversationId).filter(Boolean))
243
+ );
244
+ let conversationsById = /* @__PURE__ */ new Map();
245
+ if (conversationIds.length > 0) {
246
+ const conversations = await findWithDecryption(
247
+ em,
248
+ ExternalConversation,
249
+ {
250
+ id: { $in: conversationIds },
251
+ tenantId: ctx.tenantId,
252
+ organizationId: ctx.organizationId ?? null
253
+ },
254
+ void 0,
255
+ dscope
256
+ );
257
+ conversationsById = new Map(conversations.map((c) => [c.id, c]));
258
+ }
259
+ const linksByMessage = /* @__PURE__ */ new Map();
260
+ for (const link of links) linksByMessage.set(link.messageId, link);
261
+ return records.map((r) => {
262
+ const link = linksByMessage.get(r.id);
263
+ if (!link) return { ...r, _channelContact: null };
264
+ const conversation = conversationsById.get(link.externalConversationId);
265
+ if (!conversation) return { ...r, _channelContact: null };
266
+ const enrichment = {
267
+ contactPersonId: conversation.contactPersonId ?? null,
268
+ assignedUserId: conversation.assignedUserId ?? null,
269
+ subject: conversation.subject ?? null
270
+ };
271
+ return { ...r, _channelContact: enrichment };
272
+ });
273
+ }
274
+ };
275
+ const enrichers = [
276
+ messageChannelEnricher,
277
+ messageChannelPayloadEnricher,
278
+ messageReactionsEnricher,
279
+ conversationContactEnricher
280
+ ];
281
+ var enrichers_default = enrichers;
282
+ export {
283
+ enrichers_default as default,
284
+ enrichers
285
+ };
286
+ //# sourceMappingURL=enrichers.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/communication_channels/data/enrichers.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { EnricherContext, ResponseEnricher } from '@open-mercato/shared/lib/crud/response-enricher'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n CommunicationChannel,\n ExternalConversation,\n MessageChannelLink,\n MessageReaction,\n} from './entities'\n\n/**\n * Resolve channels by `id` (not `providerKey`) \u2014 multi-user channels share the\n * same `providerKey` (e.g. two users with Gmail), so a providerKey-keyed Map\n * collapses them and returns the wrong owner's capabilities snapshot. The hop\n * goes `MessageChannelLink \u2192 ExternalConversation \u2192 CommunicationChannel`.\n *\n * Returns a Map keyed by the platform Message id so enrichers can read\n * `channelByMessageId.get(record.id)` without further joins.\n */\nasync function resolveChannelsByMessageId(\n em: EntityManager,\n links: MessageChannelLink[],\n tenantId: string,\n organizationId: string | null,\n): Promise<Map<string, CommunicationChannel>> {\n if (links.length === 0) return new Map()\n const conversationIds = Array.from(\n new Set(\n links.map((l) => (l as { externalConversationId?: string }).externalConversationId).filter(\n (id): id is string => typeof id === 'string' && id.length > 0,\n ),\n ),\n )\n if (conversationIds.length === 0) return new Map()\n const dscope = { tenantId, organizationId }\n const conversations = await findWithDecryption(\n em,\n ExternalConversation,\n { id: { $in: conversationIds }, tenantId, organizationId },\n undefined,\n dscope,\n )\n const channelIdByConversation = new Map<string, string>()\n for (const conv of conversations) {\n channelIdByConversation.set(conv.id, conv.channelId)\n }\n const channelIds = Array.from(new Set(Array.from(channelIdByConversation.values())))\n if (channelIds.length === 0) return new Map()\n const channels = await findWithDecryption(\n em,\n CommunicationChannel,\n { id: { $in: channelIds }, tenantId, organizationId, deletedAt: null },\n undefined,\n dscope,\n )\n const channelsById = new Map<string, CommunicationChannel>(\n channels.map((c) => [c.id, c]),\n )\n const result = new Map<string, CommunicationChannel>()\n for (const link of links) {\n const channelId = link.externalConversationId\n ? channelIdByConversation.get(link.externalConversationId)\n : undefined\n const channel = channelId ? channelsById.get(channelId) : undefined\n if (channel) result.set(link.messageId, channel)\n }\n return result\n}\n\n/**\n * Response enrichers for the messages.message entity.\n *\n * The hub declares 4 enrichers; downstream hosts (Messages module's `/api/messages`\n * CRUD route + future provider routes) opt in via `makeCrudRoute({ enrichers: { entityId: 'messages.message' } })`.\n *\n * - `_channel` \u2192 channel metadata + capabilities snapshot\n * - `_channelPayload` \u2192 channel-native payload (Block Kit / interactive / email MIME / \u2026)\n * - `_reactions` \u2192 grouped emoji counts + users + reactedByMe\n * - `_channelContact` \u2192 CRM person preview (email + display name)\n *\n * Per `packages/shared/lib/crud/response-enricher` rules:\n * - `enrichMany` is implemented for every enricher (N+1 prevention).\n * - Enriched fields are namespaced with `_channel*` / `_reactions` prefixes.\n * - Enrichers are read-only; no writes via the EntityManager.\n * - Each enricher is feature-gated by `communication_channels.view`.\n */\n\ntype MessageRecord = Record<string, unknown> & {\n id: string\n threadId?: string | null\n}\n\ntype ResolvedCtx = EnricherContext & {\n em: EntityManager\n}\n\nfunction ctxEm(ctx: EnricherContext): EntityManager {\n return ctx.em as EntityManager\n}\n\n// \u2500\u2500 _channel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst messageChannelEnricher: ResponseEnricher<MessageRecord, { _channel?: ChannelEnrichment | null }> = {\n id: 'communication_channels.message-channel',\n targetEntity: 'messages.message',\n features: ['communication_channels.view'],\n priority: 30,\n timeout: 1500,\n fallback: { _channel: null },\n critical: false,\n\n async enrichOne(record, ctx) {\n const [out] = await this.enrichMany!([record], ctx)\n return out\n },\n\n async enrichMany(records, ctx) {\n if (records.length === 0) return records\n const messageIds = records.map((r) => r.id)\n const em = ctxEm(ctx)\n const dscope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId ?? null }\n const links = await findWithDecryption(\n em,\n MessageChannelLink,\n {\n messageId: { $in: messageIds },\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? null,\n },\n // Result set is already bounded by the `messageId $in messageIds` filter\n // (the host list endpoint caps the page at 100), matching the other\n // enrichers \u2014 no separate row limit needed.\n undefined,\n dscope,\n )\n\n // Resolve channels via the `link \u2192 ExternalConversation \u2192 CommunicationChannel`\n // path. Keys are platform Message ids \u2014 direct mapping per row.\n const channelByMessageId = await resolveChannelsByMessageId(\n em,\n links,\n ctx.tenantId as string,\n ctx.organizationId ?? null,\n )\n\n const linksByMessage = new Map<string, MessageChannelLink>()\n for (const link of links) linksByMessage.set(link.messageId, link)\n\n return records.map((r) => {\n const link = linksByMessage.get(r.id)\n if (!link) return { ...r, _channel: null }\n const channel = channelByMessageId.get(r.id)\n const enrichment: ChannelEnrichment = {\n providerKey: link.providerKey,\n channelType: link.channelType,\n direction: link.direction,\n deliveryStatus: link.deliveryStatus ?? null,\n capabilities: (channel?.capabilities as Record<string, unknown> | null) ?? null,\n }\n return { ...r, _channel: enrichment }\n })\n },\n}\n\nexport type ChannelEnrichment = {\n providerKey: string\n channelType: string\n direction: 'inbound' | 'outbound' | string\n deliveryStatus: string | null\n capabilities: Record<string, unknown> | null\n}\n\n// \u2500\u2500 _channelPayload \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst messageChannelPayloadEnricher: ResponseEnricher<\n MessageRecord,\n { _channelPayload?: ChannelPayloadEnrichment | null }\n> = {\n id: 'communication_channels.message-channel-payload',\n targetEntity: 'messages.message',\n features: ['communication_channels.view'],\n priority: 20,\n timeout: 1500,\n fallback: { _channelPayload: null },\n critical: false,\n\n async enrichOne(record, ctx) {\n const [out] = await this.enrichMany!([record], ctx)\n return out\n },\n\n async enrichMany(records, ctx) {\n if (records.length === 0) return records\n const messageIds = records.map((r) => r.id)\n const em = ctxEm(ctx)\n const dscope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId ?? null }\n const links = await findWithDecryption(\n em,\n MessageChannelLink,\n {\n messageId: { $in: messageIds },\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? null,\n },\n undefined,\n dscope,\n )\n const byMessage = new Map<string, MessageChannelLink>()\n for (const link of links) byMessage.set(link.messageId, link)\n\n return records.map((r) => {\n const link = byMessage.get(r.id)\n if (!link) return { ...r, _channelPayload: null }\n const enrichment: ChannelPayloadEnrichment = {\n channelContentType: link.channelContentType ?? null,\n channelPayload: link.channelPayload ?? null,\n interactiveState: link.interactiveState ?? null,\n channelMetadata: link.channelMetadata ?? null,\n }\n return { ...r, _channelPayload: enrichment }\n })\n },\n}\n\nexport type ChannelPayloadEnrichment = {\n channelContentType: string | null\n channelPayload: Record<string, unknown> | null\n interactiveState: Record<string, unknown> | null\n channelMetadata: Record<string, unknown> | null\n}\n\n// \u2500\u2500 _reactions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst messageReactionsEnricher: ResponseEnricher<\n MessageRecord,\n { _reactions?: ReactionGroup[] }\n> = {\n id: 'communication_channels.message-reactions',\n targetEntity: 'messages.message',\n features: ['communication_channels.view'],\n priority: 25,\n timeout: 1500,\n fallback: { _reactions: [] },\n critical: false,\n\n async enrichOne(record, ctx) {\n const [out] = await this.enrichMany!([record], ctx)\n return out\n },\n\n async enrichMany(records, ctx) {\n if (records.length === 0) return records\n const messageIds = records.map((r) => r.id)\n const em = ctxEm(ctx)\n const dscope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId ?? null }\n const reactions = await findWithDecryption(\n em,\n MessageReaction,\n {\n messageId: { $in: messageIds },\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? null,\n },\n undefined,\n dscope,\n )\n\n const byMessage = new Map<string, MessageReaction[]>()\n for (const reaction of reactions) {\n const existing = byMessage.get(reaction.messageId) ?? []\n existing.push(reaction)\n byMessage.set(reaction.messageId, existing)\n }\n\n return records.map((r) => {\n const rows = byMessage.get(r.id) ?? []\n const grouped = groupReactions(rows, ctx.userId)\n return { ...r, _reactions: grouped }\n })\n },\n}\n\nexport type ReactionGroup = {\n emoji: string\n count: number\n users: Array<{\n userId?: string | null\n externalId?: string | null\n displayName?: string | null\n providerKey?: string | null\n }>\n reactedByMe: boolean\n /**\n * MessageReaction.id of the current user's reaction row, if any. Exposed so\n * the reaction-bar UI can issue\n * `DELETE /api/communication_channels/messages/{messageId}/reactions/{myReactionId}`\n * when the user toggles their own reaction off. Null when `reactedByMe` is\n * false or the row was added by an external participant (provider-side).\n */\n myReactionId: string | null\n}\n\nfunction groupReactions(rows: MessageReaction[], currentUserId: string): ReactionGroup[] {\n const map = new Map<string, ReactionGroup>()\n for (const row of rows) {\n const key = row.emoji\n if (!map.has(key)) {\n map.set(key, { emoji: key, count: 0, users: [], reactedByMe: false, myReactionId: null })\n }\n const group = map.get(key)!\n group.count += 1\n group.users.push({\n userId: row.reactedByUserId ?? null,\n externalId: row.reactedByExternalId ?? null,\n displayName: row.reactedByDisplayName ?? null,\n providerKey: row.providerKey ?? null,\n })\n if (row.reactedByUserId && row.reactedByUserId === currentUserId) {\n group.reactedByMe = true\n if (!group.myReactionId) group.myReactionId = row.id ?? null\n }\n }\n return Array.from(map.values()).sort((a, b) => b.count - a.count)\n}\n\n// \u2500\u2500 _channelContact \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst conversationContactEnricher: ResponseEnricher<\n MessageRecord,\n { _channelContact?: ChannelContactEnrichment | null }\n> = {\n id: 'communication_channels.conversation-contact',\n targetEntity: 'messages.message',\n features: ['communication_channels.view'],\n priority: 15,\n timeout: 2000,\n fallback: { _channelContact: null },\n critical: false,\n\n async enrichOne(record, ctx) {\n const [out] = await this.enrichMany!([record], ctx)\n return out\n },\n\n async enrichMany(records, ctx) {\n if (records.length === 0) return records\n const messageIds = records.map((r) => r.id)\n const em = ctxEm(ctx)\n const dscope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId ?? null }\n const links = await findWithDecryption(\n em,\n MessageChannelLink,\n {\n messageId: { $in: messageIds },\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? null,\n },\n undefined,\n dscope,\n )\n const conversationIds = Array.from(\n new Set(links.map((l) => l.externalConversationId).filter(Boolean)),\n )\n let conversationsById = new Map<string, ExternalConversation>()\n if (conversationIds.length > 0) {\n const conversations = await findWithDecryption(\n em,\n ExternalConversation,\n {\n id: { $in: conversationIds },\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? null,\n },\n undefined,\n dscope,\n )\n conversationsById = new Map(conversations.map((c) => [c.id, c]))\n }\n\n const linksByMessage = new Map<string, MessageChannelLink>()\n for (const link of links) linksByMessage.set(link.messageId, link)\n\n return records.map((r) => {\n const link = linksByMessage.get(r.id)\n if (!link) return { ...r, _channelContact: null }\n const conversation = conversationsById.get(link.externalConversationId)\n if (!conversation) return { ...r, _channelContact: null }\n const enrichment: ChannelContactEnrichment = {\n contactPersonId: conversation.contactPersonId ?? null,\n assignedUserId: conversation.assignedUserId ?? null,\n subject: conversation.subject ?? null,\n }\n return { ...r, _channelContact: enrichment }\n })\n },\n}\n\nexport type ChannelContactEnrichment = {\n contactPersonId: string | null\n assignedUserId: string | null\n subject: string | null\n}\n\n// \u2500\u2500 Export \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const enrichers: ResponseEnricher[] = [\n messageChannelEnricher as unknown as ResponseEnricher,\n messageChannelPayloadEnricher as unknown as ResponseEnricher,\n messageReactionsEnricher as unknown as ResponseEnricher,\n conversationContactEnricher as unknown as ResponseEnricher,\n]\n\nexport default enrichers\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }