@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,179 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { EntityManager } from '@mikro-orm/postgresql'
4
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
5
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
7
+ import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
8
+ import type { CommandBus } from '@open-mercato/shared/lib/commands'
9
+ import {
10
+ validateCrudMutationGuard,
11
+ runCrudMutationGuardAfterSuccess,
12
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
13
+ import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
14
+ import { CustomerInteraction } from '../../../../data/entities'
15
+ import type { InteractionUpdateInput } from '../../../../data/validators'
16
+ import { resolveAuthActorId } from '../../../../lib/interactionRequestContext'
17
+ import { emitCustomersEvent } from '../../../../events'
18
+
19
+ export const metadata = {
20
+ path: '/customers/interactions/[id]/visibility',
21
+ PATCH: {
22
+ requireAuth: true,
23
+ requireFeatures: ['customers.email.compose'],
24
+ },
25
+ }
26
+
27
+ const bodySchema = z.object({ visibility: z.enum(['private', 'shared']) }).strict()
28
+
29
+ type RouteContext = { params: Promise<{ id: string }> | { id: string } }
30
+
31
+ export async function PATCH(req: Request, context: RouteContext): Promise<Response> {
32
+ const { id } = await context.params
33
+ if (!z.string().uuid().safeParse(id).success) {
34
+ return NextResponse.json({ error: 'Invalid interaction id' }, { status: 400 })
35
+ }
36
+
37
+ const auth = await getAuthFromRequest(req)
38
+ if (!auth?.sub || !auth?.tenantId) {
39
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
40
+ }
41
+
42
+ let body: z.infer<typeof bodySchema>
43
+ try {
44
+ body = bodySchema.parse(await readJsonSafe(req, null))
45
+ } catch (err) {
46
+ return NextResponse.json(
47
+ { error: err instanceof Error ? err.message : 'Invalid request body' },
48
+ { status: 422 },
49
+ )
50
+ }
51
+
52
+ const container = await createRequestContainer()
53
+ const em = (container.resolve('em') as EntityManager).fork()
54
+ const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
55
+ const organizationId = scope?.selectedId ?? (auth as { orgId?: string | null }).orgId ?? null
56
+ const dscope = { tenantId: auth.tenantId as string, organizationId }
57
+ const userId = resolveAuthActorId(auth)
58
+
59
+ const guardResult = await validateCrudMutationGuard(container, {
60
+ tenantId: auth.tenantId,
61
+ organizationId,
62
+ userId,
63
+ resourceKind: 'customers.interaction',
64
+ resourceId: id,
65
+ operation: 'custom',
66
+ requestMethod: req.method,
67
+ requestHeaders: req.headers,
68
+ })
69
+ if (guardResult && !guardResult.ok) {
70
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
71
+ }
72
+
73
+ const interaction = (await findOneWithDecryption(
74
+ em,
75
+ CustomerInteraction,
76
+ {
77
+ id,
78
+ tenantId: auth.tenantId,
79
+ organizationId,
80
+ deletedAt: null,
81
+ interactionType: 'email',
82
+ } as any,
83
+ undefined,
84
+ dscope,
85
+ )) as { id: string; authorUserId?: string | null; visibility?: string | null } | null
86
+
87
+ if (!interaction) {
88
+ return NextResponse.json({ error: 'Email not found' }, { status: 404 })
89
+ }
90
+
91
+ // Personal mailbox privacy (v1: strict owner-only): ONLY the author may flip
92
+ // their own email's visibility — no admin bypass. Return 404 (not 403) for
93
+ // everyone else so we don't leak the row's existence — this also covers
94
+ // non-authors who cannot see a private email in the first place.
95
+ const isAuthor = !!interaction.authorUserId && interaction.authorUserId === auth.sub
96
+ if (!isAuthor) {
97
+ return NextResponse.json({ error: 'Email not found' }, { status: 404 })
98
+ }
99
+
100
+ // No-op: visibility is already at the requested value.
101
+ if (interaction.visibility === body.visibility) {
102
+ return NextResponse.json({ ok: true, changed: false })
103
+ }
104
+
105
+ const previousVisibility = (interaction.visibility ?? 'private') as 'private' | 'shared'
106
+
107
+ // Route the write through the interactions update command so the change runs
108
+ // the full mutation pipeline — query-index refresh, audit log and undo —
109
+ // instead of a raw em.flush() that would leave the indexed `entity_indexes`
110
+ // doc stale. Authorization (author-only, v1) was already enforced above; the
111
+ // command only owns persistence and side effects.
112
+ const commandBus = container.resolve('commandBus') as CommandBus
113
+ await commandBus.execute<InteractionUpdateInput, { interactionId: string }>(
114
+ 'customers.interactions.update',
115
+ {
116
+ input: { id, visibility: body.visibility },
117
+ ctx: {
118
+ container,
119
+ auth: auth as never,
120
+ organizationScope: null,
121
+ selectedOrganizationId: organizationId,
122
+ organizationIds: organizationId ? [organizationId] : null,
123
+ },
124
+ },
125
+ )
126
+
127
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
128
+ await runCrudMutationGuardAfterSuccess(container, {
129
+ tenantId: auth.tenantId,
130
+ organizationId,
131
+ userId,
132
+ resourceKind: 'customers.interaction',
133
+ resourceId: id,
134
+ operation: 'custom',
135
+ requestMethod: req.method,
136
+ requestHeaders: req.headers,
137
+ metadata: guardResult.metadata ?? null,
138
+ })
139
+ }
140
+
141
+ // Emit audit event best-effort — failure must NOT roll back the DB flush.
142
+ try {
143
+ await emitCustomersEvent('customers.email.visibility_changed', {
144
+ interactionId: interaction.id,
145
+ previousVisibility,
146
+ nextVisibility: body.visibility,
147
+ authorUserId: interaction.authorUserId ?? null,
148
+ actorUserId: auth.sub,
149
+ // v1: strict owner-only — only the author reaches this point, so a
150
+ // visibility change is never an admin bypass.
151
+ adminBypass: false,
152
+ tenantId: auth.tenantId,
153
+ organizationId,
154
+ })
155
+ } catch {
156
+ /* swallow — audit emission must not block the response */
157
+ }
158
+
159
+ return NextResponse.json({ ok: true, changed: true })
160
+ }
161
+
162
+ export const openApi = {
163
+ tags: ['Customers', 'Email'],
164
+ methods: {
165
+ PATCH: {
166
+ summary: 'Flip an email interaction visibility (private ↔ shared)',
167
+ tags: ['Customers', 'Email'],
168
+ responses: [
169
+ { status: 200, description: 'Updated' },
170
+ { status: 400, description: 'Invalid id' },
171
+ { status: 401, description: 'Unauthorized' },
172
+ { status: 404, description: 'Email not found or not visible to caller' },
173
+ { status: 422, description: 'Invalid body' },
174
+ ],
175
+ },
176
+ },
177
+ }
178
+
179
+ export default PATCH
@@ -8,6 +8,7 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
8
8
  import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
