@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,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../../src/modules/communication_channels/api/get/oauth/%5Bprovider%5D/callback/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { toAbsoluteUrl } from '@open-mercato/shared/lib/url'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createConnectedChannelRow, MailboxAlreadyConnectedError } from '../../../../../lib/connect-channel'\nimport { getChannelAdapter } from '../../../../../lib/adapter-registry-singleton'\nimport { resolveOAuthClientCredentials } from '../../../../../lib/oauth-client-config'\nimport {\n COMMUNICATION_CHANNELS_OAUTH_STATE_COOKIE_NAME,\n DEFAULT_OAUTH_RETURN_URL,\n normalizeOAuthReturnUrl,\n OAuthStateError,\n verifyOAuthState,\n} from '../../../../../lib/oauth-state'\n\nexport const metadata = {\n path: '/communication_channels/oauth/[provider]/callback',\n // No auth feature gate \u2014 the state cookie carries identity. The route still\n // verifies the session below to bind the callback to its initiator.\n GET: { requireAuth: true },\n}\n\ntype RouteContext = {\n params: Promise<{ provider: string }> | { provider: string }\n}\n\ntype CredentialsServiceLike = {\n resolve: (\n integrationId: string,\n scope: { organizationId: string; tenantId: string; userId?: string | null },\n ) => Promise<Record<string, unknown> | null>\n save?: (\n integrationId: string,\n credentials: Record<string, unknown>,\n scope: { organizationId: string; tenantId: string; userId?: string | null },\n ) => Promise<void>\n}\n\nfunction redirectWithFlash(\n req: Request,\n returnUrl: string,\n flash: { type: 'connected' | 'error'; code?: string; provider?: string; channelId?: string },\n): Response {\n const base = new URL(\n normalizeOAuthReturnUrl(returnUrl, DEFAULT_OAUTH_RETURN_URL),\n new URL(req.url).origin,\n )\n base.searchParams.set('flash', flash.type)\n if (flash.code) base.searchParams.set('code', flash.code)\n if (flash.provider) base.searchParams.set('provider', flash.provider)\n if (flash.channelId) base.searchParams.set('channelId', flash.channelId)\n const response = NextResponse.redirect(base.toString(), 302)\n response.cookies.set({\n name: COMMUNICATION_CHANNELS_OAUTH_STATE_COOKIE_NAME,\n value: '',\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n path: '/',\n maxAge: 0,\n })\n return response\n}\n\nexport async function GET(req: Request, context: RouteContext): Promise<Response> {\n const { provider } = await context.params\n const url = new URL(req.url)\n const code = url.searchParams.get('code') ?? ''\n const stateParam = url.searchParams.get('state') ?? ''\n const error = url.searchParams.get('error')\n\n if (error) {\n return redirectWithFlash(req, DEFAULT_OAUTH_RETURN_URL, { type: 'error', code: error, provider })\n }\n if (!code || !stateParam) {\n return redirectWithFlash(req, DEFAULT_OAUTH_RETURN_URL, {\n type: 'error',\n code: 'missing_code_or_state',\n provider,\n })\n }\n\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub || !auth?.tenantId) {\n return redirectWithFlash(req, DEFAULT_OAUTH_RETURN_URL, {\n type: 'error',\n code: 'unauthorized',\n provider,\n })\n }\n\n const adapter = getChannelAdapter(provider)\n if (!adapter || typeof adapter.exchangeOAuthCode !== 'function') {\n return redirectWithFlash(req, DEFAULT_OAUTH_RETURN_URL, {\n type: 'error',\n code: 'unknown_provider',\n provider,\n })\n }\n\n const cookieValue = req.headers.get('cookie') ?? ''\n const stateCookie = parseCookie(cookieValue, COMMUNICATION_CHANNELS_OAUTH_STATE_COOKIE_NAME)\n\n let statePayload\n try {\n statePayload = verifyOAuthState({\n cookie: stateCookie,\n expectedUserId: auth.sub as string,\n expectedProviderKey: provider,\n expectedState: stateParam,\n })\n } catch (err) {\n const errCode = err instanceof OAuthStateError ? err.code : 'invalid_state'\n return redirectWithFlash(req, DEFAULT_OAUTH_RETURN_URL, {\n type: 'error',\n code: errCode,\n provider,\n })\n }\n\n // Defense-in-depth: the signed state is minted for a specific tenant at\n // initiate time and `verifyOAuthState` already binds it to `auth.sub`. Assert\n // the session tenant matches too, so a token/session anomaly can never create\n // a channel under a different tenant than the one that started the flow.\n if (statePayload.tenantId !== auth.tenantId) {\n return redirectWithFlash(req, DEFAULT_OAUTH_RETURN_URL, {\n type: 'error',\n code: 'tenant_mismatch',\n provider,\n })\n }\n\n const returnUrl = normalizeOAuthReturnUrl(statePayload.returnUrl, DEFAULT_OAUTH_RETURN_URL)\n\n // Exchange the code via the adapter.\n const container = await createRequestContainer()\n const credentialsService = (() => {\n try {\n return container.resolve('integrationCredentialsService') as CredentialsServiceLike\n } catch {\n return null\n }\n })()\n // Resolve the tenant's OAuth client app config from the `channel_<provider>`\n // integration (userId = null). Same source the `initiate` route uses; a\n // missing row means the provider was never configured, so bounce back with an\n // actionable code rather than attempting an exchange with empty credentials.\n const oauthClientCredentials = await resolveOAuthClientCredentials(credentialsService, provider, {\n tenantId: statePayload.tenantId,\n organizationId: statePayload.organizationId ?? null,\n })\n if (!oauthClientCredentials) {\n return redirectWithFlash(req, returnUrl, {\n type: 'error',\n code: 'oauth_client_not_configured',\n provider,\n })\n }\n\n // Must byte-for-byte match the redirect_uri sent at authorize time (the\n // `initiate` route), including behind a reverse proxy \u2014 derive it from the\n // configured app origin rather than the raw request URL.\n const redirectUri = toAbsoluteUrl(req, `/api/communication_channels/oauth/${provider}/callback`)\n\n let exchange\n try {\n exchange = await adapter.exchangeOAuthCode({\n code,\n redirectUri,\n credentials: oauthClientCredentials,\n scope: {\n tenantId: statePayload.tenantId,\n organizationId: statePayload.organizationId ?? statePayload.tenantId,\n },\n stateExtra: statePayload.extra,\n })\n } catch (err) {\n console.warn(\n `[communication_channels:oauth] code exchange failed for provider ${provider}:`,\n err instanceof Error ? err.message : err,\n )\n return redirectWithFlash(req, returnUrl, {\n type: 'error',\n code: 'exchange_failed',\n provider,\n })\n }\n\n // Persist the encrypted credentials under a per-user `integration_credentials`\n // row, then create / update the per-user `CommunicationChannel`.\n const em = (container.resolve('em') as EntityManager).fork()\n // Per-user scope: pass `userId` so the credentials service writes to a\n // user-scoped row instead of overwriting the tenant-wide row. Without this,\n // two users on the same tenant share one credentials row (see review R2-C1\n // / N1, 2026-05-26).\n const credentialsScope = {\n tenantId: statePayload.tenantId,\n organizationId: statePayload.organizationId ?? statePayload.tenantId,\n userId: auth.sub as string,\n }\n let credentialsRefId: string | null = null\n let credentialsPersisted = false\n if (credentialsService?.save) {\n try {\n // Save under a per-provider integration id namespace; per-user scoping is\n // recorded on `IntegrationCredentials.user_id` via the `scope.userId`.\n // Arg order MUST be (integrationId, credentials, scope) \u2014 matches the real\n // CredentialsService signature; the legacy reversed order corrupted the\n // saved row by writing the scope object into the credentials field.\n await credentialsService.save(\n `channel_${provider}`,\n {\n ...exchange.credentials,\n userId: auth.sub,\n expiresAt: exchange.expiresAt ? exchange.expiresAt.toISOString() : undefined,\n },\n credentialsScope,\n )\n credentialsPersisted = true\n } catch (err) {\n console.warn(\n `[communication_channels:oauth] persisting credentials failed for provider ${provider}:`,\n err instanceof Error ? err.message : err,\n )\n }\n }\n // Resolve the saved row's id so we can link `channel.credentialsRef` to it.\n // `credentialsService.save` is `void`-returning by contract, so we re-find the\n // row immediately afterwards. Best-effort \u2014 null is acceptable; the channel\n // creation below will downgrade to `requires_reauth` so workers don't poll\n // a credential-less channel.\n if (credentialsPersisted) {\n try {\n const { IntegrationCredentials } = await import('@open-mercato/core/modules/integrations/data/entities')\n const { findOneWithDecryption } = await import('@open-mercato/shared/lib/encryption/find')\n const row = await findOneWithDecryption(\n em,\n IntegrationCredentials,\n {\n integrationId: `channel_${provider}`,\n tenantId: credentialsScope.tenantId,\n organizationId: credentialsScope.organizationId,\n userId: credentialsScope.userId,\n deletedAt: null,\n },\n undefined,\n credentialsScope,\n )\n credentialsRefId = (row as { id?: string } | null)?.id ?? null\n } catch {\n credentialsRefId = null\n }\n }\n\n const displayName =\n exchange.displayName ?? exchange.externalIdentifier ?? `${provider} channel`\n // Fail-safe: if credentials persistence failed (no `credentialsRef` available),\n // create the channel in `requires_reauth` so workers don't poll a channel that\n // has no usable credentials. The user can re-run the OAuth flow to recover\n // (see review R2-H4 / F6, 2026-05-26).\n const credentialsAvailable = credentialsRefId !== null\n let channel\n try {\n channel = await createConnectedChannelRow({\n em,\n adapter,\n providerKey: provider,\n displayName,\n externalIdentifier: exchange.externalIdentifier ?? null,\n credentialsRefId,\n userId: auth.sub as string,\n scope: { tenantId: statePayload.tenantId, organizationId: statePayload.organizationId ?? null },\n })\n } catch (err) {\n // Same mailbox already connected via another provider \u2014 don't create a second\n // channel that double-ingests every message; send the user back with a flash.\n if (err instanceof MailboxAlreadyConnectedError) {\n console.warn(\n `[communication_channels:oauth] mailbox ${err.externalIdentifier} already connected via ${err.existingProviderKey}`,\n )\n return redirectWithFlash(req, returnUrl, {\n type: 'error',\n code: 'mailbox_already_connected',\n provider,\n })\n }\n throw err\n }\n\n // Spec C \u00A7 Phase C5 \u2014 best-effort push registration for OAuth providers\n // that support it (Gmail). Same shape as the credential-connect\n // path: failures persist as `pushStatus='failed'` on channelState and DO\n // NOT fail the connect \u2014 polling fallback covers until the operator clicks\n // \"Re-register push\" on the channels page.\n const adapterSupportsPush =\n typeof adapter.registerPush === 'function' && typeof adapter.unregisterPush === 'function'\n if (\n credentialsAvailable &&\n adapterSupportsPush &&\n statePayload.organizationId &&\n provider === 'gmail'\n ) {\n try {\n const { pushRegister } = await import('../../../../../commands/push-register')\n await pushRegister({\n container,\n scope: {\n tenantId: statePayload.tenantId,\n organizationId: statePayload.organizationId,\n userId: auth.sub as string,\n },\n input: { channelId: channel.id },\n })\n } catch (err) {\n console.warn(\n `[oauth-callback] best-effort pushRegister failed for ${channel.id}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n }\n\n return redirectWithFlash(req, returnUrl, {\n type: 'connected',\n provider,\n channelId: channel.id,\n })\n}\n\nfunction parseCookie(header: string, name: string): string | null {\n if (!header) return null\n const segments = header.split(';')\n for (const segment of segments) {\n const trimmed = segment.trim()\n if (trimmed.startsWith(`${name}=`)) {\n return decodeURIComponent(trimmed.slice(name.length + 1))\n }\n }\n return null\n}\n\nexport const openApi = {\n tags: ['CommunicationChannels'],\n methods: {\n GET: {\n summary: 'OAuth callback \u2014 exchange code, persist credentials, create per-user channel',\n tags: ['CommunicationChannels'],\n responses: [\n { status: 302, description: 'Redirect back to returnUrl with flash query params' },\n ],\n },\n },\n}\nexport default GET\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,168 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
4
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
5
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
6
+ import { CommunicationChannel } from "../../../../../data/entities.js";
7
+ import { ChannelAccessDeniedError, assertCanManageChannel } from "../../../../../lib/access-control.js";
8
+ import {
9
+ queueImportHistory,
10
+ queueImportHistorySchema
11
+ } from "../../../../../commands/queue-import-history.js";
12
+ import { validateRouteMutationGuard } from "../../../../../lib/route-mutation-guard.js";
13
+ const metadata = {
14
+ path: "/communication_channels/channels/[id]/import-history",
15
+ POST: {
16
+ requireAuth: true,
17
+ requireFeatures: ["communication_channels.connect_user_channel"]
18
+ }
19
+ };
20
+ const bodySchema = queueImportHistorySchema.omit({ channelId: true });
21
+ async function POST(req, context) {
22
+ const { id } = await context.params;
23
+ if (!z.string().uuid().safeParse(id).success) {
24
+ return NextResponse.json({ error: "Invalid channel id" }, { status: 400 });
25
+ }
26
+ const auth = await getAuthFromRequest(req);
27
+ if (!auth?.sub || !auth?.tenantId) {
28
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
29
+ }
30
+ let rawBody;
31
+ try {
32
+ const text = await req.text();
33
+ rawBody = text ? JSON.parse(text) : {};
34
+ } catch {
35
+ return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
36
+ }
37
+ const parsed = bodySchema.safeParse(rawBody);
38
+ if (!parsed.success) {
39
+ const first = parsed.error.issues[0];
40
+ return NextResponse.json(
41
+ {
42
+ error: first?.message ?? "Invalid request",
43
+ fieldErrors: first ? { [first.path.join(".")]: first.message } : void 0
44
+ },
45
+ { status: 400 }
46
+ );
47
+ }
48
+ const container = await createRequestContainer();
49
+ const em = container.resolve("em").fork();
50
+ const organizationId = auth.orgId ?? null;
51
+ if (!organizationId) {
52
+ return NextResponse.json({ error: "No organization scope" }, { status: 400 });
53
+ }
54
+ const dscope = { tenantId: auth.tenantId, organizationId };
55
+ const channel = await findOneWithDecryption(
56
+ em,
57
+ CommunicationChannel,
58
+ {
59
+ id,
60
+ tenantId: auth.tenantId,
61
+ organizationId,
62
+ deletedAt: null
63
+ },
64
+ void 0,
65
+ dscope
66
+ );
67
+ if (!channel) {
68
+ return NextResponse.json({ error: "Channel not found" }, { status: 404 });
69
+ }
70
+ let userFeatures = [];
71
+ try {
72
+ const rbac = container.resolve("rbacService");
73
+ const acl = await rbac.loadAcl(auth.sub, {
74
+ tenantId: auth.tenantId,
75
+ organizationId
76
+ });
77
+ userFeatures = acl?.isSuperAdmin ? ["*"] : Array.isArray(acl?.features) ? acl.features : [];
78
+ } catch {
79
+ userFeatures = [];
80
+ }
81
+ try {
82
+ assertCanManageChannel(
83
+ { userId: channel.userId },
84
+ auth.sub,
85
+ userFeatures,
86
+ "communication_channels.channel.import_history"
87
+ );
88
+ } catch (err) {
89
+ if (err instanceof ChannelAccessDeniedError) {
90
+ return NextResponse.json({ error: "Channel not found" }, { status: 404 });
91
+ }
92
+ throw err;
93
+ }
94
+ const guard = await validateRouteMutationGuard({
95
+ container,
96
+ req,
97
+ auth,
98
+ input: {
99
+ resourceKind: "communication_channels.channel",
100
+ resourceId: id,
101
+ operation: "custom",
102
+ mutationPayload: parsed.data
103
+ }
104
+ });
105
+ if ("response" in guard) return guard.response;
106
+ try {
107
+ const result = await queueImportHistory({
108
+ container,
109
+ scope: {
110
+ tenantId: auth.tenantId,
111
+ organizationId,
112
+ userId: auth.sub
113
+ },
114
+ input: { channelId: id, ...parsed.data }
115
+ });
116
+ await guard.afterSuccess();
117
+ return NextResponse.json({ ok: true, ...result }, { status: 202 });
118
+ } catch (err) {
119
+ const candidate = err;
120
+ if (candidate && typeof candidate.status === "number") {
121
+ return NextResponse.json(
122
+ {
123
+ error: candidate.message,
124
+ fieldErrors: candidate.fieldErrors
125
+ },
126
+ { status: candidate.status }
127
+ );
128
+ }
129
+ console.error(`[import-history] failed to enqueue for channel ${id}:`, err);
130
+ return NextResponse.json(
131
+ { error: err instanceof Error ? err.message : "Failed to queue import-history" },
132
+ { status: 500 }
133
+ );
134
+ }
135
+ }
136
+ const openApi = {
137
+ tags: ["CommunicationChannels"],
138
+ methods: {
139
+ POST: {
140
+ summary: "Queue a backlog import for a channel (Spec B \xA7 Phase B6)",
141
+ tags: ["CommunicationChannels"],
142
+ requestBody: {
143
+ required: true,
144
+ contentType: "application/json",
145
+ schema: bodySchema
146
+ },
147
+ responses: [
148
+ { status: 202, description: "Import job queued; returns { progressJobId }" },
149
+ { status: 400, description: "Invalid channel id or unsupported provider" },
150
+ { status: 401, description: "Unauthorized" },
151
+ { status: 404, description: "Channel not found / not accessible" },
152
+ { status: 409, description: "Channel is not connected (requires reauth / error)" },
153
+ {
154
+ status: 429,
155
+ description: "Another import is already running for this channel"
156
+ }
157
+ ]
158
+ }
159
+ }
160
+ };
161
+ var route_default = POST;
162
+ export {
163
+ POST,
164
+ route_default as default,
165
+ metadata,
166
+ openApi
167
+ };
168
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../../src/modules/communication_channels/api/post/channels/%5Bid%5D/import-history/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { CrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { CommunicationChannel } from '../../../../../data/entities'\nimport { ChannelAccessDeniedError, assertCanManageChannel } from '../../../../../lib/access-control'\nimport {\n queueImportHistory,\n queueImportHistorySchema,\n} from '../../../../../commands/queue-import-history'\nimport { validateRouteMutationGuard } from '../../../../../lib/route-mutation-guard'\n\ntype RbacServiceLike = {\n loadAcl: (\n userId: string,\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<{ isSuperAdmin: boolean; features: string[]; organizations: string[] | null }>\n}\n\n/**\n * Spec B \u00A7 Phase B6 \u2014 operator-triggered backlog import.\n *\n * Enqueues a `channel-import-history` job that calls\n * `adapter.importHistory(...)` paginated up to `maxMessages`, routes each\n * message through `ingest-inbound-message`, and reports progress on a\n * `ProgressJob` consumed by the existing ProgressTopBar.\n *\n * Owner self-service: a user may import history for their OWN mailbox (gated by\n * `connect_user_channel`). Importing into a shared/tenant-wide channel still\n * requires `communication_channels.channel.import_history` \u2014 enforced per\n * channel type by `assertCanManageChannel` in the handler.\n */\nexport const metadata = {\n path: '/communication_channels/channels/[id]/import-history',\n POST: {\n requireAuth: true,\n requireFeatures: ['communication_channels.connect_user_channel'],\n },\n}\n\ntype RouteContext = {\n params: Promise<{ id: string }> | { id: string }\n}\n\nconst bodySchema = queueImportHistorySchema.omit({ channelId: true })\n\nexport async function POST(req: Request, context: RouteContext): Promise<Response> {\n const { id } = await context.params\n if (!z.string().uuid().safeParse(id).success) {\n return NextResponse.json({ error: 'Invalid channel id' }, { status: 400 })\n }\n\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub || !auth?.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n // Parse body defensively \u2014 the route is called from a CrudForm dialog, but\n // also from automated tests / scripts.\n let rawBody: unknown\n try {\n const text = await req.text()\n rawBody = text ? JSON.parse(text) : {}\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n const parsed = bodySchema.safeParse(rawBody)\n if (!parsed.success) {\n const first = parsed.error.issues[0]\n return NextResponse.json(\n {\n error: first?.message ?? 'Invalid request',\n fieldErrors: first ? { [first.path.join('.')]: first.message } : undefined,\n },\n { status: 400 },\n )\n }\n\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager).fork()\n const organizationId = (auth as { orgId?: string | null }).orgId ?? null\n if (!organizationId) {\n return NextResponse.json({ error: 'No organization scope' }, { status: 400 })\n }\n const dscope = { tenantId: auth.tenantId as string, organizationId }\n\n // Per-user access guard \u2014 load the channel just to check ownership; the\n // command repeats the lookup but at the cost of one extra read we get a\n // clean 404 for non-owners (instead of a generic 500 from inside the cmd).\n const channel = await findOneWithDecryption(\n em,\n CommunicationChannel,\n {\n id,\n tenantId: auth.tenantId as string,\n organizationId,\n deletedAt: null,\n },\n undefined,\n dscope,\n )\n if (!channel) {\n return NextResponse.json({ error: 'Channel not found' }, { status: 404 })\n }\n\n let userFeatures: string[] = []\n try {\n const rbac = container.resolve('rbacService') as RbacServiceLike\n const acl = await rbac.loadAcl(auth.sub as string, {\n tenantId: auth.tenantId as string,\n organizationId,\n })\n userFeatures = acl?.isSuperAdmin ? ['*'] : Array.isArray(acl?.features) ? acl.features : []\n } catch {\n userFeatures = []\n }\n try {\n assertCanManageChannel(\n { userId: (channel as { userId?: string | null }).userId },\n auth.sub as string,\n userFeatures,\n 'communication_channels.channel.import_history',\n )\n } catch (err) {\n if (err instanceof ChannelAccessDeniedError) {\n return NextResponse.json({ error: 'Channel not found' }, { status: 404 })\n }\n throw err\n }\n\n const guard = await validateRouteMutationGuard({\n container,\n req,\n auth,\n input: {\n resourceKind: 'communication_channels.channel',\n resourceId: id,\n operation: 'custom',\n mutationPayload: parsed.data as unknown as Record<string, unknown>,\n },\n })\n if ('response' in guard) return guard.response\n\n try {\n const result = await queueImportHistory({\n container,\n scope: {\n tenantId: auth.tenantId as string,\n organizationId,\n userId: auth.sub as string,\n },\n input: { channelId: id, ...parsed.data },\n })\n await guard.afterSuccess()\n // 202 Accepted: durable work runs in the channel-import-history worker; the\n // response carries progressJobId for the ProgressTopBar (progress module contract).\n return NextResponse.json({ ok: true, ...result }, { status: 202 })\n } catch (err) {\n const candidate = err as CrudFormError\n if (candidate && typeof candidate.status === 'number') {\n return NextResponse.json(\n {\n error: candidate.message,\n fieldErrors: candidate.fieldErrors,\n },\n { status: candidate.status },\n )\n }\n console.error(`[import-history] failed to enqueue for channel ${id}:`, err)\n return NextResponse.json(\n { error: err instanceof Error ? err.message : 'Failed to queue import-history' },\n { status: 500 },\n )\n }\n}\n\nexport const openApi = {\n tags: ['CommunicationChannels'],\n methods: {\n POST: {\n summary: 'Queue a backlog import for a channel (Spec B \u00A7 Phase B6)',\n tags: ['CommunicationChannels'],\n requestBody: {\n required: true,\n contentType: 'application/json',\n schema: bodySchema,\n },\n responses: [\n { status: 202, description: 'Import job queued; returns { progressJobId }' },\n { status: 400, description: 'Invalid channel id or unsupported provider' },\n { status: 401, description: 'Unauthorized' },\n { status: 404, description: 'Channel not found / not accessible' },\n { status: 409, description: 'Channel is not connected (requires reauth / error)' },\n {\n status: 429,\n description: 'Another import is already running for this channel',\n },\n ],\n },\n },\n}\n\nexport default POST\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,143 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
4
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
5
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
6
+ import { CommunicationChannel } from "../../../../../data/entities.js";
7
+ import { ChannelAccessDeniedError, assertCanManageChannel } from "../../../../../lib/access-control.js";
8
+ import { COMMUNICATION_CHANNELS_QUEUES, getCommunicationChannelsQueue } from "../../../../../lib/queue.js";
9
+ import { validateRouteMutationGuard } from "../../../../../lib/route-mutation-guard.js";
10
+ const metadata = {
11
+ path: "/communication_channels/channels/[id]/poll-now",
12
+ POST: {
13
+ // Owner self-service: a user may sync their OWN mailbox (gated by
14
+ // `connect_user_channel`). Polling a shared/tenant-wide channel still
15
+ // requires `manage` — enforced per channel type by `assertCanManageChannel`.
16
+ requireAuth: true,
17
+ requireFeatures: ["communication_channels.connect_user_channel"]
18
+ }
19
+ };
20
+ async function POST(req, context) {
21
+ const { id } = await context.params;
22
+ if (!z.string().uuid().safeParse(id).success) {
23
+ return NextResponse.json({ error: "Invalid channel id" }, { status: 400 });
24
+ }
25
+ const auth = await getAuthFromRequest(req);
26
+ if (!auth?.sub || !auth?.tenantId) {
27
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
28
+ }
29
+ const container = await createRequestContainer();
30
+ const em = container.resolve("em").fork();
31
+ const organizationId = auth.orgId ?? null;
32
+ const dscope = { tenantId: auth.tenantId, organizationId };
33
+ const channel = await findOneWithDecryption(
34
+ em,
35
+ CommunicationChannel,
36
+ {
37
+ id,
38
+ tenantId: auth.tenantId,
39
+ organizationId,
40
+ deletedAt: null
41
+ },
42
+ void 0,
43
+ dscope
44
+ );
45
+ if (!channel) {
46
+ return NextResponse.json({ error: "Channel not found" }, { status: 404 });
47
+ }
48
+ let userFeatures = [];
49
+ try {
50
+ const rbac = container.resolve("rbacService");
51
+ const acl = await rbac.loadAcl(auth.sub, {
52
+ tenantId: auth.tenantId,
53
+ organizationId
54
+ });
55
+ userFeatures = acl?.isSuperAdmin ? ["*"] : Array.isArray(acl?.features) ? acl.features : [];
56
+ } catch {
57
+ userFeatures = [];
58
+ }
59
+ try {
60
+ assertCanManageChannel(
61
+ { userId: channel.userId },
62
+ auth.sub,
63
+ userFeatures,
64
+ "communication_channels.manage"
65
+ );
66
+ } catch (err) {
67
+ if (err instanceof ChannelAccessDeniedError) {
68
+ return NextResponse.json({ error: "Channel not found" }, { status: 404 });
69
+ }
70
+ throw err;
71
+ }
72
+ if (!channel.isActive) {
73
+ return NextResponse.json({ error: "Channel is disabled" }, { status: 409 });
74
+ }
75
+ if (channel.status === "requires_reauth") {
76
+ return NextResponse.json(
77
+ { error: "Channel needs reauthentication \u2014 reconnect from /backend/profile/communication-channels" },
78
+ { status: 409 }
79
+ );
80
+ }
81
+ if (channel.status === "disconnected") {
82
+ return NextResponse.json(
83
+ { error: "Channel is disconnected \u2014 reconnect to resume polling" },
84
+ { status: 409 }
85
+ );
86
+ }
87
+ const guard = await validateRouteMutationGuard({
88
+ container,
89
+ req,
90
+ auth,
91
+ input: {
92
+ resourceKind: "communication_channels.channel",
93
+ resourceId: channel.id,
94
+ operation: "custom",
95
+ mutationPayload: { action: "poll-now" }
96
+ }
97
+ });
98
+ if ("response" in guard) return guard.response;
99
+ const queue = getCommunicationChannelsQueue(COMMUNICATION_CHANNELS_QUEUES.poll);
100
+ const payload = {
101
+ channelId: channel.id,
102
+ scope: {
103
+ tenantId: auth.tenantId,
104
+ organizationId: organizationId ?? null
105
+ },
106
+ attempt: 1
107
+ };
108
+ await queue.enqueue(payload);
109
+ await guard.afterSuccess();
110
+ return NextResponse.json(
111
+ {
112
+ ok: true,
113
+ channelId: channel.id,
114
+ queued: true,
115
+ message: "Poll queued \u2014 new messages will appear after the worker runs."
116
+ },
117
+ { status: 202 }
118
+ );
119
+ }
120
+ const openApi = {
121
+ tags: ["CommunicationChannels"],
122
+ methods: {
123
+ POST: {
124
+ summary: "Manually trigger a poll cycle for a channel (demo / operator override)",
125
+ tags: ["CommunicationChannels"],
126
+ responses: [
127
+ { status: 202, description: "Poll job enqueued" },
128
+ { status: 400, description: "Invalid channel id" },
129
+ { status: 401, description: "Unauthorized" },
130
+ { status: 404, description: "Channel not found" },
131
+ { status: 409, description: "Channel disabled or not connected" }
132
+ ]
133
+ }
134
+ }
135
+ };
136
+ var route_default = POST;
137
+ export {
138
+ POST,
139
+ route_default as default,
140
+ metadata,
141
+ openApi
142
+ };
143
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../../src/modules/communication_channels/api/post/channels/%5Bid%5D/poll-now/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CommunicationChannel } from '../../../../../data/entities'\nimport { ChannelAccessDeniedError, assertCanManageChannel } from '../../../../../lib/access-control'\nimport { COMMUNICATION_CHANNELS_QUEUES, getCommunicationChannelsQueue } from '../../../../../lib/queue'\nimport type { PollChannelJobPayload } from '../../../../../workers/poll-channel'\nimport { validateRouteMutationGuard } from '../../../../../lib/route-mutation-guard'\n\ntype RbacServiceLike = {\n loadAcl: (\n userId: string,\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<{ isSuperAdmin: boolean; features: string[]; organizations: string[] | null }>\n}\n\nexport const metadata = {\n path: '/communication_channels/channels/[id]/poll-now',\n POST: {\n // Owner self-service: a user may sync their OWN mailbox (gated by\n // `connect_user_channel`). Polling a shared/tenant-wide channel still\n // requires `manage` \u2014 enforced per channel type by `assertCanManageChannel`.\n requireAuth: true,\n requireFeatures: ['communication_channels.connect_user_channel'],\n },\n}\n\ntype RouteContext = {\n params: Promise<{ id: string }> | { id: string }\n}\n\n/**\n * Manual poll trigger \u2014 enqueues a single `poll-channel` job immediately so\n * the operator (or a demo) doesn't have to wait for the 60-second scheduler\n * tick + per-channel `poll_interval_seconds` window.\n *\n * Per-user access guard mirrors the rest of the channels API: only the channel\n * owner (or an admin with `communication_channels.admin`) can trigger a poll.\n */\nexport async function POST(req: Request, context: RouteContext): Promise<Response> {\n const { id } = await context.params\n if (!z.string().uuid().safeParse(id).success) {\n return NextResponse.json({ error: 'Invalid channel id' }, { status: 400 })\n }\n\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub || !auth?.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager).fork()\n const organizationId = (auth as { orgId?: string | null }).orgId ?? null\n const dscope = { tenantId: auth.tenantId as string, organizationId }\n\n const channel = await findOneWithDecryption(\n em,\n CommunicationChannel,\n {\n id,\n tenantId: auth.tenantId as string,\n organizationId,\n deletedAt: null,\n },\n undefined,\n dscope,\n )\n if (!channel) {\n return NextResponse.json({ error: 'Channel not found' }, { status: 404 })\n }\n\n // Load features via RBAC so admin bypass is honoured.\n let userFeatures: string[] = []\n try {\n const rbac = container.resolve('rbacService') as RbacServiceLike\n const acl = await rbac.loadAcl(auth.sub as string, {\n tenantId: auth.tenantId as string,\n organizationId,\n })\n userFeatures = acl?.isSuperAdmin ? ['*'] : Array.isArray(acl?.features) ? acl.features : []\n } catch {\n userFeatures = []\n }\n try {\n assertCanManageChannel(\n { userId: (channel as { userId?: string | null }).userId },\n auth.sub as string,\n userFeatures,\n 'communication_channels.manage',\n )\n } catch (err) {\n if (err instanceof ChannelAccessDeniedError) {\n return NextResponse.json({ error: 'Channel not found' }, { status: 404 })\n }\n throw err\n }\n\n if (!channel.isActive) {\n return NextResponse.json({ error: 'Channel is disabled' }, { status: 409 })\n }\n // Allow manual poll-now from 'connected' AND 'error' states. The operator's\n // intent in clicking \"Poll now\" while the channel is in error is exactly\n // \"retry the connection right now\"; a successful poll auto-resets status\n // back to 'connected' (see poll-channel.ts).\n // Block only the explicitly-broken lifecycle states.\n if (channel.status === 'requires_reauth') {\n return NextResponse.json(\n { error: 'Channel needs reauthentication \u2014 reconnect from /backend/profile/communication-channels' },\n { status: 409 },\n )\n }\n if (channel.status === 'disconnected') {\n return NextResponse.json(\n { error: 'Channel is disconnected \u2014 reconnect to resume polling' },\n { status: 409 },\n )\n }\n\n const guard = await validateRouteMutationGuard({\n container,\n req,\n auth,\n input: {\n resourceKind: 'communication_channels.channel',\n resourceId: channel.id,\n operation: 'custom',\n mutationPayload: { action: 'poll-now' },\n },\n })\n if ('response' in guard) return guard.response\n\n const queue = getCommunicationChannelsQueue(COMMUNICATION_CHANNELS_QUEUES.poll)\n const payload: PollChannelJobPayload = {\n channelId: channel.id,\n scope: {\n tenantId: auth.tenantId as string,\n organizationId: organizationId ?? null,\n },\n attempt: 1,\n }\n await queue.enqueue(payload as unknown as Record<string, unknown>)\n await guard.afterSuccess()\n\n return NextResponse.json(\n {\n ok: true,\n channelId: channel.id,\n queued: true,\n message: 'Poll queued \u2014 new messages will appear after the worker runs.',\n },\n { status: 202 },\n )\n}\n\nexport const openApi = {\n tags: ['CommunicationChannels'],\n methods: {\n POST: {\n summary: 'Manually trigger a poll cycle for a channel (demo / operator override)',\n tags: ['CommunicationChannels'],\n responses: [\n { status: 202, description: 'Poll job enqueued' },\n { status: 400, description: 'Invalid channel id' },\n { status: 401, description: 'Unauthorized' },\n { status: 404, description: 'Channel not found' },\n { status: 409, description: 'Channel disabled or not connected' },\n ],\n },\n },\n}\nexport default POST\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,127 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
4
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
5
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
6
+ import { CommunicationChannel } from "../../../../../../data/entities.js";
7
+ import { ChannelAccessDeniedError, assertCanManageChannel } from "../../../../../../lib/access-control.js";
8
+ import { pushRegister } from "../../../../../../commands/push-register.js";
9
+ import { validateRouteMutationGuard } from "../../../../../../lib/route-mutation-guard.js";
10
+ const metadata = {
11
+ path: "/communication_channels/channels/[id]/push/register",
12
+ POST: {
13
+ requireAuth: true,
14
+ requireFeatures: ["communication_channels.connect_user_channel"]
15
+ }
16
+ };
17
+ async function POST(req, context) {
18
+ const { id } = await context.params;
19
+ if (!z.string().uuid().safeParse(id).success) {
20
+ return NextResponse.json({ error: "Invalid channel id" }, { status: 400 });
21
+ }
22
+ const auth = await getAuthFromRequest(req);
23
+ if (!auth?.sub || !auth?.tenantId) {
24
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
25
+ }
26
+ const organizationId = auth.orgId ?? null;
27
+ if (!organizationId) {
28
+ return NextResponse.json({ error: "No organization scope" }, { status: 400 });
29
+ }
30
+ const container = await createRequestContainer();
31
+ const em = container.resolve("em").fork();
32
+ const channel = await findOneWithDecryption(
33
+ em,
34
+ CommunicationChannel,
35
+ { id, tenantId: auth.tenantId, organizationId, deletedAt: null },
36
+ void 0,
37
+ { tenantId: auth.tenantId, organizationId }
38
+ );
39
+ if (!channel) {
40
+ return NextResponse.json({ error: "Channel not found" }, { status: 404 });
41
+ }
42
+ let userFeatures = [];
43
+ try {
44
+ const rbac = container.resolve("rbacService");
45
+ const acl = await rbac.loadAcl(auth.sub, {
46
+ tenantId: auth.tenantId,
47
+ organizationId
48
+ });
49
+ userFeatures = acl?.isSuperAdmin ? ["*"] : Array.isArray(acl?.features) ? acl.features : [];
50
+ } catch {
51
+ userFeatures = [];
52
+ }
53
+ try {
54
+ assertCanManageChannel(
55
+ { userId: channel.userId },
56
+ auth.sub,
57
+ userFeatures,
58
+ "communication_channels.channel.push.manage"
59
+ );
60
+ } catch (err) {
61
+ if (err instanceof ChannelAccessDeniedError) {
62
+ return NextResponse.json({ error: "Channel not found" }, { status: 404 });
63
+ }
64
+ throw err;
65
+ }
66
+ const guard = await validateRouteMutationGuard({
67
+ container,
68
+ req,
69
+ auth,
70
+ input: {
71
+ resourceKind: "communication_channels.channel",
72
+ resourceId: id,
73
+ operation: "custom",
74
+ mutationPayload: { pushStatus: "register" }
75
+ }
76
+ });
77
+ if ("response" in guard) return guard.response;
78
+ try {
79
+ const result = await pushRegister({
80
+ container,
81
+ scope: { tenantId: auth.tenantId, organizationId, userId: auth.sub },
82
+ input: { channelId: id }
83
+ });
84
+ await guard.afterSuccess();
85
+ return NextResponse.json({ ok: true, ...result }, { status: 202 });
86
+ } catch (err) {
87
+ const candidate = err;
88
+ if (candidate && typeof candidate.status === "number") {
89
+ return NextResponse.json(
90
+ { error: candidate.message, fieldErrors: candidate.fieldErrors },
91
+ { status: candidate.status }
92
+ );
93
+ }
94
+ console.error(`[push-register] failed for channel ${id}:`, err);
95
+ return NextResponse.json(
96
+ { error: err instanceof Error ? err.message : "Failed to register push" },
97
+ { status: 500 }
98
+ );
99
+ }
100
+ }
101
+ const openApi = {
102
+ tags: ["CommunicationChannels"],
103
+ methods: {
104
+ POST: {
105
+ summary: "Force-register push delivery for a channel (Spec C \xA7 Phase C5)",
106
+ tags: ["CommunicationChannels"],
107
+ responses: [
108
+ { status: 202, description: "Push registration attempted; check result.pushStatus" },
109
+ { status: 400, description: "Invalid id or unsupported provider" },
110
+ { status: 401, description: "Unauthorized" },
111
+ { status: 403, description: "Missing push.manage feature" },
112
+ { status: 404, description: "Channel not found" },
113
+ { status: 409, description: "Provider does not support push (IMAP)" },
114
+ { status: 502, description: "Provider returned an error during registration" },
115
+ { status: 503, description: "Webhook base URL or Pub/Sub topic not configured" }
116
+ ]
117
+ }
118
+ }
119
+ };
120
+ var route_default = POST;
121
+ export {
122
+ POST,
123
+ route_default as default,
124
+ metadata,
125
+ openApi
126
+ };
127
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../../../src/modules/communication_channels/api/post/channels/%5Bid%5D/push/register/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { CrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { CommunicationChannel } from '../../../../../../data/entities'\nimport { ChannelAccessDeniedError, assertCanManageChannel } from '../../../../../../lib/access-control'\nimport { pushRegister } from '../../../../../../commands/push-register'\nimport { validateRouteMutationGuard } from '../../../../../../lib/route-mutation-guard'\n\ntype RbacServiceLike = {\n loadAcl: (\n userId: string,\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<{ isSuperAdmin: boolean; features: string[]; organizations: string[] | null }>\n}\n\n/**\n * Spec C \u00A7 Phase C5 \u2014 Operator-facing \"Re-register push\" endpoint.\n *\n * Owner self-service: a user may (re-)register push on their OWN mailbox (gated\n * by `connect_user_channel`). Registering push on a shared/tenant-wide channel\n * still requires `communication_channels.channel.push.manage` \u2014 enforced per\n * channel type by `assertCanManageChannel` in the handler. Used by the profile\n * page and the channel detail page's `PushStatusSection` to recover from a\n * `pushStatus='failed'` state.\n */\nexport const metadata = {\n path: '/communication_channels/channels/[id]/push/register',\n POST: {\n requireAuth: true,\n requireFeatures: ['communication_channels.connect_user_channel'],\n },\n}\n\ntype RouteContext = {\n params: Promise<{ id: string }> | { id: string }\n}\n\nexport async function POST(req: Request, context: RouteContext): Promise<Response> {\n const { id } = await context.params\n if (!z.string().uuid().safeParse(id).success) {\n return NextResponse.json({ error: 'Invalid channel id' }, { status: 400 })\n }\n\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub || !auth?.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const organizationId = (auth as { orgId?: string | null }).orgId ?? null\n if (!organizationId) {\n return NextResponse.json({ error: 'No organization scope' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n\n // Defense-in-depth: `push.manage` is admin-default, but enforce per-user\n // ownership anyway so a non-admin operator who is granted `push.manage`\n // cannot re-register push on another user's channel. Admins (and tenant-wide\n // channels) pass via `assertCanAccessChannel`. Automatic callers (OAuth\n // callback, connect, renew worker) invoke `pushRegister` directly and are\n // intentionally not subject to this user-facing guard.\n const em = (container.resolve('em') as EntityManager).fork()\n const channel = await findOneWithDecryption(\n em,\n CommunicationChannel,\n { id, tenantId: auth.tenantId as string, organizationId, deletedAt: null },\n undefined,\n { tenantId: auth.tenantId as string, organizationId },\n )\n if (!channel) {\n return NextResponse.json({ error: 'Channel not found' }, { status: 404 })\n }\n let userFeatures: string[] = []\n try {\n const rbac = container.resolve('rbacService') as RbacServiceLike\n const acl = await rbac.loadAcl(auth.sub as string, {\n tenantId: auth.tenantId as string,\n organizationId,\n })\n userFeatures = acl?.isSuperAdmin ? ['*'] : Array.isArray(acl?.features) ? acl.features : []\n } catch {\n userFeatures = []\n }\n try {\n assertCanManageChannel(\n { userId: (channel as { userId?: string | null }).userId },\n auth.sub as string,\n userFeatures,\n 'communication_channels.channel.push.manage',\n )\n } catch (err) {\n if (err instanceof ChannelAccessDeniedError) {\n return NextResponse.json({ error: 'Channel not found' }, { status: 404 })\n }\n throw err\n }\n\n const guard = await validateRouteMutationGuard({\n container,\n req,\n auth,\n input: {\n resourceKind: 'communication_channels.channel',\n resourceId: id,\n operation: 'custom',\n mutationPayload: { pushStatus: 'register' },\n },\n })\n if ('response' in guard) return guard.response\n\n try {\n const result = await pushRegister({\n container,\n scope: { tenantId: auth.tenantId as string, organizationId, userId: auth.sub as string },\n input: { channelId: id },\n })\n await guard.afterSuccess()\n return NextResponse.json({ ok: true, ...result }, { status: 202 })\n } catch (err) {\n const candidate = err as CrudFormError\n if (candidate && typeof candidate.status === 'number') {\n return NextResponse.json(\n { error: candidate.message, fieldErrors: candidate.fieldErrors },\n { status: candidate.status },\n )\n }\n console.error(`[push-register] failed for channel ${id}:`, err)\n return NextResponse.json(\n { error: err instanceof Error ? err.message : 'Failed to register push' },\n { status: 500 },\n )\n }\n}\n\nexport const openApi = {\n tags: ['CommunicationChannels'],\n methods: {\n POST: {\n summary: 'Force-register push delivery for a channel (Spec C \u00A7 Phase C5)',\n tags: ['CommunicationChannels'],\n responses: [\n { status: 202, description: 'Push registration attempted; check result.pushStatus' },\n { status: 400, description: 'Invalid id or unsupported provider' },\n { status: 401, description: 'Unauthorized' },\n { status: 403, description: 'Missing push.manage feature' },\n { status: 404, description: 'Channel not found' },\n { status: 409, description: 'Provider does not support push (IMAP)' },\n { status: 502, description: 'Provider returned an error during registration' },\n { status: 503, description: 'Webhook base URL or Pub/Sub topic not configured' },\n ],\n },\n },\n}\n\nexport default POST\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }