@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,205 @@
1
+ import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
2
+ import { CustomerInteraction } from "../data/entities.js";
3
+ import { buildEmailVisibilityMikroFilter } from "./visibilityFilter.js";
4
+ const DEFAULT_MAX_THREADS = 50;
5
+ const DEFAULT_MAX_MESSAGES_PER_THREAD = 200;
6
+ const PREVIEW_LENGTH = 140;
7
+ function extractAddresses(value) {
8
+ const out = [];
9
+ const pushOne = (item) => {
10
+ if (typeof item === "string") {
11
+ const trimmed = item.trim();
12
+ if (trimmed) out.push({ email: trimmed, name: null });
13
+ return;
14
+ }
15
+ if (item && typeof item === "object") {
16
+ const rec = item;
17
+ const address = typeof rec.address === "string" ? rec.address.trim() : null;
18
+ const name = typeof rec.name === "string" && rec.name.trim() ? rec.name.trim() : null;
19
+ if (address) out.push({ email: address, name });
20
+ }
21
+ };
22
+ if (Array.isArray(value)) {
23
+ for (const item of value) pushOne(item);
24
+ } else {
25
+ pushOne(value);
26
+ }
27
+ return out;
28
+ }
29
+ function firstString(...values) {
30
+ for (const value of values) {
31
+ if (typeof value === "string" && value.trim().length > 0) return value;
32
+ }
33
+ return null;
34
+ }
35
+ function toStringArray(...values) {
36
+ for (const value of values) {
37
+ if (Array.isArray(value)) {
38
+ const strings = value.filter((item) => typeof item === "string" && item.trim().length > 0);
39
+ if (strings.length > 0) return strings;
40
+ }
41
+ }
42
+ return [];
43
+ }
44
+ function truncate(value) {
45
+ if (!value) return value;
46
+ const collapsed = value.replace(/\s+/g, " ").trim();
47
+ if (collapsed.length <= PREVIEW_LENGTH) return collapsed;
48
+ return `${collapsed.slice(0, PREVIEW_LENGTH - 1)}\u2026`;
49
+ }
50
+ async function buildPersonEmailThreads(em, opts) {
51
+ const {
52
+ personId,
53
+ tenantId,
54
+ organizationId,
55
+ viewerUserId,
56
+ userFeatures,
57
+ maxThreads = DEFAULT_MAX_THREADS,
58
+ maxMessagesPerThread = DEFAULT_MAX_MESSAGES_PER_THREAD
59
+ } = opts;
60
+ const interactionWhere = {
61
+ entity: personId,
62
+ interactionType: "email",
63
+ deletedAt: null,
64
+ tenantId
65
+ };
66
+ if (organizationId) interactionWhere.organizationId = organizationId;
67
+ interactionWhere.$or = buildEmailVisibilityMikroFilter({
68
+ currentUserId: viewerUserId,
69
+ userFeatures
70
+ }).$or;
71
+ const dscope = { tenantId, organizationId: organizationId ?? null };
72
+ const interactions = await findWithDecryption(
73
+ em,
74
+ CustomerInteraction,
75
+ interactionWhere,
76
+ { orderBy: { occurredAt: "desc", createdAt: "desc" }, limit: maxThreads * 20 },
77
+ dscope
78
+ );
79
+ const linkIdByInteraction = /* @__PURE__ */ new Map();
80
+ const linkIds = [];
81
+ for (const interaction of interactions) {
82
+ const linkId = interaction.externalMessageId;
83
+ if (!linkId || linkIdByInteraction.has(linkId)) continue;
84
+ linkIdByInteraction.set(linkId, { occurredAt: interaction.occurredAt ?? interaction.createdAt });
85
+ linkIds.push(linkId);
86
+ }
87
+ if (linkIds.length === 0) return [];
88
+ const linkWhere = { id: { $in: linkIds }, tenantId };
89
+ if (organizationId) linkWhere.organizationId = organizationId;
90
+ const links = await findWithDecryption(
91
+ em,
92
+ "MessageChannelLink",
93
+ linkWhere,
94
+ void 0,
95
+ dscope
96
+ );
97
+ const messageIds = Array.from(
98
+ new Set(
99
+ links.map((link) => typeof link.messageId === "string" ? link.messageId : null).filter((value) => !!value)
100
+ )
101
+ );
102
+ const messageById = /* @__PURE__ */ new Map();
103
+ if (messageIds.length > 0) {
104
+ const messageWhere = { id: { $in: messageIds }, tenantId };
105
+ if (organizationId) messageWhere.organizationId = organizationId;
106
+ const messages = await findWithDecryption(
107
+ em,
108
+ "Message",
109
+ messageWhere,
110
+ void 0,
111
+ dscope
112
+ );
113
+ for (const message of messages) {
114
+ if (typeof message.id === "string") messageById.set(message.id, message);
115
+ }
116
+ }
117
+ const threadsByKey = /* @__PURE__ */ new Map();
118
+ for (const link of links) {
119
+ const linkId = typeof link.id === "string" ? link.id : null;
120
+ if (!linkId) continue;
121
+ const direction = link.direction === "outbound" ? "outbound" : "inbound";
122
+ const providerKey = typeof link.providerKey === "string" ? link.providerKey : null;
123
+ const payload = link.channelPayload ?? null;
124
+ const meta = link.channelMetadata ?? null;
125
+ const messageId = typeof link.messageId === "string" ? link.messageId : null;
126
+ const message = messageId ? messageById.get(messageId) ?? null : null;
127
+ const primary = direction === "outbound" ? meta : payload;
128
+ const secondary = direction === "outbound" ? payload : meta;
129
+ const fromList = extractAddresses(primary?.from ?? secondary?.from);
130
+ const toList = extractAddresses(primary?.to ?? secondary?.to);
131
+ const ccList = extractAddresses(primary?.cc ?? secondary?.cc);
132
+ const subject = firstString(
133
+ primary?.subject,
134
+ secondary?.subject,
135
+ typeof message?.subject === "string" ? message.subject : null
136
+ );
137
+ const bodyText = firstString(
138
+ direction === "outbound" ? meta?.bodyText : payload?.text,
139
+ direction === "outbound" ? payload?.text : meta?.bodyText,
140
+ typeof message?.body === "string" ? message.body : null
141
+ );
142
+ const sentAtRaw = (typeof message?.sentAt === "string" || message?.sentAt instanceof Date ? message.sentAt : null) ?? (link.createdAt instanceof Date || typeof link.createdAt === "string" ? link.createdAt : null) ?? linkIdByInteraction.get(linkId)?.occurredAt ?? /* @__PURE__ */ new Date();
143
+ const sentAt = (sentAtRaw instanceof Date ? sentAtRaw : new Date(sentAtRaw)).toISOString();
144
+ const threadKey = (typeof message?.threadId === "string" && message.threadId ? message.threadId : null) ?? (messageId ? `message:${messageId}` : `link:${linkId}`);
145
+ const dto = {
146
+ id: linkId,
147
+ messageId,
148
+ rfcMessageId: firstString(primary?.messageId, secondary?.messageId),
149
+ references: toStringArray(primary?.references, secondary?.references),
150
+ direction,
151
+ fromName: fromList[0]?.name ?? null,
152
+ fromEmail: fromList[0]?.email ?? null,
153
+ to: toList.map((a) => a.email),
154
+ cc: ccList.map((a) => a.email),
155
+ subject,
156
+ bodyText,
157
+ sentAt,
158
+ providerKey
159
+ };
160
+ const existing = threadsByKey.get(threadKey);
161
+ if (existing) {
162
+ existing.messages.push(dto);
163
+ } else {
164
+ threadsByKey.set(threadKey, {
165
+ threadKey,
166
+ subject,
167
+ preview: null,
168
+ participants: [],
169
+ lastMessageAt: sentAt,
170
+ messageCount: 0,
171
+ providerKey,
172
+ lastDirection: direction,
173
+ messages: [dto]
174
+ });
175
+ }
176
+ }
177
+ const threads = [];
178
+ for (const thread of threadsByKey.values()) {
179
+ thread.messages.sort((a, b) => a.sentAt.localeCompare(b.sentAt));
180
+ if (thread.messages.length > maxMessagesPerThread) {
181
+ thread.messages = thread.messages.slice(thread.messages.length - maxMessagesPerThread);
182
+ }
183
+ const last = thread.messages[thread.messages.length - 1];
184
+ const firstWithSubject = thread.messages.find((m) => m.subject);
185
+ const participantSet = /* @__PURE__ */ new Set();
186
+ for (const message of thread.messages) {
187
+ if (message.direction === "inbound" && message.fromEmail) participantSet.add(message.fromEmail);
188
+ if (message.direction === "outbound") message.to.forEach((email) => participantSet.add(email));
189
+ }
190
+ thread.subject = firstWithSubject?.subject ?? thread.subject;
191
+ thread.preview = truncate(last?.bodyText ?? null);
192
+ thread.participants = Array.from(participantSet);
193
+ thread.lastMessageAt = last?.sentAt ?? thread.lastMessageAt;
194
+ thread.lastDirection = last?.direction ?? thread.lastDirection;
195
+ thread.providerKey = last?.providerKey ?? thread.providerKey;
196
+ thread.messageCount = thread.messages.length;
197
+ threads.push(thread);
198
+ }
199
+ threads.sort((a, b) => b.lastMessageAt.localeCompare(a.lastMessageAt));
200
+ return threads.slice(0, maxThreads);
201
+ }
202
+ export {
203
+ buildPersonEmailThreads
204
+ };
205
+ //# sourceMappingURL=personEmailThreads.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/customers/lib/personEmailThreads.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CustomerInteraction } from '../data/entities'\nimport { buildEmailVisibilityMikroFilter } from './visibilityFilter'\n\n/**\n * Read model that turns a Person's email `CustomerInteraction` rows into\n * Gmail-style threads for the CRM Person page.\n *\n * The Person\u2194email anchor lives only on `CustomerInteraction`\n * (`interactionType='email'`, `externalMessageId` \u2192 `MessageChannelLink.id`).\n * The thread grouping key and the rich per-message content (From/To/Cc, body,\n * direction) live in the communication_channels hub. We read the hub entities\n * by their string class names \u2014 the same cross-module pattern used by\n * `link-channel-message-handler.ts` \u2014 so the customers module never imports the\n * hub's entity classes (root AGENTS.md: no direct ORM relationships between\n * modules).\n */\n\nexport type EmailThreadDirection = 'inbound' | 'outbound'\n\nexport type PersonEmailMessage = {\n id: string\n /** Open Mercato `messages.message` id \u2014 used as `parentMessageId` when replying so the reply joins this thread. */\n messageId: string | null\n /** RFC2822 Message-ID of this email \u2014 used as `In-Reply-To` on a reply. */\n rfcMessageId: string | null\n /** RFC2822 References chain for this email. */\n references: string[]\n direction: EmailThreadDirection\n fromName: string | null\n fromEmail: string | null\n to: string[]\n cc: string[]\n subject: string | null\n bodyText: string | null\n sentAt: string\n providerKey: string | null\n}\n\nexport type PersonEmailThread = {\n threadKey: string\n subject: string | null\n preview: string | null\n participants: string[]\n lastMessageAt: string\n messageCount: number\n providerKey: string | null\n lastDirection: EmailThreadDirection\n messages: PersonEmailMessage[]\n}\n\nexport type BuildPersonEmailThreadsOptions = {\n personId: string\n tenantId: string\n organizationId: string | null\n viewerUserId: string | null\n userFeatures: string[] | null | undefined\n maxThreads?: number\n maxMessagesPerThread?: number\n}\n\nconst DEFAULT_MAX_THREADS = 50\nconst DEFAULT_MAX_MESSAGES_PER_THREAD = 200\nconst PREVIEW_LENGTH = 140\n\ntype JsonRecord = Record<string, unknown>\n\n/** Extracts `{ email, name }[]` from the many shapes a channel address field can take. */\nfunction extractAddresses(value: unknown): Array<{ email: string; name: string | null }> {\n const out: Array<{ email: string; name: string | null }> = []\n const pushOne = (item: unknown): void => {\n if (typeof item === 'string') {\n const trimmed = item.trim()\n if (trimmed) out.push({ email: trimmed, name: null })\n return\n }\n if (item && typeof item === 'object') {\n const rec = item as JsonRecord\n const address = typeof rec.address === 'string' ? rec.address.trim() : null\n const name = typeof rec.name === 'string' && rec.name.trim() ? rec.name.trim() : null\n if (address) out.push({ email: address, name })\n }\n }\n if (Array.isArray(value)) {\n for (const item of value) pushOne(item)\n } else {\n pushOne(value)\n }\n return out\n}\n\nfunction firstString(...values: unknown[]): string | null {\n for (const value of values) {\n if (typeof value === 'string' && value.trim().length > 0) return value\n }\n return null\n}\n\nfunction toStringArray(...values: unknown[]): string[] {\n for (const value of values) {\n if (Array.isArray(value)) {\n const strings = value.filter((item): item is string => typeof item === 'string' && item.trim().length > 0)\n if (strings.length > 0) return strings\n }\n }\n return []\n}\n\nfunction truncate(value: string | null): string | null {\n if (!value) return value\n const collapsed = value.replace(/\\s+/g, ' ').trim()\n if (collapsed.length <= PREVIEW_LENGTH) return collapsed\n return `${collapsed.slice(0, PREVIEW_LENGTH - 1)}\u2026`\n}\n\n/**\n * Builds the per-Person email-thread read model. Pure data assembly \u2014 callers\n * are responsible for auth and scoping (tenantId/organizationId required).\n */\nexport async function buildPersonEmailThreads(\n em: EntityManager,\n opts: BuildPersonEmailThreadsOptions,\n): Promise<PersonEmailThread[]> {\n const {\n personId,\n tenantId,\n organizationId,\n viewerUserId,\n userFeatures,\n maxThreads = DEFAULT_MAX_THREADS,\n maxMessagesPerThread = DEFAULT_MAX_MESSAGES_PER_THREAD,\n } = opts\n\n // \u2500\u2500 (1) Load the Person's email interactions (the Person\u2194email anchor) \u2500\u2500\n const interactionWhere: JsonRecord = {\n entity: personId,\n interactionType: 'email',\n deletedAt: null,\n tenantId,\n }\n if (organizationId) interactionWhere.organizationId = organizationId\n\n // Per-email visibility (v1: strict owner-only, no admin bypass) \u2014 the CRM\n // Person page applies the SAME rule as every other interactions read path via\n // `buildEmailVisibilityMikroFilter`, so the Emails tab and the `/interactions`\n // timeline can never disagree about who sees an email:\n // - `visibility = 'shared'` is visible to every user with CRM access to this\n // Person (lets a teammate pick up a handed-off thread),\n // - `visibility = 'private'` is visible ONLY to its author (the mailbox\n // owner) \u2014 never to teammates, not even an admin/superadmin (team\n // oversight is a deliberate v2 follow-up),\n // - legacy/unset rows (`visibility IS NULL`) stay visible so pre-existing\n // CRM history is never silently hidden.\n // Fail-closed: a null viewer (API-key caller) never matches the author arm, so\n // it only ever sees shared/legacy rows \u2014 never anyone's private email.\n interactionWhere.$or = buildEmailVisibilityMikroFilter({\n currentUserId: viewerUserId,\n userFeatures,\n }).$or\n\n // `customer_interaction.title`/`body` are encrypted at rest, so reads go\n // through `findWithDecryption` even though we only consume non-encrypted\n // columns here \u2014 this keeps the encrypted-entity contract intact.\n const dscope = { tenantId, organizationId: organizationId ?? null }\n const interactions = (await findWithDecryption(\n em,\n CustomerInteraction,\n interactionWhere as never,\n { orderBy: { occurredAt: 'desc', createdAt: 'desc' }, limit: maxThreads * 20 },\n dscope,\n )) as CustomerInteraction[]\n\n const linkIdByInteraction = new Map<string, { occurredAt: Date }>()\n const linkIds: string[] = []\n for (const interaction of interactions) {\n const linkId = interaction.externalMessageId\n if (!linkId || linkIdByInteraction.has(linkId)) continue\n linkIdByInteraction.set(linkId, { occurredAt: interaction.occurredAt ?? interaction.createdAt })\n linkIds.push(linkId)\n }\n if (linkIds.length === 0) return []\n\n // \u2500\u2500 (2) Resolve MessageChannelLink rows (rich content + direction) \u2500\u2500\u2500\u2500\u2500\u2500\n const linkWhere: JsonRecord = { id: { $in: linkIds }, tenantId }\n if (organizationId) linkWhere.organizationId = organizationId\n const links = (await findWithDecryption(\n em,\n 'MessageChannelLink' as never,\n linkWhere as never,\n undefined,\n dscope,\n )) as JsonRecord[]\n\n // \u2500\u2500 (3) Resolve hub Message rows (the authoritative thread grouping) \u2500\u2500\u2500\u2500\n const messageIds = Array.from(\n new Set(\n links\n .map((link) => (typeof link.messageId === 'string' ? (link.messageId as string) : null))\n .filter((value): value is string => !!value),\n ),\n )\n const messageById = new Map<string, JsonRecord>()\n if (messageIds.length > 0) {\n const messageWhere: JsonRecord = { id: { $in: messageIds }, tenantId }\n if (organizationId) messageWhere.organizationId = organizationId\n const messages = (await findWithDecryption(\n em,\n 'Message' as never,\n messageWhere as never,\n undefined,\n dscope,\n )) as JsonRecord[]\n for (const message of messages) {\n if (typeof message.id === 'string') messageById.set(message.id, message)\n }\n }\n\n // \u2500\u2500 (4) Build per-message DTOs grouped by thread \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 const threadsByKey = new Map<string, PersonEmailThread>()\n\n for (const link of links) {\n const linkId = typeof link.id === 'string' ? (link.id as string) : null\n if (!linkId) continue\n const direction: EmailThreadDirection = link.direction === 'outbound' ? 'outbound' : 'inbound'\n const providerKey = typeof link.providerKey === 'string' ? (link.providerKey as string) : null\n const payload = (link.channelPayload ?? null) as JsonRecord | null\n const meta = (link.channelMetadata ?? null) as JsonRecord | null\n const messageId = typeof link.messageId === 'string' ? (link.messageId as string) : null\n const message = messageId ? messageById.get(messageId) ?? null : null\n\n // Outbound addresses live in channelMetadata; inbound in channelPayload.\n // Prefer the direction-appropriate source, fall back to the other.\n const primary = direction === 'outbound' ? meta : payload\n const secondary = direction === 'outbound' ? payload : meta\n const fromList = extractAddresses(primary?.from ?? secondary?.from)\n const toList = extractAddresses(primary?.to ?? secondary?.to)\n const ccList = extractAddresses(primary?.cc ?? secondary?.cc)\n\n const subject = firstString(\n primary?.subject,\n secondary?.subject,\n typeof message?.subject === 'string' ? message.subject : null,\n )\n const bodyText = firstString(\n direction === 'outbound' ? meta?.bodyText : payload?.text,\n direction === 'outbound' ? payload?.text : meta?.bodyText,\n typeof message?.body === 'string' ? message.body : null,\n )\n\n const sentAtRaw =\n (typeof message?.sentAt === 'string' || message?.sentAt instanceof Date\n ? message.sentAt\n : null) ??\n (link.createdAt instanceof Date || typeof link.createdAt === 'string' ? link.createdAt : null) ??\n linkIdByInteraction.get(linkId)?.occurredAt ??\n new Date()\n const sentAt = (sentAtRaw instanceof Date ? sentAtRaw : new Date(sentAtRaw)).toISOString()\n\n const threadKey =\n (typeof message?.threadId === 'string' && message.threadId ? message.threadId : null) ??\n (messageId ? `message:${messageId}` : `link:${linkId}`)\n\n const dto: PersonEmailMessage = {\n id: linkId,\n messageId,\n rfcMessageId: firstString(primary?.messageId, secondary?.messageId),\n references: toStringArray(primary?.references, secondary?.references),\n direction,\n fromName: fromList[0]?.name ?? null,\n fromEmail: fromList[0]?.email ?? null,\n to: toList.map((a) => a.email),\n cc: ccList.map((a) => a.email),\n subject,\n bodyText,\n sentAt,\n providerKey,\n }\n\n const existing = threadsByKey.get(threadKey)\n if (existing) {\n existing.messages.push(dto)\n } else {\n threadsByKey.set(threadKey, {\n threadKey,\n subject,\n preview: null,\n participants: [],\n lastMessageAt: sentAt,\n messageCount: 0,\n providerKey,\n lastDirection: direction,\n messages: [dto],\n })\n }\n }\n\n // \u2500\u2500 (5) Finalize thread summaries \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 const threads: PersonEmailThread[] = []\n for (const thread of threadsByKey.values()) {\n thread.messages.sort((a, b) => a.sentAt.localeCompare(b.sentAt))\n if (thread.messages.length > maxMessagesPerThread) {\n thread.messages = thread.messages.slice(thread.messages.length - maxMessagesPerThread)\n }\n const last = thread.messages[thread.messages.length - 1]\n const firstWithSubject = thread.messages.find((m) => m.subject)\n const participantSet = new Set<string>()\n for (const message of thread.messages) {\n // External counterpart = the \"from\" for inbound, the \"to\" for outbound.\n if (message.direction === 'inbound' && message.fromEmail) participantSet.add(message.fromEmail)\n if (message.direction === 'outbound') message.to.forEach((email) => participantSet.add(email))\n }\n thread.subject = firstWithSubject?.subject ?? thread.subject\n thread.preview = truncate(last?.bodyText ?? null)\n thread.participants = Array.from(participantSet)\n thread.lastMessageAt = last?.sentAt ?? thread.lastMessageAt\n thread.lastDirection = last?.direction ?? thread.lastDirection\n thread.providerKey = last?.providerKey ?? thread.providerKey\n thread.messageCount = thread.messages.length\n threads.push(thread)\n }\n\n threads.sort((a, b) => b.lastMessageAt.localeCompare(a.lastMessageAt))\n return threads.slice(0, maxThreads)\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,51 @@
1
+ import { hasFeature } from "@open-mercato/shared/security/features";
2
+ const EMAIL_VIEW_PRIVATE_FEATURE = "customers.email.view_private";
3
+ function callerHasEmailViewPrivate(userFeatures) {
4
+ if (!Array.isArray(userFeatures) || userFeatures.length === 0) return false;
5
+ return hasFeature(userFeatures, EMAIL_VIEW_PRIVATE_FEATURE);
6
+ }
7
+ function canChangeEmailVisibility(opts) {
8
+ if (opts.interactionType !== "email") return true;
9
+ if ((opts.nextVisibility ?? null) === (opts.currentVisibility ?? null)) return true;
10
+ return Boolean(opts.actorUserId) && opts.authorUserId === opts.actorUserId;
11
+ }
12
+ function applyEmailVisibilityFilter(query, opts) {
13
+ const currentUserId = opts.currentUserId;
14
+ return query.where(
15
+ (eb) => eb.or([
16
+ eb("interaction_type", "!=", "email"),
17
+ eb("visibility", "is", null),
18
+ eb("visibility", "!=", "private"),
19
+ currentUserId ? eb("author_user_id", "=", currentUserId) : eb.val(false)
20
+ ])
21
+ );
22
+ }
23
+ async function resolveCallerEmailFeatures(container, userId, tenantId, organizationId) {
24
+ if (!userId) return void 0;
25
+ try {
26
+ const rbac = container.resolve("rbacService");
27
+ if (!rbac?.getGrantedFeatures) return void 0;
28
+ return await rbac.getGrantedFeatures(userId, { tenantId, organizationId });
29
+ } catch {
30
+ return void 0;
31
+ }
32
+ }
33
+ function buildEmailVisibilityMikroFilter(opts) {
34
+ return {
35
+ $or: [
36
+ { interactionType: { $ne: "email" } },
37
+ { visibility: null },
38
+ { visibility: { $ne: "private" } },
39
+ ...opts.currentUserId ? [{ authorUserId: opts.currentUserId }] : []
40
+ ]
41
+ };
42
+ }
43
+ export {
44
+ EMAIL_VIEW_PRIVATE_FEATURE,
45
+ applyEmailVisibilityFilter,
46
+ buildEmailVisibilityMikroFilter,
47
+ callerHasEmailViewPrivate,
48
+ canChangeEmailVisibility,
49
+ resolveCallerEmailFeatures
50
+ };
51
+ //# sourceMappingURL=visibilityFilter.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/customers/lib/visibilityFilter.ts"],
4
+ "sourcesContent": ["import type { FilterQuery } from '@mikro-orm/postgresql'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport { CustomerInteraction } from '../data/entities'\n\n/**\n * The ACL feature that grants admins the right to see private emails authored\n * by other users. Declared in `acl.ts` but granted to NO role in v1 (reserved\n * for the v2 oversight feature \u2014 see `callerHasEmailViewPrivate`).\n */\nexport const EMAIL_VIEW_PRIVATE_FEATURE = 'customers.email.view_private'\n\n/**\n * Returns true when the caller holds the admin override to see ALL private email\n * interactions. Honours wildcards (`customers.*`, `*`).\n *\n * RESERVED FOR v2 \u2014 NOT wired in v1. The v1 model is strict owner-only with no\n * admin bypass: the visibility filters and `canChangeEmailVisibility` ignore\n * caller features, and `customers.email.view_private` is granted to no role.\n * Kept (with {@link EMAIL_VIEW_PRIVATE_FEATURE}) so v2 oversight can opt back in\n * without re-introducing the helper. Do NOT wire this into a read path without\n * an explicit v2 spec.\n */\nexport function callerHasEmailViewPrivate(userFeatures: string[] | null | undefined): boolean {\n if (!Array.isArray(userFeatures) || userFeatures.length === 0) return false\n return hasFeature(userFeatures, EMAIL_VIEW_PRIVATE_FEATURE)\n}\n\n/**\n * Authorization predicate for CHANGING an email interaction's visibility.\n *\n * Personal mailbox privacy (v1: strict owner-only): ONLY the interaction's\n * author may flip their own email between private/shared \u2014 there is no admin\n * bypass. Non-email rows and no-op changes are always allowed. Mirrors the gate\n * in the dedicated `PATCH .../visibility` route so the generic interaction-update\n * path cannot bypass the privacy control. `userFeatures` is reserved for v2.\n */\nexport function canChangeEmailVisibility(opts: {\n interactionType: string\n currentVisibility: string | null | undefined\n nextVisibility: string | null | undefined\n authorUserId: string | null | undefined\n actorUserId: string | null | undefined\n userFeatures: string[] | null | undefined\n}): boolean {\n if (opts.interactionType !== 'email') return true\n if ((opts.nextVisibility ?? null) === (opts.currentVisibility ?? null)) return true\n return Boolean(opts.actorUserId) && opts.authorUserId === opts.actorUserId\n}\n\nexport interface ApplyEmailVisibilityFilterOptions {\n currentUserId: string | null\n userFeatures: string[] | null | undefined\n}\n\n/**\n * Adds a `WHERE` predicate to a kysely query so that:\n * - Non-email interactions (calls, meetings, tasks) pass through unchanged.\n * - Email interactions with `visibility = 'shared'` are visible to all.\n * - Email interactions with `visibility = 'private'` are visible ONLY to the\n * `authorUserId` (channel owner).\n *\n * Personal mailbox privacy (v1: strict owner-only) \u2014 there is NO admin bypass:\n * a private email is hidden from everyone except its author, including\n * admins/superadmins. `opts.userFeatures` is retained for signature stability\n * and reserved for the v2 admin-oversight feature.\n *\n * The function expects a kysely-style builder whose `.where()` accepts an\n * expression-builder callback. Returns the same builder for chaining.\n */\nexport function applyEmailVisibilityFilter<T extends { where: (...args: any[]) => T }>(\n query: T,\n opts: ApplyEmailVisibilityFilterOptions,\n): T {\n const currentUserId = opts.currentUserId\n // A row is hidden ONLY when it is an email explicitly marked `private` and the\n // caller is not its author. Everything else stays visible, including:\n // - non-email interactions (calls, meetings, tasks),\n // - emails marked `shared`,\n // - legacy/unset rows where `visibility IS NULL` (e.g. email-log entries\n // created before per-email visibility shipped) \u2014 these must remain\n // visible to avoid silently hiding pre-existing CRM history.\n return query.where((eb: any) =>\n eb.or([\n eb('interaction_type', '!=', 'email'),\n eb('visibility', 'is', null),\n eb('visibility', '!=', 'private'),\n currentUserId\n ? eb('author_user_id', '=', currentUserId)\n : eb.val(false),\n ]),\n )\n}\n\ntype RbacServiceLike = {\n getGrantedFeatures?: (\n userId: string,\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<string[] | undefined>\n}\n\n/**\n * Resolve the caller's granted features (wildcard-aware downstream) so a v2\n * visibility filter could honour the `customers.email.view_private` admin\n * override. Returns `undefined` when there is no user or the RBAC service is\n * unavailable \u2014 callers MUST treat `undefined` as \"no bypass\" (fail closed).\n *\n * RESERVED FOR v2 \u2014 NOT called by any v1 read path. v1 is strict owner-only, so\n * the read routes pass `userFeatures: undefined` to the filters rather than\n * resolving features here (which would be a wasted RBAC round-trip). Re-wire\n * only under an explicit v2 oversight spec.\n */\nexport async function resolveCallerEmailFeatures(\n container: { resolve: (name: string) => unknown },\n userId: string | null,\n tenantId: string | null,\n organizationId: string | null,\n): Promise<string[] | undefined> {\n if (!userId) return undefined\n try {\n const rbac = container.resolve('rbacService') as RbacServiceLike | undefined\n if (!rbac?.getGrantedFeatures) return undefined\n return await rbac.getGrantedFeatures(userId, { tenantId, organizationId })\n } catch {\n return undefined\n }\n}\n\n/**\n * MikroORM equivalent of {@link applyEmailVisibilityFilter}. Returns a\n * `FilterQuery` fragment to merge (implicit AND) into a `CustomerInteraction`\n * where-clause so private email rows are excluded for non-owner, non-admin\n * callers on MikroORM read paths (`findWithDecryption`/`em.find`/`em.count`).\n *\n * Mirrors the kysely predicate exactly, including the legacy `visibility IS NULL`\n * passthrough so pre-existing CRM history is never hidden. Personal mailbox\n * privacy (v1: strict owner-only): no admin bypass \u2014 a private email is hidden\n * from everyone except its author. `opts.userFeatures` is reserved for v2.\n */\nexport type EmailVisibilityMikroFilter = { $or?: FilterQuery<CustomerInteraction>[] }\n\nexport function buildEmailVisibilityMikroFilter(\n opts: ApplyEmailVisibilityFilterOptions,\n): EmailVisibilityMikroFilter {\n return {\n $or: [\n { interactionType: { $ne: 'email' } },\n { visibility: null },\n { visibility: { $ne: 'private' } },\n ...(opts.currentUserId ? [{ authorUserId: opts.currentUserId }] : []),\n ],\n }\n}\n"],
5
+ "mappings": "AACA,SAAS,kBAAkB;AAQpB,MAAM,6BAA6B;AAanC,SAAS,0BAA0B,cAAoD;AAC5F,MAAI,CAAC,MAAM,QAAQ,YAAY,KAAK,aAAa,WAAW,EAAG,QAAO;AACtE,SAAO,WAAW,cAAc,0BAA0B;AAC5D;AAWO,SAAS,yBAAyB,MAO7B;AACV,MAAI,KAAK,oBAAoB,QAAS,QAAO;AAC7C,OAAK,KAAK,kBAAkB,WAAW,KAAK,qBAAqB,MAAO,QAAO;AAC/E,SAAO,QAAQ,KAAK,WAAW,KAAK,KAAK,iBAAiB,KAAK;AACjE;AAsBO,SAAS,2BACd,OACA,MACG;AACH,QAAM,gBAAgB,KAAK;AAQ3B,SAAO,MAAM;AAAA,IAAM,CAAC,OAClB,GAAG,GAAG;AAAA,MACJ,GAAG,oBAAoB,MAAM,OAAO;AAAA,MACpC,GAAG,cAAc,MAAM,IAAI;AAAA,MAC3B,GAAG,cAAc,MAAM,SAAS;AAAA,MAChC,gBACI,GAAG,kBAAkB,KAAK,aAAa,IACvC,GAAG,IAAI,KAAK;AAAA,IAClB,CAAC;AAAA,EACH;AACF;AAoBA,eAAsB,2BACpB,WACA,QACA,UACA,gBAC+B;AAC/B,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI;AACF,UAAM,OAAO,UAAU,QAAQ,aAAa;AAC5C,QAAI,CAAC,MAAM,mBAAoB,QAAO;AACtC,WAAO,MAAM,KAAK,mBAAmB,QAAQ,EAAE,UAAU,eAAe,CAAC;AAAA,EAC3E,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAeO,SAAS,gCACd,MAC4B;AAC5B,SAAO;AAAA,IACL,KAAK;AAAA,MACH,EAAE,iBAAiB,EAAE,KAAK,QAAQ,EAAE;AAAA,MACpC,EAAE,YAAY,KAAK;AAAA,MACnB,EAAE,YAAY,EAAE,KAAK,UAAU,EAAE;AAAA,MACjC,GAAI,KAAK,gBAAgB,CAAC,EAAE,cAAc,KAAK,cAAc,CAAC,IAAI,CAAC;AAAA,IACrE;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,20 @@
1
+ import { Migration } from "@mikro-orm/migrations";
2
+ class Migration20260527012240_customers extends Migration {
3
+ up() {
4
+ this.addSql(`alter table "customer_interactions" add "external_message_id" uuid null, add "channel_provider_key" text null;`);
5
+ this.addSql(`update "customer_interactions" set "visibility" = 'shared' where "interaction_type" = 'email' and "visibility" is null and "external_message_id" is null;`);
6
+ this.addSql(`create index "customer_interactions_external_msg_idx" on "customer_interactions" ("external_message_id") where "external_message_id" is not null;`);
7
+ this.addSql(`create unique index "customer_interactions_email_dedupe_uq" on "customer_interactions" ("entity_id", "external_message_id") where "external_message_id" is not null and "deleted_at" is null;`);
8
+ this.addSql(`create index "customer_interactions_email_visibility_idx" on "customer_interactions" ("entity_id", "interaction_type", "visibility", "author_user_id") where "interaction_type" = 'email' and "deleted_at" is null;`);
9
+ }
10
+ down() {
11
+ this.addSql(`drop index if exists "customer_interactions_email_visibility_idx";`);
12
+ this.addSql(`drop index if exists "customer_interactions_email_dedupe_uq";`);
13
+ this.addSql(`drop index if exists "customer_interactions_external_msg_idx";`);
14
+ this.addSql(`alter table "customer_interactions" drop column "external_message_id", drop column "channel_provider_key";`);
15
+ }
16
+ }
17
+ export {
18
+ Migration20260527012240_customers
19
+ };
20
+ //# sourceMappingURL=Migration20260527012240_customers.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/customers/migrations/Migration20260527012240_customers.ts"],
4
+ "sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\nexport class Migration20260527012240_customers extends Migration {\n\n override up(): void | Promise<void> {\n this.addSql(`alter table \"customer_interactions\" add \"external_message_id\" uuid null, add \"channel_provider_key\" text null;`);\n // Backfill: pre-existing email-log interactions predate per-email visibility\n // and have null visibility. Mark them 'shared' so they keep their prior\n // visible-to-all behavior once the visibility filter is enabled.\n this.addSql(`update \"customer_interactions\" set \"visibility\" = 'shared' where \"interaction_type\" = 'email' and \"visibility\" is null and \"external_message_id\" is null;`);\n this.addSql(`create index \"customer_interactions_external_msg_idx\" on \"customer_interactions\" (\"external_message_id\") where \"external_message_id\" is not null;`);\n this.addSql(`create unique index \"customer_interactions_email_dedupe_uq\" on \"customer_interactions\" (\"entity_id\", \"external_message_id\") where \"external_message_id\" is not null and \"deleted_at\" is null;`);\n this.addSql(`create index \"customer_interactions_email_visibility_idx\" on \"customer_interactions\" (\"entity_id\", \"interaction_type\", \"visibility\", \"author_user_id\") where \"interaction_type\" = 'email' and \"deleted_at\" is null;`);\n }\n\n override down(): void | Promise<void> {\n this.addSql(`drop index if exists \"customer_interactions_email_visibility_idx\";`);\n this.addSql(`drop index if exists \"customer_interactions_email_dedupe_uq\";`);\n this.addSql(`drop index if exists \"customer_interactions_external_msg_idx\";`);\n this.addSql(`alter table \"customer_interactions\" drop column \"external_message_id\", drop column \"channel_provider_key\";`);\n }\n\n}\n"],
5
+ "mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,0CAA0C,UAAU;AAAA,EAEtD,KAA2B;AAClC,SAAK,OAAO,gHAAgH;AAI5H,SAAK,OAAO,2JAA2J;AACvK,SAAK,OAAO,mJAAmJ;AAC/J,SAAK,OAAO,+LAA+L;AAC3M,SAAK,OAAO,qNAAqN;AAAA,EACnO;AAAA,EAES,OAA6B;AACpC,SAAK,OAAO,oEAAoE;AAChF,SAAK,OAAO,+DAA+D;AAC3E,SAAK,OAAO,gEAAgE;AAC5E,SAAK,OAAO,4GAA4G;AAAA,EAC1H;AAEF;",
6
+ "names": []
7
+ }
@@ -94,7 +94,8 @@ const setup = {
94
94
  "customers.widgets.new-customers",
95
95
  "customers.widgets.new-deals",
96
96
  "customers.roles.view",
97
- "customers.roles.manage"
97
+ "customers.roles.manage",
98
+ "customers.email.compose"
98
99
  ]
