@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
@@ -45,8 +45,13 @@ export class SyncExternalIdMapping {
45
45
 
46
46
  @Entity({ tableName: 'integration_credentials' })
47
47
  @Index({ properties: ['integrationId', 'organizationId', 'tenantId'] })
48
+ @Index({
49
+ name: 'integration_credentials_user_lookup_idx',
50
+ expression:
51
+ `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`,
52
+ })
48
53
  export class IntegrationCredentials {
49
- [OptionalProps]?: 'createdAt' | 'updatedAt' | 'deletedAt'
54
+ [OptionalProps]?: 'createdAt' | 'updatedAt' | 'deletedAt' | 'userId'
50
55
  @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
51
56
  id!: string
52
57
 
@@ -62,6 +67,21 @@ export class IntegrationCredentials {
62
67
  @Property({ name: 'tenant_id', type: 'uuid' })
63
68
  tenantId!: string
64
69
 
70
+ /**
71
+ * Per-user secret scoping (additive — added by the email integration spec).
72
+ *
73
+ * NULL = tenant-wide secret (existing behaviour, e.g. shared WhatsApp Business token).
74
+ * Set = per-user secret (e.g. Jane's personal Gmail OAuth refresh token).
75
+ *
76
+ * Cross-module link to `auth:user` is declared in
77
+ * `packages/core/src/modules/communication_channels/data/extensions.ts` via
78
+ * `EntityExtension` — no raw FOREIGN KEY constraint (root `AGENTS.md` rule:
79
+ * "No direct ORM relationships between modules"). App-layer query callers
80
+ * MUST scope by `user_id` on every per-user credential read.
81
+ */
82
+ @Property({ name: 'user_id', type: 'uuid', nullable: true })
83
+ userId?: string | null
84
+
65
85
  @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
66
86
  createdAt: Date = new Date()
67
87
 
@@ -39,6 +39,34 @@ function normalizeCredentialsRecord(value: unknown): Record<string, unknown> {
39
39
  return isRecordValue(parsed) ? parsed : {}
40
40
  }
41
41
 
42
+ /**
43
+ * Build the where-filter for credential lookups.
44
+ *
45
+ * Per-user scoping (added 2026-05-26): when `scope.userId` is set, the filter
46
+ * matches the row owned by that user — different users on the same tenant get
47
+ * their OWN row for the same provider. When `scope.userId` is `undefined` /
48
+ * `null`, the filter matches tenant-wide credentials (existing behaviour,
49
+ * e.g. shared Stripe/Akeneo API keys).
50
+ *
51
+ * The partial unique index `integration_credentials_user_lookup_idx` enforces
52
+ * uniqueness across `(integration_id, organization_id, tenant_id, user_id)`
53
+ * when `user_id IS NOT NULL`.
54
+ */
55
+ export function buildCredentialsFilter(integrationId: string, scope: IntegrationScope) {
56
+ const base = {
57
+ integrationId,
58
+ organizationId: scope.organizationId,
59
+ tenantId: scope.tenantId,
60
+ deletedAt: null,
61
+ } as Record<string, unknown>
62
+ if (scope.userId) {
63
+ base.userId = scope.userId
64
+ } else {
65
+ base.userId = null
66
+ }
67
+ return base
68
+ }
69
+
42
70
  export function createCredentialsService(em: EntityManager) {
43
71
  const credentialsEncryptionSpec = [{ field: 'credentials' }]
44
72
 
@@ -118,18 +146,30 @@ export function createCredentialsService(em: EntityManager) {
118
146
 
119
147
  return {
120
148
  async getRaw(integrationId: string, scope: IntegrationScope): Promise<Record<string, unknown> | null> {
121
- const row = await findOneWithDecryption(
149
+ let row = await findOneWithDecryption(
122
150
  em,
123
151
  IntegrationCredentials,
124
- {
125
- integrationId,
126
- organizationId: scope.organizationId,
127
- tenantId: scope.tenantId,
128
- deletedAt: null,
129
- },
152
+ buildCredentialsFilter(integrationId, scope),
130
153
  undefined,
131
154
  scope,
132
155
  )
156
+ // Spec 2026-05-21 (email-integration-foundation) "Hub credentials store":
157
+ // per-user secrets resolve as `WHERE user_id = currentUser.id OR user_id IS NULL`.
158
+ // A user-scoped read of a TENANT-WIDE integration (sync_excel, Stripe, Akeneo,
159
+ // S3, the channel OAuth *client* config) MUST still find the shared
160
+ // `user_id = NULL` row — the per-user row takes precedence, and we only fall
161
+ // back to the tenant-wide row when the user has none of their own. Writes stay
162
+ // strict (`save` uses the unmodified filter) so a per-user save never clobbers
163
+ // the shared credential.
164
+ if (!row && scope.userId) {
165
+ row = await findOneWithDecryption(
166
+ em,
167
+ IntegrationCredentials,
168
+ buildCredentialsFilter(integrationId, { ...scope, userId: null }),
169
+ undefined,
170
+ scope,
171
+ )
172
+ }
133
173
  if (!row) return null
134
174
  return decryptCredentialsBlob(row.credentials, scope)
135
175
  },
@@ -150,12 +190,7 @@ export function createCredentialsService(em: EntityManager) {
150
190
  const row = await findOneWithDecryption(
151
191
  em,
152
192
  IntegrationCredentials,
153
- {
154
- integrationId,
155
- organizationId: scope.organizationId,
156
- tenantId: scope.tenantId,
157
- deletedAt: null,
158
- },
193
+ buildCredentialsFilter(integrationId, scope),
159
194
  undefined,
160
195
  scope,
161
196
  )
@@ -171,6 +206,7 @@ export function createCredentialsService(em: EntityManager) {
171
206
  credentials: encryptedCredentials,
172
207
  organizationId: scope.organizationId,
173
208
  tenantId: scope.tenantId,
209
+ ...(scope.userId ? { userId: scope.userId } : {}),
174
210
  })
175
211
  await em.persist(created).flush()
176
212
  },
@@ -135,6 +135,22 @@
135
135
  "comment": null,
136
136
  "enumItems": [],
137
137
  "mappedType": "datetime"
138
+ },
139
+ "user_id": {
140
+ "name": "user_id",
141
+ "type": "uuid",
142
+ "unsigned": false,
143
+ "autoincrement": false,
144
+ "primary": false,
145
+ "nullable": true,
146
+ "unique": false,
147
+ "length": null,
148
+ "precision": null,
149
+ "scale": null,
150
+ "default": null,
151
+ "comment": null,
152
+ "enumItems": [],
153
+ "mappedType": "uuid"
138
154
  }
139
155
  },
140
156
  "indexes": [
@@ -159,6 +175,15 @@
159
175
  "keyName": "integration_credentials_pkey",
160
176
  "primary": true,
161
177
  "unique": true
178
+ },
179
+ {
180
+ "columnNames": [],
181
+ "composite": false,
182
+ "constraint": false,
183
+ "keyName": "integration_credentials_user_lookup_idx",
184
+ "primary": false,
185
+ "unique": true,
186
+ "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"
162
187
  }
163
188
  ],
164
189
  "checks": [],
@@ -901,4 +926,4 @@
901
926
  ],
902
927
  "views": [],
903
928
  "nativeEnums": {}
904
- }
929
+ }
@@ -0,0 +1,15 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+
3
+ export class Migration20260526154136_integrations extends Migration {
4
+
5
+ override up(): void | Promise<void> {
6
+ this.addSql(`alter table "integration_credentials" add "user_id" uuid null;`);
7
+ this.addSql(`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;`);
8
+ }
9
+
10
+ override down(): void | Promise<void> {
11
+ this.addSql(`drop index "integration_credentials_user_lookup_idx";`);
12
+ this.addSql(`alter table "integration_credentials" drop column "user_id";`);
13
+ }
14
+
15
+ }
@@ -125,6 +125,10 @@ const composeCommandSchema = composeMessageSchema.safeExtend({
125
125
  tenantId: scopeSchema.shape.tenantId,
126
126
  organizationId: scopeSchema.shape.organizationId,
127
127
  userId: scopeSchema.shape.userId,
128
+ // Optional dedup key (inbound email ingest sets it; other callers leave it
129
+ // undefined). When set, a re-issued compose returns the first message instead
130
+ // of creating a duplicate.
131
+ idempotencyKey: z.string().min(1).max(255).optional(),
128
132
  })