9
9
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
10
10
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
11
+ import { applyEmailVisibilityFilter } from '../../../lib/visibilityFilter'
11
12
 
12
13
  const querySchema = z.object({
13
14
  entityId: z.string().uuid(),
@@ -87,6 +88,15 @@ export async function GET(req: Request) {
87
88
  baseQuery = baseQuery.where('status', '=', query.status)
88
89
  }
89
90
 
91
+ // Per-user email privacy: exclude other users' private emails from the
92
+ // per-type counts so the `email` total matches the visibility-filtered list.
93
+ // v1 strict owner-only — no admin bypass (the filter ignores caller features).
94
+ const viewerUserId = auth.isApiKey ? null : auth.sub ?? null
95
+ baseQuery = applyEmailVisibilityFilter(baseQuery, {
96
+ currentUserId: viewerUserId,
97
+ userFeatures: undefined,
98
+ })
99
+
90
100
  // Raw SELECT: reads only unencrypted columns (id, interaction_type); title/notes are excluded to avoid ciphertext leakage.
91
101
  const rows = await baseQuery
92
102
  .select(['interaction_type', sql<string>`count(*)`.as('count')])
@@ -22,6 +22,7 @@ import {
22
22
  defaultOkResponseSchema,
23
23
  } from '../openapi'
24
24
  import { CUSTOMER_INTERACTION_ENTITY_ID } from '../../lib/interactionCompatibility'
25
+ import { applyEmailVisibilityFilter } from '../../lib/visibilityFilter'
25
26
  import { resolveCanonicalActivityTargetId } from '../../lib/legacyActivityBridge'
26
27
  import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
27
28
 
@@ -301,6 +302,7 @@ async function buildEnricherContext(
301
302
  container: { resolve: (name: string) => unknown },
302
303
  auth: NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>,
303
304
  organizationId: string | null,
305
+ precomputedUserFeatures?: { userId: string; features: string[] | undefined },
304
306
  ): Promise<EnricherContext> {
305
307
  const userId =
306
308
  (typeof auth.sub === 'string' && auth.sub.trim().length > 0
@@ -311,13 +313,20 @@ async function buildEnricherContext(
311
313
  ? auth.keyId
312
314
  : 'system')
313
315
 
316
+ // Reuse features already resolved for this same user (the GET handler resolves
317
+ // them once for the visibility filter) to avoid a second RBAC lookup per request.
318
+ const userFeatures =
319
+ precomputedUserFeatures && precomputedUserFeatures.userId === userId
320
+ ? precomputedUserFeatures.features
321
+ : await resolveUserFeatures(container, userId, auth.tenantId ?? null, organizationId)
322
+
314
323
  return {
315
324
  organizationId: organizationId ?? '',
316
325
  tenantId: auth.tenantId ?? '',
317
326
  userId,
318
327
  em: container.resolve('em'),
319
328
  container,
320
- userFeatures: await resolveUserFeatures(container, userId, auth.tenantId ?? null, organizationId),
329
+ userFeatures,
321
330
  }
322
331
  }
323
332
 
@@ -415,6 +424,12 @@ export async function GET(req: Request) {
415
424
  }
416
425
  if (query.excludeInteractionType) rowsQuery = rowsQuery.where('interaction_type', '!=', query.excludeInteractionType)
417
426
  if (query.search) {
427
+ // NOTE: for tenants with data encryption enabled, `title`/`body` are
428
+ // ciphertext at rest (see encryption.ts), so this ILIKE matches encrypted
429
+ // bytes and returns no rows — substring search over encrypted free-text
430
+ // columns is unsupported, the same documented limitation as
431
+ // customer_activity / customer_comment. The returned page's title/body are
432
+ // still decrypted for display further below.
418
433
  const searchTerm = `%${query.search}%`
419
434
  rowsQuery = rowsQuery.where(sql<boolean>`coalesce(title, '') ilike ${searchTerm} or coalesce(body, '') ilike ${searchTerm}`)
420
435
  }
@@ -438,6 +453,21 @@ export async function GET(req: Request) {
438
453
  ]))
439
454
  }