99
100
  }
100
101
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/customers/setup.ts"],
4
- "sourcesContent": ["import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { FeatureToggle } from '@open-mercato/core/modules/feature_toggles/data/entities'\nimport {\n ensureCustomerCustomFieldDefinitions,\n seedCustomerDictionaries,\n seedCurrencyDictionary,\n seedCustomerExamples,\n seedDefaultPipeline,\n} from './cli'\nimport { ensureDictionaryEntry } from './commands/shared'\nimport { DEFAULT_CUSTOMER_ROLE_TYPES } from './lib/customerRoleTypes'\n\nconst interactionFeatureToggles = [\n {\n identifier: 'customers.interactions.unified',\n name: 'Unified Interactions',\n description: 'When enabled, interactions use the unified canonical model instead of per-entity activity tracking.',\n category: 'customers',\n type: 'boolean' as const,\n defaultValue: false,\n },\n {\n identifier: 'customers.interactions.legacy-adapters',\n name: 'Interaction Legacy Adapters',\n description: 'When enabled, legacy activity/todo APIs are bridged to the canonical interaction model.',\n category: 'customers',\n type: 'boolean' as const,\n defaultValue: true,\n },\n {\n identifier: 'customers.interactions.external-sync',\n name: 'Interaction External Sync',\n description: 'When enabled, interactions can be synced from external systems (calendars, email providers).',\n category: 'customers',\n type: 'boolean' as const,\n defaultValue: false,\n },\n]\n\nasync function seedInteractionFeatureToggles(em: EntityManager): Promise<void> {\n for (const toggle of interactionFeatureToggles) {\n const existing = await em.findOne(FeatureToggle, { identifier: toggle.identifier, deletedAt: null })\n if (existing) continue\n const entity = em.create(FeatureToggle, {\n identifier: toggle.identifier,\n name: toggle.name,\n description: toggle.description,\n category: toggle.category,\n type: toggle.type,\n defaultValue: toggle.defaultValue,\n })\n em.persist(entity)\n }\n await em.flush()\n}\n\nexport const setup: ModuleSetupConfig = {\n seedDefaults: async (ctx) => {\n const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n await seedCustomerDictionaries(ctx.em, scope)\n await seedCurrencyDictionary(ctx.em, scope)\n await seedDefaultPipeline(ctx.em, scope)\n await ensureCustomerCustomFieldDefinitions(ctx.em, ctx.tenantId)\n await seedInteractionFeatureToggles(ctx.em)\n for (const entry of DEFAULT_CUSTOMER_ROLE_TYPES) {\n await ensureDictionaryEntry(ctx.em, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n kind: 'customer_role_type',\n value: entry.value,\n label: entry.label,\n color: entry.color,\n icon: entry.icon,\n })\n }\n },\n\n seedExamples: async (ctx) => {\n const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n await seedCustomerExamples(ctx.em, ctx.container, scope)\n },\n\n defaultRoleFeatures: {\n admin: [\n 'customers.*',\n ],\n employee: [\n 'customers.people.view',\n 'customers.people.manage',\n 'customers.companies.view',\n 'customers.companies.manage',\n 'customers.deals.view',\n 'customers.deals.manage',\n 'customers.activities.view',\n 'customers.activities.manage',\n 'customers.pipelines.view',\n 'customers.interactions.view',\n 'customers.widgets.todos',\n 'customers.widgets.next-interactions',\n 'customers.widgets.new-customers',\n 'customers.widgets.new-deals',\n 'customers.roles.view',\n 'customers.roles.manage',\n ],\n },\n}\n\nexport default setup\n"],
5
- "mappings": "AAEA,SAAS,qBAAqB;AAC9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,6BAA6B;AACtC,SAAS,mCAAmC;AAE5C,MAAM,4BAA4B;AAAA,EAChC;AAAA,IACE,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AACF;AAEA,eAAe,8BAA8B,IAAkC;AAC7E,aAAW,UAAU,2BAA2B;AAC9C,UAAM,WAAW,MAAM,GAAG,QAAQ,eAAe,EAAE,YAAY,OAAO,YAAY,WAAW,KAAK,CAAC;AACnG,QAAI,SAAU;AACd,UAAM,SAAS,GAAG,OAAO,eAAe;AAAA,MACtC,YAAY,OAAO;AAAA,MACnB,MAAM,OAAO;AAAA,MACb,aAAa,OAAO;AAAA,MACpB,UAAU,OAAO;AAAA,MACjB,MAAM,OAAO;AAAA,MACb,cAAc,OAAO;AAAA,IACvB,CAAC;AACD,OAAG,QAAQ,MAAM;AAAA,EACnB;AACA,QAAM,GAAG,MAAM;AACjB;AAEO,MAAM,QAA2B;AAAA,EACtC,cAAc,OAAO,QAAQ;AAC3B,UAAM,QAAQ,EAAE,UAAU,IAAI,UAAU,gBAAgB,IAAI,eAAe;AAC3E,UAAM,yBAAyB,IAAI,IAAI,KAAK;AAC5C,UAAM,uBAAuB,IAAI,IAAI,KAAK;AAC1C,UAAM,oBAAoB,IAAI,IAAI,KAAK;AACvC,UAAM,qCAAqC,IAAI,IAAI,IAAI,QAAQ;AAC/D,UAAM,8BAA8B,IAAI,EAAE;AAC1C,eAAW,SAAS,6BAA6B;AAC/C,YAAM,sBAAsB,IAAI,IAAI;AAAA,QAClC,UAAU,IAAI;AAAA,QACd,gBAAgB,IAAI;AAAA,QACpB,MAAM;AAAA,QACN,OAAO,MAAM;AAAA,QACb,OAAO,MAAM;AAAA,QACb,OAAO,MAAM;AAAA,QACb,MAAM,MAAM;AAAA,MACd,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,cAAc,OAAO,QAAQ;AAC3B,UAAM,QAAQ,EAAE,UAAU,IAAI,UAAU,gBAAgB,IAAI,eAAe;AAC3E,UAAM,qBAAqB,IAAI,IAAI,IAAI,WAAW,KAAK;AAAA,EACzD;AAAA,EAEA,qBAAqB;AAAA,IACnB,OAAO;AAAA,MACL;AAAA,IACF;AAAA,IACA,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,gBAAQ;",
4
+ "sourcesContent": ["import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { FeatureToggle } from '@open-mercato/core/modules/feature_toggles/data/entities'\nimport {\n ensureCustomerCustomFieldDefinitions,\n seedCustomerDictionaries,\n seedCurrencyDictionary,\n seedCustomerExamples,\n seedDefaultPipeline,\n} from './cli'\nimport { ensureDictionaryEntry } from './commands/shared'\nimport { DEFAULT_CUSTOMER_ROLE_TYPES } from './lib/customerRoleTypes'\n\nconst interactionFeatureToggles = [\n {\n identifier: 'customers.interactions.unified',\n name: 'Unified Interactions',\n description: 'When enabled, interactions use the unified canonical model instead of per-entity activity tracking.',\n category: 'customers',\n type: 'boolean' as const,\n defaultValue: false,\n },\n {\n identifier: 'customers.interactions.legacy-adapters',\n name: 'Interaction Legacy Adapters',\n description: 'When enabled, legacy activity/todo APIs are bridged to the canonical interaction model.',\n category: 'customers',\n type: 'boolean' as const,\n defaultValue: true,\n },\n {\n identifier: 'customers.interactions.external-sync',\n name: 'Interaction External Sync',\n description: 'When enabled, interactions can be synced from external systems (calendars, email providers).',\n category: 'customers',\n type: 'boolean' as const,\n defaultValue: false,\n },\n]\n\nasync function seedInteractionFeatureToggles(em: EntityManager): Promise<void> {\n for (const toggle of interactionFeatureToggles) {\n const existing = await em.findOne(FeatureToggle, { identifier: toggle.identifier, deletedAt: null })\n if (existing) continue\n const entity = em.create(FeatureToggle, {\n identifier: toggle.identifier,\n name: toggle.name,\n description: toggle.description,\n category: toggle.category,\n type: toggle.type,\n defaultValue: toggle.defaultValue,\n })\n em.persist(entity)\n }\n await em.flush()\n}\n\nexport const setup: ModuleSetupConfig = {\n seedDefaults: async (ctx) => {\n const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n await seedCustomerDictionaries(ctx.em, scope)\n await seedCurrencyDictionary(ctx.em, scope)\n await seedDefaultPipeline(ctx.em, scope)\n await ensureCustomerCustomFieldDefinitions(ctx.em, ctx.tenantId)\n await seedInteractionFeatureToggles(ctx.em)\n for (const entry of DEFAULT_CUSTOMER_ROLE_TYPES) {\n await ensureDictionaryEntry(ctx.em, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n kind: 'customer_role_type',\n value: entry.value,\n label: entry.label,\n color: entry.color,\n icon: entry.icon,\n })\n }\n },\n\n seedExamples: async (ctx) => {\n const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n await seedCustomerExamples(ctx.em, ctx.container, scope)\n },\n\n defaultRoleFeatures: {\n admin: [\n 'customers.*',\n ],\n employee: [\n 'customers.people.view',\n 'customers.people.manage',\n 'customers.companies.view',\n 'customers.companies.manage',\n 'customers.deals.view',\n 'customers.deals.manage',\n 'customers.activities.view',\n 'customers.activities.manage',\n 'customers.pipelines.view',\n 'customers.interactions.view',\n 'customers.widgets.todos',\n 'customers.widgets.next-interactions',\n 'customers.widgets.new-customers',\n 'customers.widgets.new-deals',\n 'customers.roles.view',\n 'customers.roles.manage',\n 'customers.email.compose',\n ],\n },\n}\n\nexport default setup\n"],
5
+ "mappings": "AAEA,SAAS,qBAAqB;AAC9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,6BAA6B;AACtC,SAAS,mCAAmC;AAE5C,MAAM,4BAA4B;AAAA,EAChC;AAAA,IACE,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AACF;AAEA,eAAe,8BAA8B,IAAkC;AAC7E,aAAW,UAAU,2BAA2B;AAC9C,UAAM,WAAW,MAAM,GAAG,QAAQ,eAAe,EAAE,YAAY,OAAO,YAAY,WAAW,KAAK,CAAC;AACnG,QAAI,SAAU;AACd,UAAM,SAAS,GAAG,OAAO,eAAe;AAAA,MACtC,YAAY,OAAO;AAAA,MACnB,MAAM,OAAO;AAAA,MACb,aAAa,OAAO;AAAA,MACpB,UAAU,OAAO;AAAA,MACjB,MAAM,OAAO;AAAA,MACb,cAAc,OAAO;AAAA,IACvB,CAAC;AACD,OAAG,QAAQ,MAAM;AAAA,EACnB;AACA,QAAM,GAAG,MAAM;AACjB;AAEO,MAAM,QAA2B;AAAA,EACtC,cAAc,OAAO,QAAQ;AAC3B,UAAM,QAAQ,EAAE,UAAU,IAAI,UAAU,gBAAgB,IAAI,eAAe;AAC3E,UAAM,yBAAyB,IAAI,IAAI,KAAK;AAC5C,UAAM,uBAAuB,IAAI,IAAI,KAAK;AAC1C,UAAM,oBAAoB,IAAI,IAAI,KAAK;AACvC,UAAM,qCAAqC,IAAI,IAAI,IAAI,QAAQ;AAC/D,UAAM,8BAA8B,IAAI,EAAE;AAC1C,eAAW,SAAS,6BAA6B;AAC/C,YAAM,sBAAsB,IAAI,IAAI;AAAA,QAClC,UAAU,IAAI;AAAA,QACd,gBAAgB,IAAI;AAAA,QACpB,MAAM;AAAA,QACN,OAAO,MAAM;AAAA,QACb,OAAO,MAAM;AAAA,QACb,OAAO,MAAM;AAAA,QACb,MAAM,MAAM;AAAA,MACd,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,cAAc,OAAO,QAAQ;AAC3B,UAAM,QAAQ,EAAE,UAAU,IAAI,UAAU,gBAAgB,IAAI,eAAe;AAC3E,UAAM,qBAAqB,IAAI,IAAI,IAAI,WAAW,KAAK;AAAA,EACzD;AAAA,EAEA,qBAAqB;AAAA,IACnB,OAAO;AAAA,MACL;AAAA,IACF;AAAA,IACA,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,gBAAQ;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,12 @@
1
+ import linkChannelMessageHandler from "../lib/link-channel-message-handler.js";
2
+ const metadata = {
3
+ event: "communication_channels.message.received",
4
+ persistent: true,
5
+ id: "customers:link-channel-message-received"
6
+ };
7
+ var link_channel_message_received_default = linkChannelMessageHandler;
8
+ export {
9
+ link_channel_message_received_default as default,
10
+ metadata
11
+ };
12
+ //# sourceMappingURL=link-channel-message-received.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/customers/subscribers/link-channel-message-received.ts"],
4
+ "sourcesContent": ["/**\n * Persistent subscriber for `communication_channels.message.received`.\n *\n * When an inbound email arrives, resolves People by address from the linked\n * MessageChannelLink and creates CustomerInteraction rows (one per match).\n * Falls back to threading-inheritance (In-Reply-To chain) when no direct\n * address match is found.\n *\n * Logic lives in `../lib/link-channel-message-handler.ts`; both this file\n * and `link-channel-message-sent.ts` delegate there so the two subscriber\n * registrations can share a single implementation.\n */\nimport linkChannelMessageHandler from '../lib/link-channel-message-handler'\n\nexport const metadata = {\n event: 'communication_channels.message.received',\n persistent: true,\n id: 'customers:link-channel-message-received',\n}\n\nexport default linkChannelMessageHandler\n"],
5
+ "mappings": "AAYA,OAAO,+BAA+B;AAE/B,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAEA,IAAO,wCAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ import linkChannelMessageHandler from "../lib/link-channel-message-handler.js";
2
+ const metadata = {
3
+ event: "communication_channels.message.sent",
4
+ persistent: true,
5
+ id: "customers:link-channel-message-sent"
6
+ };
7
+ var link_channel_message_sent_default = linkChannelMessageHandler;
8
+ export {
9
+ link_channel_message_sent_default as default,
10
+ metadata
11
+ };
12
+ //# sourceMappingURL=link-channel-message-sent.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/customers/subscribers/link-channel-message-sent.ts"],
4
+ "sourcesContent": ["/**\n * Persistent subscriber for `communication_channels.message.sent`.\n *\n * When an outbound email is delivered, resolves People by address (or by the\n * `crmPersonId` hint written by the compose route) and creates CustomerInteraction\n * rows. Reads `crmVisibility` from `channelMetadata` to set the per-row\n * visibility ('private' | 'shared').\n *\n * Logic lives in `../lib/link-channel-message-handler.ts`; both this file\n * and `link-channel-message-received.ts` delegate there so the two subscriber\n * registrations can share a single implementation.\n */\nimport linkChannelMessageHandler from '../lib/link-channel-message-handler'\n\nexport const metadata = {\n event: 'communication_channels.message.sent',\n persistent: true,\n id: 'customers:link-channel-message-sent',\n}\n\nexport default linkChannelMessageHandler\n"],
5
+ "mappings": "AAYA,OAAO,+BAA+B;AAE/B,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAEA,IAAO,oCAAQ;",
6
+ "names": []
7
+ }
@@ -81,6 +81,9 @@ __decorateClass([
81
81
  __decorateClass([
82
82
  Property({ name: "tenant_id", type: "uuid" })
83
83
  ], IntegrationCredentials.prototype, "tenantId", 2);
84
+ __decorateClass([
85
+ Property({ name: "user_id", type: "uuid", nullable: true })
86
+ ], IntegrationCredentials.prototype, "userId", 2);
84
87
  __decorateClass([
85
88
  Property({ name: "created_at", type: Date, onCreate: () => /* @__PURE__ */ new Date() })
86
89
  ], IntegrationCredentials.prototype, "createdAt", 2);
@@ -92,7 +95,11 @@ __decorateClass([
92
95
  ], IntegrationCredentials.prototype, "deletedAt", 2);
93
96
  IntegrationCredentials = __decorateClass([
94
97
  Entity({ tableName: "integration_credentials" }),
95
- Index({ properties: ["integrationId", "organizationId", "tenantId"] })
98
+ Index({ properties: ["integrationId", "organizationId", "tenantId"] }),
99
+ Index({
100
+ name: "integration_credentials_user_lookup_idx",
101
+ expression: `create unique index "integration_credentials_user_lookup_idx" on "integration_credentials" ("integration_id", "organization_id", "tenant_id", "user_id") where "user_id" is not null and "deleted_at" is null`
102
+ })
96
103
  ], IntegrationCredentials);
97
104
  OptionalProps;
98
105
  let IntegrationState = class {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/integrations/data/entities.ts"],
4
- "sourcesContent": ["import { OptionalProps } from '@mikro-orm/core'\nimport { Entity, Index, PrimaryKey, Property } from '@mikro-orm/decorators/legacy'\n\n@Entity({ tableName: 'sync_external_id_mappings' })\n@Index({ properties: ['internalEntityType', 'internalEntityId', 'organizationId'] })\n@Index({ properties: ['integrationId', 'externalId', 'organizationId'] })\nexport class SyncExternalIdMapping {\n [OptionalProps]?: 'syncStatus' | 'lastSyncedAt' | 'createdAt' | 'updatedAt' | 'deletedAt'\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'integration_id', type: 'text' })\n integrationId!: string\n\n @Property({ name: 'internal_entity_type', type: 'text' })\n internalEntityType!: string\n\n @Property({ name: 'internal_entity_id', type: 'uuid' })\n internalEntityId!: string\n\n @Property({ name: 'external_id', type: 'text' })\n externalId!: string\n\n @Property({ name: 'sync_status', type: 'text', default: 'not_synced' })\n syncStatus: 'synced' | 'pending' | 'error' | 'not_synced' = 'not_synced'\n\n @Property({ name: 'last_synced_at', type: Date, nullable: true })\n lastSyncedAt?: Date | null\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'integration_credentials' })\n@Index({ properties: ['integrationId', 'organizationId', 'tenantId'] })\nexport class IntegrationCredentials {\n [OptionalProps]?: 'createdAt' | 'updatedAt' | 'deletedAt'\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'integration_id', type: 'text' })\n integrationId!: string\n\n @Property({ name: 'credentials', type: 'json' })\n credentials!: Record<string, unknown>\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'integration_states' })\n@Index({ properties: ['integrationId', 'organizationId', 'tenantId'] })\nexport class IntegrationState {\n [OptionalProps]?: 'isEnabled' | 'apiVersion' | 'reauthRequired' | 'lastHealthStatus' | 'lastHealthCheckedAt' | 'lastHealthLatencyMs' | 'enabledAt' | 'createdAt' | 'updatedAt' | 'deletedAt'\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'integration_id', type: 'text' })\n integrationId!: string\n\n @Property({ name: 'is_enabled', type: 'boolean', default: true })\n isEnabled: boolean = true\n\n @Property({ name: 'api_version', type: 'text', nullable: true })\n apiVersion?: string | null\n\n @Property({ name: 'reauth_required', type: 'boolean', default: false })\n reauthRequired: boolean = false\n\n @Property({ name: 'last_health_status', type: 'text', nullable: true })\n lastHealthStatus?: 'healthy' | 'degraded' | 'unhealthy' | null\n\n @Property({ name: 'last_health_checked_at', type: Date, nullable: true })\n lastHealthCheckedAt?: Date | null\n\n @Property({ name: 'last_health_latency_ms', type: 'int', nullable: true })\n lastHealthLatencyMs?: number | null\n\n @Property({ name: 'enabled_at', type: Date, nullable: true })\n enabledAt?: Date | null\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'integration_logs' })\n@Index({ properties: ['integrationId', 'organizationId', 'tenantId', 'createdAt'] })\n@Index({ properties: ['level', 'organizationId', 'tenantId', 'createdAt'] })\nexport class IntegrationLog {\n [OptionalProps]?: 'runId' | 'scopeEntityType' | 'scopeEntityId' | 'code' | 'payload' | 'createdAt'\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'integration_id', type: 'text' })\n integrationId!: string\n\n @Property({ name: 'run_id', type: 'uuid', nullable: true })\n runId?: string | null\n\n @Property({ name: 'scope_entity_type', type: 'text', nullable: true })\n scopeEntityType?: string | null\n\n @Property({ name: 'scope_entity_id', type: 'uuid', nullable: true })\n scopeEntityId?: string | null\n\n @Property({ name: 'level', type: 'text' })\n level!: 'info' | 'warn' | 'error'\n\n @Property({ name: 'message', type: 'text' })\n message!: string\n\n @Property({ name: 'code', type: 'text', nullable: true })\n code?: string | null\n\n @Property({ name: 'payload', type: 'json', nullable: true })\n payload?: Record<string, unknown> | null\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { OptionalProps } from '@mikro-orm/core'\nimport { Entity, Index, PrimaryKey, Property } from '@mikro-orm/decorators/legacy'\n\n@Entity({ tableName: 'sync_external_id_mappings' })\n@Index({ properties: ['internalEntityType', 'internalEntityId', 'organizationId'] })\n@Index({ properties: ['integrationId', 'externalId', 'organizationId'] })\nexport class SyncExternalIdMapping {\n [OptionalProps]?: 'syncStatus' | 'lastSyncedAt' | 'createdAt' | 'updatedAt' | 'deletedAt'\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'integration_id', type: 'text' })\n integrationId!: string\n\n @Property({ name: 'internal_entity_type', type: 'text' })\n internalEntityType!: string\n\n @Property({ name: 'internal_entity_id', type: 'uuid' })\n internalEntityId!: string\n\n @Property({ name: 'external_id', type: 'text' })\n externalId!: string\n\n @Property({ name: 'sync_status', type: 'text', default: 'not_synced' })\n syncStatus: 'synced' | 'pending' | 'error' | 'not_synced' = 'not_synced'\n\n @Property({ name: 'last_synced_at', type: Date, nullable: true })\n lastSyncedAt?: Date | null\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'integration_credentials' })\n@Index({ properties: ['integrationId', 'organizationId', 'tenantId'] })\n@Index({\n name: 'integration_credentials_user_lookup_idx',\n expression:\n `create unique index \"integration_credentials_user_lookup_idx\" on \"integration_credentials\" (\"integration_id\", \"organization_id\", \"tenant_id\", \"user_id\") where \"user_id\" is not null and \"deleted_at\" is null`,\n})\nexport class IntegrationCredentials {\n [OptionalProps]?: 'createdAt' | 'updatedAt' | 'deletedAt' | 'userId'\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'integration_id', type: 'text' })\n integrationId!: string\n\n @Property({ name: 'credentials', type: 'json' })\n credentials!: Record<string, unknown>\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n /**\n * Per-user secret scoping (additive \u2014 added by the email integration spec).\n *\n * NULL = tenant-wide secret (existing behaviour, e.g. shared WhatsApp Business token).\n * Set = per-user secret (e.g. Jane's personal Gmail OAuth refresh token).\n *\n * Cross-module link to `auth:user` is declared in\n * `packages/core/src/modules/communication_channels/data/extensions.ts` via\n * `EntityExtension` \u2014 no raw FOREIGN KEY constraint (root `AGENTS.md` rule:\n * \"No direct ORM relationships between modules\"). App-layer query callers\n * MUST scope by `user_id` on every per-user credential read.\n */\n @Property({ name: 'user_id', type: 'uuid', nullable: true })\n userId?: string | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'integration_states' })\n@Index({ properties: ['integrationId', 'organizationId', 'tenantId'] })\nexport class IntegrationState {\n [OptionalProps]?: 'isEnabled' | 'apiVersion' | 'reauthRequired' | 'lastHealthStatus' | 'lastHealthCheckedAt' | 'lastHealthLatencyMs' | 'enabledAt' | 'createdAt' | 'updatedAt' | 'deletedAt'\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'integration_id', type: 'text' })\n integrationId!: string\n\n @Property({ name: 'is_enabled', type: 'boolean', default: true })\n isEnabled: boolean = true\n\n @Property({ name: 'api_version', type: 'text', nullable: true })\n apiVersion?: string | null\n\n @Property({ name: 'reauth_required', type: 'boolean', default: false })\n reauthRequired: boolean = false\n\n @Property({ name: 'last_health_status', type: 'text', nullable: true })\n lastHealthStatus?: 'healthy' | 'degraded' | 'unhealthy' | null\n\n @Property({ name: 'last_health_checked_at', type: Date, nullable: true })\n lastHealthCheckedAt?: Date | null\n\n @Property({ name: 'last_health_latency_ms', type: 'int', nullable: true })\n lastHealthLatencyMs?: number | null\n\n @Property({ name: 'enabled_at', type: Date, nullable: true })\n enabledAt?: Date | null\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'integration_logs' })\n@Index({ properties: ['integrationId', 'organizationId', 'tenantId', 'createdAt'] })\n@Index({ properties: ['level', 'organizationId', 'tenantId', 'createdAt'] })\nexport class IntegrationLog {\n [OptionalProps]?: 'runId' | 'scopeEntityType' | 'scopeEntityId' | 'code' | 'payload' | 'createdAt'\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'integration_id', type: 'text' })\n integrationId!: string\n\n @Property({ name: 'run_id', type: 'uuid', nullable: true })\n runId?: string | null\n\n @Property({ name: 'scope_entity_type', type: 'text', nullable: true })\n scopeEntityType?: string | null\n\n @Property({ name: 'scope_entity_id', type: 'uuid', nullable: true })\n scopeEntityId?: string | null\n\n @Property({ name: 'level', type: 'text' })\n level!: 'info' | 'warn' | 'error'\n\n @Property({ name: 'message', type: 'text' })\n message!: string\n\n @Property({ name: 'code', type: 'text', nullable: true })\n code?: string | null\n\n @Property({ name: 'payload', type: 'json', nullable: true })\n payload?: Record<string, unknown> | null\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -29,6 +29,20 @@ function normalizeCredentialsRecord(value) {
29
29
  const parsed = parseDecryptedFieldValue(value);
30
30
  return isRecordValue(parsed) ? parsed : {};
31
31
  }
32
+ function buildCredentialsFilter(integrationId, scope) {
33
+ const base = {
34
+ integrationId,
35
+ organizationId: scope.organizationId,
36
+ tenantId: scope.tenantId,
37
+ deletedAt: null
38
+ };
39
+ if (scope.userId) {
40
+ base.userId = scope.userId;
41
+ } else {
42
+ base.userId = null;
43
+ }
44
+ return base;
45
+ }
32
46
  function createCredentialsService(em) {
33
47
  const credentialsEncryptionSpec = [{ field: "credentials" }];
34
48
  async function ensureCredentialsEncryptionMap(scope) {
@@ -89,18 +103,22 @@ function createCredentialsService(em) {
89
103
  }
90
104
  return {
91
105
  async getRaw(integrationId, scope) {
92
- const row = await findOneWithDecryption(
106
+ let row = await findOneWithDecryption(
93
107
  em,
94
108
  IntegrationCredentials,
95
- {
96
- integrationId,
97
- organizationId: scope.organizationId,
98
- tenantId: scope.tenantId,
99
- deletedAt: null
100
- },
109
+ buildCredentialsFilter(integrationId, scope),
101
110
  void 0,
102
111
  scope
103
112
  );
113
+ if (!row && scope.userId) {
114
+ row = await findOneWithDecryption(
115
+ em,
116
+ IntegrationCredentials,
117
+ buildCredentialsFilter(integrationId, { ...scope, userId: null }),
118
+ void 0,
119
+ scope
120
+ );
121
+ }
104
122
  if (!row) return null;
105
123
  return decryptCredentialsBlob(row.credentials, scope);
106
124
  },
@@ -117,12 +135,7 @@ function createCredentialsService(em) {
117
135
  const row = await findOneWithDecryption(
118
136
  em,
119
137
  IntegrationCredentials,
120
- {
121
- integrationId,
122
- organizationId: scope.organizationId,
123
- tenantId: scope.tenantId,
124
- deletedAt: null
125
- },
138
+ buildCredentialsFilter(integrationId, scope),
126
139
  void 0,
127
140
  scope
128
141
  );
@@ -135,7 +148,8 @@ function createCredentialsService(em) {
135
148
  integrationId,
136
149
  credentials: encryptedCredentials,
137
150
  organizationId: scope.organizationId,
138
- tenantId: scope.tenantId
151
+ tenantId: scope.tenantId,
152
+ ...scope.userId ? { userId: scope.userId } : {}
139
153
  });
140
154
  await em.persist(created).flush();
141
155
  },
@@ -158,6 +172,7 @@ function createCredentialsService(em) {
158
172
  }
159
173
  export {
160
174
  CredentialsEncryptionUnavailableError,
175
+ buildCredentialsFilter,
161
176
  createCredentialsService,
162
177
  isCredentialsEncryptionUnavailableError
163
178
  };