129
133
 
130
134
  const updateDraftCommandSchema = updateDraftSchema.safeExtend({
@@ -207,7 +211,49 @@ async function requireMessageById(
207
211
  return message
208
212
  }
209
213
 
210
- const composeMessageCommand: CommandHandler<unknown, { id: string; threadId: string | null; externalEmail: string | null; isDraft: boolean; recipientUserIds: string[] }> = {
214
+ function isUniqueViolation(err: unknown): boolean {
215
+ if (!err || typeof err !== 'object') return false
216
+ const code = (err as { code?: string }).code
217
+ if (code === '23505') return true
218
+ const message = (err as { message?: string }).message
219
+ return typeof message === 'string' && /duplicate key value|unique constraint/i.test(message)
220
+ }
221
+
222
+ type ComposeMessageResult = {
223
+ id: string
224
+ threadId: string | null
225
+ externalEmail: string | null
226
+ isDraft: boolean
227
+ recipientUserIds: string[]
228
+ /**
229
+ * True when this was an idempotent replay — an existing message was returned
230
+ * and nothing was written. Signals `buildLog` to skip the audit/undo entry.
231
+ */
232
+ deduplicated?: boolean
233
+ }
234
+
235
+ async function buildComposeResultFromExisting(
236
+ em: EntityManager,
237
+ message: Message,
238
+ ): Promise<ComposeMessageResult> {
239
+ const recipients = await findWithDecryption(
240
+ em,
241
+ MessageRecipient,
242
+ { messageId: message.id, deletedAt: null },
243
+ undefined,
244
+ { tenantId: message.tenantId, organizationId: message.organizationId ?? null },
245
+ )
246
+ return {
247
+ id: message.id,
248
+ threadId: message.threadId ?? null,
249
+ externalEmail: message.externalEmail ?? null,
250
+ isDraft: message.isDraft,
251
+ recipientUserIds: recipients.map((recipient) => recipient.recipientUserId),
252
+ deduplicated: true,
253
+ }
254
+ }
255
+
256
+ const composeMessageCommand: CommandHandler<unknown, { id: string; threadId: string | null; externalEmail: string | null; isDraft: boolean; recipientUserIds: string[]; deduplicated?: boolean }> = {
211
257
  id: 'messages.messages.compose',
212
258
  async execute(rawInput, ctx) {
213
259
  const input = composeCommandSchema.parse(rawInput)
@@ -217,19 +263,42 @@ const composeMessageCommand: CommandHandler<unknown, { id: string; threadId: str
217
263
  }
218
264
 
219
265
  const em = (ctx.container.resolve('em') as EntityManager).fork()
266
+ const scope = { tenantId: input.tenantId, organizationId: input.organizationId }
267
+
268
+ // Idempotency fast-path: a retried inbound-email ingest re-issues compose
269
+ // for the same source message (the first attempt committed the message but a
270
+ // downstream transient failure rolled the ingest back). Return that message
271
+ // so the retry cannot create a duplicate.
272
+ if (input.idempotencyKey) {
273
+ const existing = await findOneWithDecryption(
274
+ em,
275
+ Message,
276
+ { tenantId: input.tenantId, idempotencyKey: input.idempotencyKey },
277
+ undefined,
278
+ scope,
279
+ )
280
+ if (existing) return buildComposeResultFromExisting(em, existing)
281
+ }
282
+
220
283
  let messageId = ''
221
284
  let responseThreadId: string | null = null
222
285
  let responseExternalEmail: string | null = null
223
286
 
224
- await em.transactional(async (trx) => {
287
+ const composeTx = em.transactional(async (trx) => {
225
288
  const threadId = input.parentMessageId
226
289
  ? (
227
- await trx.findOne(Message, {
228
- id: input.parentMessageId,
229
- tenantId: input.tenantId,
230
- organizationId: input.organizationId,
231
- deletedAt: null,
232
- })
290
+ await findOneWithDecryption(
291
+ trx,
292
+ Message,
293
+ {
294
+ id: input.parentMessageId,
295
+ tenantId: input.tenantId,
296
+ organizationId: input.organizationId,
297
+ deletedAt: null,
298
+ },
299
+ undefined,
300
+ scope,
301
+ )
233
302
  )?.threadId ?? input.parentMessageId
234
303
  : undefined
235
304
 
@@ -254,6 +323,7 @@ const composeMessageCommand: CommandHandler<unknown, { id: string; threadId: str
254
323
  sentAt: input.isDraft ? null : new Date(),
255
324
  actionData: input.actionData as MessageActionData | undefined,
256
325
  sendViaEmail,
326
+ idempotencyKey: input.idempotencyKey ?? null,
257
327
  tenantId: input.tenantId,
258
328
  organizationId: input.organizationId,
259
329
  })
@@ -313,6 +383,24 @@ const composeMessageCommand: CommandHandler<unknown, { id: string; threadId: str
313
383
  responseThreadId = message.threadId ?? null
314
384
  responseExternalEmail = message.externalEmail ?? null
315
385
  })
386
+ try {
387
+ await composeTx
388
+ } catch (err) {
389
+ // Lost a concurrent race on the same idempotency key — return the message
390
+ // the winning compose created. A propagated 23505 would otherwise be
391
+ // classified permanent and dead-letter the inbound mail.
392
+ if (input.idempotencyKey && isUniqueViolation(err)) {
393
+ const existing = await findOneWithDecryption(
394
+ em.fork(),
395
+ Message,
396
+ { tenantId: input.tenantId, idempotencyKey: input.idempotencyKey },
397
+ undefined,
398
+ scope,
399
+ )
400
+ if (existing) return buildComposeResultFromExisting(em.fork(), existing)
401
+ }
402
+ throw err
403
+ }
316
404
 
317
405
  if (!input.isDraft) {
318
406
  await emitMessageSentEvent(ctx.container, {
@@ -345,6 +433,11 @@ const composeMessageCommand: CommandHandler<unknown, { id: string; threadId: str
345
433
  return loadMessageAggregateSnapshot(em, result.id)
346
434
  },
347
435
  buildLog: async ({ input, result, snapshots }) => {
436
+ // Idempotent replay: execute returned a pre-existing message without writing
437
+ // anything. Skip the audit entry entirely — logging it as a fresh "Compose
438
+ // message" would both misrepresent the dedup and expose an undo that
439
+ // soft-deletes a legitimately-received inbound message.
440
+ if (result.deduplicated) return { skipLog: true }
348
441
  const parsed = composeCommandSchema.parse(input)
349
442
  return {
350
443
  actionLabel: parsed.isDraft ? 'Create draft message' : 'Compose message',
@@ -2,12 +2,29 @@
2
2
 
3
3
  import { useRouter } from 'next/navigation'
4
4
  import { MessageComposer } from '@open-mercato/ui/backend/messages'
5
+ // UMES extension surface — compose page injection spot (SPEC-045d §9.3a).
6
+ // Channel provider packages inject "composer capabilities" widgets here
7
+ // (character limit warnings, channel format selector, attachment scoping, etc.).
8
+ import { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'
5
9
 
6
10
  export function ComposeMessagePageClient() {
7
11
  const router = useRouter()
8
12
 
9
13
  return (
10
14
  <div className="space-y-4">
15
+ {/*
16
+ Standalone widget mount above the composer — NOT CrudForm field
17
+ resolution. This page is not a CrudForm, so the `crud-form:*:fields`
18
+ field-event pipeline (onFieldChange, value transformers, etc.) does
19
+ not apply here. Provider packages render composer-capability widgets
20
+ (character-limit warnings, channel format selectors, attachment
21
+ scoping) into this spot purely as additional UI siblings.
22
+ */}
23
+ <InjectionSpot
24
+ spotId="crud-form:messages:message:fields"
25
+ context={{ form: 'compose' }}
26
+ data={{}}
27
+ />
11
28
  <MessageComposer
12
29
  inline
13
30
  variant="compose"
@@ -2,9 +2,12 @@
2
2
 
3
3
  import * as React from 'react'
4
4
  import { MessageComposer } from '@open-mercato/ui/backend/messages'
5
+ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
5
6
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
6
7
  import { Button } from '@open-mercato/ui/primitives/button'
7
8
  import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
9
+ // UMES extension surface — message detail injection spots (SPEC-045d §9.3a)
10
+ import { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'
8
11
  import {
9
12
  getMessageUiComponentRegistry,
10
13
  } from './utils/typeUiRegistry'
@@ -29,6 +32,7 @@ function MessageConversationDetailItem({
29
32
  onToggle,
30
33
  onReply,
31
34
  onForward,
35
+ userFeatures,
32
36
  }: {
33
37
  messageId: string
34
38
  isCollapsible: boolean
@@ -36,6 +40,7 @@ function MessageConversationDetailItem({
36
40
  onToggle: () => void
37
41
  onReply: (messageId: string) => void
38
42
  onForward: (messageId: string) => void
43
+ userFeatures: string[]
39
44
  }) {
40
45
  const state = useMessageDetails(messageId)
41
46
  const messageUiRegistry = React.useMemo(() => getMessageUiComponentRegistry(), [])
@@ -87,7 +92,23 @@ function MessageConversationDetailItem({
87
92
  ContentComponent={ContentComponent}
88
93
  />
89
94
 
95
+ {/* UMES — channel payload renderer mounts here (channel-linked emails, Slack blocks, etc.). */}
96
+ <InjectionSpot
97
+ spotId="detail:messages:message:body:after"
98
+ context={{ messageId }}
99
+ data={state.detail}
100
+ />
101
+
90
102
  <MessageDetailMetaSection detail={state.detail} />
103
+
104
+ {/* UMES — channel sidebar widgets (channel info panel, contact preview, delivery status).
105
+ `userFeatures` lets feature-gated widgets (e.g. the channel reassignment editor,
106
+ gated on `communication_channels.assign`) render their controls for permitted users. */}
107
+ <InjectionSpot
108
+ spotId="detail:messages:message:sidebar"
109
+ context={{ messageId, userFeatures }}
110
+ data={state.detail}
111
+ />
91
112
  </section>
92
113
 
93
114
  <MessageDetailActionsSection
@@ -138,6 +159,27 @@ function MessageConversationDetailItem({
138
159
 
139
160
  export function MessageDetailPageClient({ id }: { id: string }) {
140
161
  const state = useMessageDetails(id)
162
+ // Resolve the viewer's feature grants once and forward them into the sidebar
163
+ // injection spot so feature-gated widgets (channel reassignment editor) can
164
+ // render their controls. Without this, hub widgets that gate on
165
+ // `context.userFeatures` (e.g. `communication_channels.assign`) stay read-only.
166
+ const [userFeatures, setUserFeatures] = React.useState<string[]>([])
167
+ React.useEffect(() => {
168
+ let cancelled = false
169
+ void (async () => {
170
+ const res = await apiCall<{ ok: boolean; granted?: string[] }>('/api/auth/feature-check', {
171
+ method: 'POST',
172
+ headers: { 'content-type': 'application/json' },
173
+ body: JSON.stringify({ features: ['communication_channels.assign'] }),
174
+ }).catch(() => null)
175
+ if (!cancelled && res?.ok && Array.isArray(res.result?.granted)) {
176
+ setUserFeatures(res.result.granted)
177
+ }
178
+ })()
179
+ return () => {
180
+ cancelled = true
181
+ }
182
+ }, [])
141
183
  const [activeInlineComposer, setActiveInlineComposer] = React.useState<{
142
184
  variant: 'reply' | 'forward'
143
185
  messageId: string
@@ -234,6 +276,7 @@ export function MessageDetailPageClient({ id }: { id: string }) {
234
276
  <MessageConversationDetailItem
235
277
  key={item.id}
236
278
  messageId={item.id}
279
+ userFeatures={userFeatures}
237
280
  isCollapsible={!isForcedExpanded}
238
281
  isExpanded
239
282
  onToggle={() => state.toggleConversationItem(item.id)}
@@ -362,6 +362,10 @@ export function MessagesInboxPageClient() {
362
362
  <div className="space-y-4">
363
363
  <DataTable
364
364
  title={t('messages.title', 'Messages')}
365
+ // UMES extension surface — opt into widget injection at:
366
+ // data-table:messages:columns / :row-actions / :bulk-actions / :filters / :toolbar / :search-trailing
367
+ // (SPEC-045d §9.3a — communication_channels hub renders channel badge + delivery status here)
368
+ extensionTableId="messages"
365
369
  columns={columns}
366
370
  data={rows}
367
371
  bulkActions={bulkActions}
@@ -30,6 +30,11 @@ export type MessageActionData = {
30
30
  @Index({ name: 'messages_type_idx', properties: ['type', 'tenantId'] })
31
31
  @Index({ name: 'messages_tenant_idx', properties: ['tenantId', 'organizationId'] })
32
32
  @Index({ name: 'messages_external_email_hash_idx', properties: ['externalEmailHash'] })
33
+ @Index({
34
+ name: 'messages_idempotency_key_uq',
35
+ expression:
36
+ 'create unique index "messages_idempotency_key_uq" on "messages" ("tenant_id", "idempotency_key") where "idempotency_key" is not null',
37
+ })
33
38
  export class Message {
34
39
  [OptionalProps]?: 'type' | 'status' | 'priority' | 'bodyFormat' | 'isDraft' | 'createdAt' | 'updatedAt'
35
40
 
@@ -117,6 +122,12 @@ export class Message {
117
122
  @Property({ name: 'external_email_hash', type: 'text', nullable: true })
118
123
  externalEmailHash?: string | null
119
124
 
125
+ // Stable per-source idempotency key (inbound email = channel + provider
126
+ // message id). Lets a retried `compose` return the message created by the
127
+ // first attempt instead of duplicating it. Partial-unique per tenant.
128
+ @Property({ name: 'idempotency_key', type: 'text', nullable: true })
129
+ idempotencyKey?: string | null
130
+
120
131
  @Property({ name: 'external_name', type: 'text', nullable: true })
121
132
  externalName?: string | null
122
133
 
@@ -270,6 +270,15 @@
270
270
  "nullable": true,
271
271
  "mappedType": "text"
272
272
  },
273
+ "idempotency_key": {
274
+ "name": "idempotency_key",
275
+ "type": "text",
276
+ "unsigned": false,
277
+ "autoincrement": false,
278
+ "primary": false,
279
+ "nullable": true,
280
+ "mappedType": "text"
281
+ },
273
282
  "external_name": {
274
283
  "name": "external_name",
275
284
  "type": "text",
@@ -365,6 +374,15 @@
365
374
  "primary": false,
366
375
  "unique": false
367
376
  },
377
+ {
378
+ "columnNames": [],
379
+ "composite": false,
380
+ "constraint": false,
381
+ "keyName": "messages_idempotency_key_uq",
382
+ "primary": false,
383
+ "unique": false,
384
+ "expression": "create unique index \"messages_idempotency_key_uq\" on \"messages\" (\"tenant_id\", \"idempotency_key\") where \"idempotency_key\" is not null"
385
+ },
368
386
  {
369
387
  "keyName": "messages_pkey",
370
388
  "columnNames": [
@@ -0,0 +1,15 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+
3
+ export class Migration20260531130000 extends Migration {
4
+
5
+ override async up(): Promise<void> {
6
+ this.addSql(`alter table "messages" add column if not exists "idempotency_key" text null;`);
7
+ this.addSql(`create unique index if not exists "messages_idempotency_key_uq" on "messages" ("tenant_id", "idempotency_key") where "idempotency_key" is not null;`);
8
+ }
9
+
10
+ override async down(): Promise<void> {
11
+ this.addSql(`drop index if exists "messages_idempotency_key_uq";`);
12
+ this.addSql(`alter table "messages" drop column if exists "idempotency_key";`);
13
+ }
14
+
15
+ }
@@ -0,0 +1,29 @@
1
+ import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'
2
+
3
+ /**
4
+ * Messages module — widget injection table.
5
+ *
6
+ * The messages module currently does not register any widgets INTO other modules' spots.
7
+ * This file exists primarily to document the spots the messages module EXPOSES for other
8
+ * modules to inject into. The actual `<InjectionSpot>` placements live in the messages
9
+ * pages themselves (see `components/MessagesInboxPageClient.tsx`,
10
+ * `components/MessageDetailPageClient.tsx`, `components/ComposeMessagePageClient.tsx`).
11
+ *
12
+ * Exposed spot IDs (additive contract per BACKWARD_COMPATIBILITY.md §6):
13
+ * - `data-table:messages:columns` — auto-wired by `DataTable extensionTableId="messages"`
14
+ * - `data-table:messages:row-actions` — auto-wired by `DataTable extensionTableId="messages"`
15
+ * - `data-table:messages:bulk-actions` — auto-wired by `DataTable extensionTableId="messages"`
16
+ * - `data-table:messages:filters` — auto-wired by `DataTable extensionTableId="messages"`
17
+ * - `data-table:messages:search-trailing` — auto-wired by `DataTable extensionTableId="messages"`
18
+ * - `data-table:messages:toolbar` — auto-wired by `DataTable extensionTableId="messages"`
19
+ * - `detail:messages:message:body:after` — placed in `MessageDetailPageClient.tsx`
20
+ * - `detail:messages:message:sidebar` — placed in `MessageDetailPageClient.tsx`
21
+ * - `crud-form:messages:message:fields` — placed in `ComposeMessagePageClient.tsx` as a standalone widget mount above the composer (NOT CrudForm field resolution; the compose page is not a CrudForm)
22
+ *
23
+ * The `communication_channels` hub (SPEC-045d) is the primary consumer of these spots,
24
+ * injecting channel badges, channel payload renderers, reaction bars, channel info
25
+ * sidebars, and composer capability adapters.
26
+ */
27
+ export const injectionTable: ModuleInjectionTable = {}
28
+
29
+ export default injectionTable