440
455
 
456
+ // ── Email visibility filter (2026-05-27) ──────────────────────────────
457
+ // Non-email interactions pass through; email rows with visibility='private'
458
+ // are filtered out unless the caller is the author or has admin bypass.
459
+ // API-key callers have no user identity (`auth.sub` undefined): resolve the
460
+ // viewer to null so they never gain the author bypass and only see shared
461
+ // emails (fail-closed). Mirrors counts/people/activities routes.
462
+ const viewerUserId = auth.isApiKey ? null : (auth.sub ?? null)
463
+ const callerUserFeatures = viewerUserId
464
+ ? await resolveUserFeatures(container, viewerUserId, auth.tenantId ?? null, selectedOrganizationId)
465
+ : undefined
466
+ rowsQuery = applyEmailVisibilityFilter(rowsQuery as any, {
467
+ currentUserId: viewerUserId,
468
+ userFeatures: callerUserFeatures,
469
+ })
470
+
441
471
  rowsQuery = rowsQuery.orderBy(sql`${sql.raw(sortSql)} ${sql.raw(sortDir)}`).orderBy('id', sortDir)
442
472
 
443
473
  const rows = await rowsQuery.execute() as InteractionListRow[]
@@ -460,7 +490,7 @@ export async function GET(req: Request) {
460
490
  )
461
491
  const interactionIds = pageRows.map((row) => row.id)
462
492
 
463
- const [users, deals, customFieldValues] = await Promise.all([
493
+ const [users, deals, customFieldValues, interactionRecords] = await Promise.all([
464
494
  authorIds.length > 0 ? findWithDecryption(em, User, { id: { $in: authorIds } }, undefined, { tenantId: auth.tenantId, organizationId: selectedOrganizationId }) : Promise.resolve([]),
465
495
  dealIds.length > 0 ? findWithDecryption(em, CustomerDeal, { id: { $in: dealIds } }, undefined, { tenantId: auth.tenantId, organizationId: selectedOrganizationId }) : Promise.resolve([]),
466
496
  interactionIds.length > 0
@@ -473,6 +503,9 @@ export async function GET(req: Request) {
473
503
  tenantFallbacks: [auth.tenantId].filter((value): value is string => !!value),
474
504
  })
475
505
  : Promise.resolve<Record<string, Record<string, unknown>>>({}),
506
+ interactionIds.length > 0
507
+ ? findWithDecryption(em, CustomerInteraction, { id: { $in: interactionIds } } as never, undefined, { tenantId: auth.tenantId, organizationId: selectedOrganizationId })
508
+ : Promise.resolve([]),
476
509
  ])
477
510
 
478
511
  const userMap = new Map(
@@ -487,14 +520,22 @@ export async function GET(req: Request) {
487
520
  const dealMap = new Map(
488
521
  deals.map((deal) => [deal.id, deal.title]),
489
522
  )
523
+ // title/body are encrypted at rest (see encryption.ts). The kysely rows above
524
+ // carry ciphertext when tenant encryption is enabled, so override them with the
525
+ // decrypted values from findWithDecryption for the returned page.
526
+ const interactionContentMap = new Map(
527
+ (interactionRecords as Array<{ id: string; title?: string | null; body?: string | null }>).map(
528
+ (record) => [record.id, { title: record.title ?? null, body: record.body ?? null }],
529
+ ),
530
+ )
490
531
 
491
532
  const baseItems = pageRows.map((row) => ({
492
533
  id: row.id,
493
534
  entityId: row.entity_id,
494
535
  dealId: row.deal_id ?? null,
495
536
  interactionType: row.interaction_type,
496
- title: row.title ?? null,
497
- body: row.body ?? null,
537
+ title: (interactionContentMap.has(row.id) ? interactionContentMap.get(row.id)!.title : row.title) ?? null,
538
+ body: (interactionContentMap.has(row.id) ? interactionContentMap.get(row.id)!.body : row.body) ?? null,
498
539
  status: row.status,
499
540
  scheduledAt: toIsoString(row.scheduled_at),
500
541
  occurredAt: toIsoString(row.occurred_at),
@@ -526,7 +567,12 @@ export async function GET(req: Request) {
526
567
  customValues: normalizeCustomFieldResponse(customFieldValues[row.id]) ?? null,
527
568
  }))
528
569
 
529
- const enricherContext = await buildEnricherContext(container, auth, selectedOrganizationId)
570
+ const enricherContext = await buildEnricherContext(
571
+ container,
572
+ auth,
573
+ selectedOrganizationId,
574
+ viewerUserId ? { userId: viewerUserId, features: callerUserFeatures } : undefined,
575
+ )
530
576
  const enriched = await applyResponseEnrichers(baseItems, 'customers.interaction', enricherContext)
531
577
 
532
578
  let nextCursor: string | undefined
@@ -0,0 +1,92 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { EntityManager } from '@mikro-orm/postgresql'
4
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
5
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
7
+ import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
8
+ import { CustomerEntity } from '../../../../data/entities'
9
+ import { buildPersonEmailThreads } from '../../../../lib/personEmailThreads'
10
+
11
+ export const metadata = {
12
+ path: '/customers/people/[id]/email-threads',
13
+ GET: {
14
+ requireAuth: true,
15
+ requireFeatures: ['customers.people.view'],
16
+ },
17
+ }
18
+
19
+ type RouteContext = {
20
+ params: Promise<{ id: string }> | { id: string }
21
+ }
22
+
23
+ export async function GET(req: Request, context: RouteContext): Promise<Response> {
24
+ const { id: personId } = await context.params
25
+ if (!z.string().uuid().safeParse(personId).success) {
26
+ return NextResponse.json({ error: 'Invalid person id' }, { status: 400 })
27
+ }
28
+
29
+ const auth = await getAuthFromRequest(req)
30
+ if (!auth?.sub || !auth?.tenantId) {
31
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
32
+ }
33
+
34
+ const container = await createRequestContainer()
35
+ const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
36
+ const organizationId = scope?.selectedId ?? (auth as { orgId?: string | null }).orgId ?? null
37
+ const em = (container.resolve('em') as EntityManager).fork()
38
+ const dscope = { tenantId: auth.tenantId as string, organizationId }
39
+
40
+ // Verify the Person exists in the caller's tenant/org (ownership check,
41
+ // same pattern as the [id]/emails compose route).
42
+ const person = await findOneWithDecryption(
43
+ em,
44
+ CustomerEntity,
45
+ {
46
+ id: personId,
47
+ kind: 'person',
48
+ tenantId: auth.tenantId,
49
+ organizationId,
50
+ deletedAt: null,
51
+ } as never,
52
+ undefined,
53
+ dscope,
54
+ )
55
+ if (!person) {
56
+ return NextResponse.json({ error: 'Person not found' }, { status: 404 })
57
+ }
58
+
59
+ const viewerUserId = auth.isApiKey ? null : auth.sub ?? null
60
+
61
+ const threads = await buildPersonEmailThreads(em, {
62
+ personId,
63
+ tenantId: auth.tenantId as string,
64
+ organizationId,
65
+ viewerUserId,
66
+ // The v1 visibility filter is owner-only and ignores `userFeatures`; match
67
+ // the sibling read routes (people/[id], interactions) by passing `undefined`
68
+ // rather than paying an RBAC round-trip the filter discards.
69
+ userFeatures: undefined,
70
+ })
71
+
72
+ return NextResponse.json({ threads })
73
+ }
74
+
75
+ export const openApi = {
76
+ tags: ['Customers', 'Email'],
77
+ methods: {
78
+ GET: {
79
+ summary: 'List a Person\'s email threads (Gmail-style conversation grouping)',
80
+ tags: ['Customers', 'Email'],
81
+ responses: [
82
+ { status: 200, description: 'Email threads for the person, grouped by conversation' },
83
+ { status: 400, description: 'Invalid person id' },
84
+ { status: 401, description: 'Unauthorized' },
85
+ { status: 403, description: 'Missing customers.people.view feature' },
86
+ { status: 404, description: 'Person not found' },
87
+ ],
88
+ },
89
+ },
90
+ }
91
+
92
+ export default GET
@@ -0,0 +1,184 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { EntityManager } from '@mikro-orm/postgresql'
4
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
5
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
7
+ import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
8
+ import {
9
+ validateCrudMutationGuard,
10
+ runCrudMutationGuardAfterSuccess,
11
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
12
+ import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
13
+ import { resolveAuthActorId } from '../../../../lib/interactionRequestContext'
14
+ import { CustomerEntity } from '../../../../data/entities'
15
+ import type { SendAsUserService } from '@open-mercato/core/modules/communication_channels/lib/send-as-user'
16
+
17
+ export const metadata = {
18
+ path: '/customers/people/[id]/emails',
19
+ POST: {
20
+ requireAuth: true,
21
+ requireFeatures: ['customers.email.compose'],
22
+ },
23
+ }
24
+
25
+ const composeSchema = z
26
+ .object({
27
+ userChannelId: z.string().uuid(),
28
+ to: z.array(z.string().email()).min(1).max(50),
29
+ cc: z.array(z.string().email()).max(50).optional(),
30
+ bcc: z.array(z.string().email()).max(50).optional(),
31
+ subject: z.string().min(1).max(500),
32
+ body: z.string().min(1).max(200_000),
33
+ bodyFormat: z.enum(['text', 'html']).default('html'),
34
+ visibility: z.enum(['private', 'shared']).default('private'),
35
+ inReplyTo: z.string().max(500).optional(),
36
+ references: z.array(z.string().max(500)).max(50).optional(),
37
+ parentMessageId: z.string().uuid().optional(),
38
+ })
39
+ .strict()
40
+
41
+ type RouteContext = {
42
+ params: Promise<{ id: string }> | { id: string }
43
+ }
44
+
45
+ export async function POST(req: Request, context: RouteContext): Promise<Response> {
46
+ const { id: personId } = await context.params
47
+ if (!z.string().uuid().safeParse(personId).success) {
48
+ return NextResponse.json({ error: 'Invalid person id' }, { status: 400 })
49
+ }
50
+
51
+ const auth = await getAuthFromRequest(req)
52
+ if (!auth?.sub || !auth?.tenantId) {
53
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
54
+ }
55
+
56
+ let body: z.infer<typeof composeSchema>
57
+ try {
58
+ body = composeSchema.parse(await readJsonSafe(req, null))
59
+ } catch (err) {
60
+ return NextResponse.json(
61
+ { error: err instanceof Error ? err.message : 'Invalid request body' },
62
+ { status: 422 },
63
+ )
64
+ }
65
+
66
+ const container = await createRequestContainer()
67
+ const userId = resolveAuthActorId(auth)
68
+ const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
69
+ const organizationId = scope?.selectedId ?? (auth as { orgId?: string | null }).orgId ?? null
70
+
71
+ const guardResult = await validateCrudMutationGuard(container, {
72
+ tenantId: auth.tenantId,
73
+ organizationId,
74
+ userId,
75
+ resourceKind: 'customers.person',
76
+ resourceId: personId,
77
+ operation: 'custom',
78
+ requestMethod: req.method,
79
+ requestHeaders: req.headers,
80
+ })
81
+ if (guardResult && !guardResult.ok) {
82
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
83
+ }
84
+
85
+ const em = (container.resolve('em') as EntityManager).fork()
86
+ const dscope = { tenantId: auth.tenantId as string, organizationId }
87
+
88
+ // 1. Verify the Person exists in the caller's tenant.
89
+ // Uses CustomerEntity (kind='person') as the canonical ownership check —
90
+ // the same pattern as the existing [id]/route.ts GET handler.
91
+ const person = await findOneWithDecryption(
92
+ em,
93
+ CustomerEntity,
94
+ {
95
+ id: personId,
96
+ kind: 'person',
97
+ tenantId: auth.tenantId,
98
+ organizationId,
99
+ deletedAt: null,
100
+ } as never,
101
+ undefined,
102
+ dscope,
103
+ )
104
+ if (!person) {
105
+ return NextResponse.json({ error: 'Person not found' }, { status: 404 })
106
+ }
107
+
108
+ // 2. Call the hub's send-as-user facade in-process (resolved via DI) so the
109
+ // customers module makes no HTTP self-call. `crmVisibility` and `crmPersonId`
110
+ // are injected as channelMetadata so the link-channel-message subscriber can
111
+ // anchor the sent message back to this Person on the
112
+ // `communication_channels.message.sent` event.
113
+ const sendAsUserService = container.resolve(
114
+ 'communicationChannelsSendAsUser',
115
+ ) as SendAsUserService
116
+
117
+ const sendResult = await sendAsUserService(
118
+ container,
119
+ { userId: auth.sub as string, tenantId: auth.tenantId as string, organizationId, auth },
120
+ {
121
+ userChannelId: body.userChannelId,
122
+ to: body.to,
123
+ cc: body.cc,
124
+ bcc: body.bcc,
125
+ subject: body.subject,
126
+ body: body.bodyFormat === 'html' ? { html: body.body } : { plain: body.body },
127
+ inReplyTo: body.inReplyTo,
128
+ references: body.references,
129
+ parentMessageId: body.parentMessageId,
130
+ channelMetadata: {
131
+ crmVisibility: body.visibility,
132
+ crmPersonId: personId,
133
+ },
134
+ },
135
+ )
136
+
137
+ if (!sendResult.ok) {
138
+ return NextResponse.json({ error: sendResult.error }, { status: sendResult.status })
139
+ }
140
+
141
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
142
+ await runCrudMutationGuardAfterSuccess(container, {
143
+ tenantId: auth.tenantId,
144
+ organizationId,
145
+ userId,
146
+ resourceKind: 'customers.person',
147
+ resourceId: personId,
148
+ operation: 'custom',
149
+ requestMethod: req.method,
150
+ requestHeaders: req.headers,
151
+ metadata: guardResult.metadata ?? null,
152
+ })
153
+ }
154
+
155
+ // Delivery is async (handled by the outbound queue worker), so this is the
156
+ // enqueue time — not the provider send time — and the provider's external
157
+ // message id is not known yet (the worker assigns it on successful delivery).
158
+ return NextResponse.json({
159
+ messageId: sendResult.messageId,
160
+ threadId: sendResult.threadId,
161
+ queuedAt: new Date().toISOString(),
162
+ })
163
+ }
164
+
165
+ export const openApi = {
166
+ tags: ['Customers', 'Email'],
167
+ methods: {
168
+ POST: {
169
+ summary: 'Compose + send an email anchored to a Person',
170
+ tags: ['Customers', 'Email'],
171
+ responses: [
172
+ { status: 200, description: 'Email queued for send' },
173
+ { status: 400, description: 'Invalid person id' },
174
+ { status: 401, description: 'Unauthorized' },
175
+ { status: 403, description: 'Missing customers.email.compose feature or mutation guard rejection' },
176
+ { status: 404, description: 'Person or channel not found' },
177
+ { status: 409, description: 'Channel not connected' },
178
+ { status: 422, description: 'Invalid request body' },
179
+ { status: 500, description: 'Send failed' },
180
+ ],
181
+ },
182
+ },
183
+ }
184
+ export default POST
@@ -40,6 +40,7 @@ import { parseBooleanFromUnknown, parseBooleanToken } from '@open-mercato/shared
40
40
  import { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'
41
41
  import { loadPersonCompanyLinks, summarizePersonCompanies } from '../../../lib/personCompanies'
42
42
  import { normalizeCustomerDetailCustomFields } from '../../detailCustomFields'
43
+ import { buildEmailVisibilityMikroFilter } from '../../../lib/visibilityFilter'
43
44
 
44
45
  export const metadata = {
45
46
  GET: { requireAuth: true, requireFeatures: ['customers.people.view'] },
@@ -513,14 +514,25 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
513
514
  profiler.mark('comments_loaded', { count: comments.length })
514
515
  }
515
516
 
517
+ // Per-user email privacy (CRM email integration spec, "Layer 1 — DB filter"):
518
+ // exclude private email interactions owned by other users from every read
519
+ // path that returns customer_interactions. Non-email rows, shared emails, and
520
+ // legacy null-visibility rows pass through. v1 is strict owner-only: there is
521
+ // NO admin bypass — the filter ignores caller features, and
522
+ // `customers.email.view_private` is reserved (inert) for v2 oversight.
523
+ const emailVisibilityFilter = buildEmailVisibilityMikroFilter({
524
+ currentUserId: viewerUserId,
525
+ userFeatures: undefined,
526
+ })
527
+
516
528
  const shouldLoadCanonicalInteractions = includeInteractions || includeActivities || includeTodos
517
529
  const canonicalInteractionRows = shouldLoadCanonicalInteractions
518
530
  ? await findWithDecryption(
519
531
  em,
520
532
  CustomerInteraction,
521
533
  interactionFlags.unified
522
- ? { entity: person.id, deletedAt: null }
523
- : { entity: person.id },
534
+ ? { entity: person.id, deletedAt: null, ...emailVisibilityFilter }
535
+ : { entity: person.id, ...emailVisibilityFilter },
524
536
  { orderBy: { scheduledAt: 'asc', createdAt: 'desc' }, limit: 100 },
525
537
  { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null },
526
538
  )
@@ -566,6 +578,7 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
566
578
  deletedAt: null,
567
579
  status: 'planned',
568
580
  interactionType: { $ne: 'task' },
581
+ ...emailVisibilityFilter,
569
582
  },
570
583
  { orderBy: { scheduledAt: 'ASC', createdAt: 'ASC' }, limit: plannedPreviewLimit },
571
584
  { tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null },
@@ -715,12 +728,14 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
715
728
  tenantId: person.tenantId,
716
729
  deletedAt: null,
717
730
  interactionType: { $ne: 'task' },
731
+ ...emailVisibilityFilter,
718
732
  }),
719
733
  interactions: await em.count(CustomerInteraction, {
720
734
  entity: person.id,
721
735
  organizationId: person.organizationId,
722
736
  tenantId: person.tenantId,
723
737
  deletedAt: null,
738
+ ...emailVisibilityFilter,
724
739
  }),
725
740
  todos: interactionFlags.unified
726
741
  ? await em.count(CustomerInteraction, {