@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.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/channel_ingest_dead_letter/index.js +25 -0
- package/dist/generated/entities/channel_ingest_dead_letter/index.js.map +7 -0
- package/dist/generated/entities/channel_thread_mapping/index.js +25 -0
- package/dist/generated/entities/channel_thread_mapping/index.js.map +7 -0
- package/dist/generated/entities/channel_thread_token/index.js +17 -0
- package/dist/generated/entities/channel_thread_token/index.js.map +7 -0
- package/dist/generated/entities/communication_channel/index.js +43 -0
- package/dist/generated/entities/communication_channel/index.js.map +7 -0
- package/dist/generated/entities/customer_interaction/index.js +4 -0
- package/dist/generated/entities/customer_interaction/index.js.map +2 -2
- package/dist/generated/entities/external_conversation/index.js +25 -0
- package/dist/generated/entities/external_conversation/index.js.map +7 -0
- package/dist/generated/entities/external_message/index.js +25 -0
- package/dist/generated/entities/external_message/index.js.map +7 -0
- package/dist/generated/entities/integration_credentials/index.js +3 -1
- package/dist/generated/entities/integration_credentials/index.js.map +2 -2
- package/dist/generated/entities/message/index.js +2 -0
- package/dist/generated/entities/message/index.js.map +2 -2
- package/dist/generated/entities/message_channel_link/index.js +33 -0
- package/dist/generated/entities/message_channel_link/index.js.map +7 -0
- package/dist/generated/entities/message_reaction/index.js +25 -0
- package/dist/generated/entities/message_reaction/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +11 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +117 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/authFixtures.js +2 -1
- package/dist/helpers/integration/authFixtures.js.map +2 -2
- package/dist/helpers/integration/communicationChannelsFixtures.js +58 -0
- package/dist/helpers/integration/communicationChannelsFixtures.js.map +7 -0
- package/dist/modules/communication_channels/acl.js +47 -0
- package/dist/modules/communication_channels/acl.js.map +7 -0
- package/dist/modules/communication_channels/api/delete/channels/[id]/route.js +133 -0
- package/dist/modules/communication_channels/api/delete/channels/[id]/route.js.map +7 -0
- package/dist/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.js +113 -0
- package/dist/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js +138 -0
- package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/channels/[id]/route.js +93 -0
- package/dist/modules/communication_channels/api/get/channels/[id]/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/channels/route.js +96 -0
- package/dist/modules/communication_channels/api/get/channels/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/me/channels/route.js +82 -0
- package/dist/modules/communication_channels/api/get/me/channels/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js +274 -0
- package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js +168 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js +143 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/push/register/route.js +127 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/push/register/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/set-primary/route.js +99 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/set-primary/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +197 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +124 -0
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/messages/[messageId]/reactions/route.js +120 -0
- package/dist/modules/communication_channels/api/post/messages/[messageId]/reactions/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +157 -0
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/send-as-user/route.js +115 -0
- package/dist/modules/communication_channels/api/post/send-as-user/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/test-seed/route.js +217 -0
- package/dist/modules/communication_channels/api/post/test-seed/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js +175 -0
- package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js +123 -0
- package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js.map +7 -0
- package/dist/modules/communication_channels/api/put/threads/[threadId]/assign/route.js +117 -0
- package/dist/modules/communication_channels/api/put/threads/[threadId]/assign/route.js.map +7 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js +180 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js.map +7 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.js +36 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.js.map +7 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +107 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +7 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.meta.js +38 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.meta.js.map +7 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +727 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +7 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js +38 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js.map +7 -0
- package/dist/modules/communication_channels/commands/connect-credential-channel.js +154 -0
- package/dist/modules/communication_channels/commands/connect-credential-channel.js.map +7 -0
- package/dist/modules/communication_channels/commands/delete-channel.js +137 -0
- package/dist/modules/communication_channels/commands/delete-channel.js.map +7 -0
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js +400 -0
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +7 -0
- package/dist/modules/communication_channels/commands/disconnect-channel.js +163 -0
- package/dist/modules/communication_channels/commands/disconnect-channel.js.map +7 -0
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js +413 -0
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +7 -0
- package/dist/modules/communication_channels/commands/interceptors.js +68 -0
- package/dist/modules/communication_channels/commands/interceptors.js.map +7 -0
- package/dist/modules/communication_channels/commands/process-inbound-reaction.js +198 -0
- package/dist/modules/communication_channels/commands/process-inbound-reaction.js.map +7 -0
- package/dist/modules/communication_channels/commands/push-register.js +146 -0
- package/dist/modules/communication_channels/commands/push-register.js.map +7 -0
- package/dist/modules/communication_channels/commands/push-renew.js +23 -0
- package/dist/modules/communication_channels/commands/push-renew.js.map +7 -0
- package/dist/modules/communication_channels/commands/push-unregister.js +108 -0
- package/dist/modules/communication_channels/commands/push-unregister.js.map +7 -0
- package/dist/modules/communication_channels/commands/queue-import-history.js +113 -0
- package/dist/modules/communication_channels/commands/queue-import-history.js.map +7 -0
- package/dist/modules/communication_channels/commands/reassign-conversation.js +193 -0
- package/dist/modules/communication_channels/commands/reassign-conversation.js.map +7 -0
- package/dist/modules/communication_channels/commands/set-primary-channel.js +114 -0
- package/dist/modules/communication_channels/commands/set-primary-channel.js.map +7 -0
- package/dist/modules/communication_channels/commands/toggle-outbound-reaction.js +260 -0
- package/dist/modules/communication_channels/commands/toggle-outbound-reaction.js.map +7 -0
- package/dist/modules/communication_channels/data/enrichers.js +286 -0
- package/dist/modules/communication_channels/data/enrichers.js.map +7 -0
- package/dist/modules/communication_channels/data/entities.js +447 -0
- package/dist/modules/communication_channels/data/entities.js.map +7 -0
- package/dist/modules/communication_channels/data/extensions.js +67 -0
- package/dist/modules/communication_channels/data/extensions.js.map +7 -0
- package/dist/modules/communication_channels/data/validators.js +123 -0
- package/dist/modules/communication_channels/data/validators.js.map +7 -0
- package/dist/modules/communication_channels/di.js +35 -0
- package/dist/modules/communication_channels/di.js.map +7 -0
- package/dist/modules/communication_channels/encryption.js +12 -0
- package/dist/modules/communication_channels/encryption.js.map +7 -0
- package/dist/modules/communication_channels/events.js +124 -0
- package/dist/modules/communication_channels/events.js.map +7 -0
- package/dist/modules/communication_channels/index.js +20 -0
- package/dist/modules/communication_channels/index.js.map +7 -0
- package/dist/modules/communication_channels/lib/access-control.js +43 -0
- package/dist/modules/communication_channels/lib/access-control.js.map +7 -0
- package/dist/modules/communication_channels/lib/adapter-compat.js +36 -0
- package/dist/modules/communication_channels/lib/adapter-compat.js.map +7 -0
- package/dist/modules/communication_channels/lib/adapter-registry-singleton.js +22 -0
- package/dist/modules/communication_channels/lib/adapter-registry-singleton.js.map +7 -0
- package/dist/modules/communication_channels/lib/adapter.js +1 -0
- package/dist/modules/communication_channels/lib/adapter.js.map +7 -0
- package/dist/modules/communication_channels/lib/connect-channel.js +95 -0
- package/dist/modules/communication_channels/lib/connect-channel.js.map +7 -0
- package/dist/modules/communication_channels/lib/contact-resolver.js +79 -0
- package/dist/modules/communication_channels/lib/contact-resolver.js.map +7 -0
- package/dist/modules/communication_channels/lib/credential-refresh.js +97 -0
- package/dist/modules/communication_channels/lib/credential-refresh.js.map +7 -0
- package/dist/modules/communication_channels/lib/dead-letter.js +62 -0
- package/dist/modules/communication_channels/lib/dead-letter.js.map +7 -0
- package/dist/modules/communication_channels/lib/email-capabilities.js +47 -0
- package/dist/modules/communication_channels/lib/email-capabilities.js.map +7 -0
- package/dist/modules/communication_channels/lib/email-contact.js +14 -0
- package/dist/modules/communication_channels/lib/email-contact.js.map +7 -0
- package/dist/modules/communication_channels/lib/email-mime.js +259 -0
- package/dist/modules/communication_channels/lib/email-mime.js.map +7 -0
- package/dist/modules/communication_channels/lib/error-classification.js +101 -0
- package/dist/modules/communication_channels/lib/error-classification.js.map +7 -0
- package/dist/modules/communication_channels/lib/gmail-pubsub-jwt.js +185 -0
- package/dist/modules/communication_channels/lib/gmail-pubsub-jwt.js.map +7 -0
- package/dist/modules/communication_channels/lib/mutation-guards.js +114 -0
- package/dist/modules/communication_channels/lib/mutation-guards.js.map +7 -0
- package/dist/modules/communication_channels/lib/oauth-client-config.js +32 -0
- package/dist/modules/communication_channels/lib/oauth-client-config.js.map +7 -0
- package/dist/modules/communication_channels/lib/oauth-state.js +128 -0
- package/dist/modules/communication_channels/lib/oauth-state.js.map +7 -0
- package/dist/modules/communication_channels/lib/oauth-token.js +45 -0
- package/dist/modules/communication_channels/lib/oauth-token.js.map +7 -0
- package/dist/modules/communication_channels/lib/pg-errors.js +11 -0
- package/dist/modules/communication_channels/lib/pg-errors.js.map +7 -0
- package/dist/modules/communication_channels/lib/provider-health.js +24 -0
- package/dist/modules/communication_channels/lib/provider-health.js.map +7 -0
- package/dist/modules/communication_channels/lib/push-state.js +19 -0
- package/dist/modules/communication_channels/lib/push-state.js.map +7 -0
- package/dist/modules/communication_channels/lib/queue.js +54 -0
- package/dist/modules/communication_channels/lib/queue.js.map +7 -0
- package/dist/modules/communication_channels/lib/reaction-processor-types.js +5 -0
- package/dist/modules/communication_channels/lib/reaction-processor-types.js.map +7 -0
- package/dist/modules/communication_channels/lib/reaction-semantics.js +11 -0
- package/dist/modules/communication_channels/lib/reaction-semantics.js.map +7 -0
- package/dist/modules/communication_channels/lib/registry.js +67 -0
- package/dist/modules/communication_channels/lib/registry.js.map +7 -0
- package/dist/modules/communication_channels/lib/route-mutation-guard.js +43 -0
- package/dist/modules/communication_channels/lib/route-mutation-guard.js.map +7 -0
- package/dist/modules/communication_channels/lib/sanitize-channel-html.js +96 -0
- package/dist/modules/communication_channels/lib/sanitize-channel-html.js.map +7 -0
- package/dist/modules/communication_channels/lib/send-as-user.js +194 -0
- package/dist/modules/communication_channels/lib/send-as-user.js.map +7 -0
- package/dist/modules/communication_channels/lib/system-user.js +22 -0
- package/dist/modules/communication_channels/lib/system-user.js.map +7 -0
- package/dist/modules/communication_channels/lib/test-seed.js +68 -0
- package/dist/modules/communication_channels/lib/test-seed.js.map +7 -0
- package/dist/modules/communication_channels/lib/thread-matcher.js +263 -0
- package/dist/modules/communication_channels/lib/thread-matcher.js.map +7 -0
- package/dist/modules/communication_channels/lib/thread-token.js +219 -0
- package/dist/modules/communication_channels/lib/thread-token.js.map +7 -0
- package/dist/modules/communication_channels/lib/use-connect-channel.js +61 -0
- package/dist/modules/communication_channels/lib/use-connect-channel.js.map +7 -0
- package/dist/modules/communication_channels/migrations/Migration20260526134719_communication_channels.js +50 -0
- package/dist/modules/communication_channels/migrations/Migration20260526134719_communication_channels.js.map +7 -0
- package/dist/modules/communication_channels/migrations/Migration20260527195446_communication_channels.js +19 -0
- package/dist/modules/communication_channels/migrations/Migration20260527195446_communication_channels.js.map +7 -0
- package/dist/modules/communication_channels/migrations/Migration20260529231848_communication_channels.js +13 -0
- package/dist/modules/communication_channels/migrations/Migration20260529231848_communication_channels.js.map +7 -0
- package/dist/modules/communication_channels/migrations/Migration20260531120000_communication_channels.js +17 -0
- package/dist/modules/communication_channels/migrations/Migration20260531120000_communication_channels.js.map +7 -0
- package/dist/modules/communication_channels/notifications.client.js +51 -0
- package/dist/modules/communication_channels/notifications.client.js.map +7 -0
- package/dist/modules/communication_channels/notifications.handlers.js +53 -0
- package/dist/modules/communication_channels/notifications.handlers.js.map +7 -0
- package/dist/modules/communication_channels/notifications.js +56 -0
- package/dist/modules/communication_channels/notifications.js.map +7 -0
- package/dist/modules/communication_channels/setup.js +105 -0
- package/dist/modules/communication_channels/setup.js.map +7 -0
- package/dist/modules/communication_channels/subscribers/channel-requires-reauth-notification.js +71 -0
- package/dist/modules/communication_channels/subscribers/channel-requires-reauth-notification.js.map +7 -0
- package/dist/modules/communication_channels/subscribers/outbound-bridge.js +103 -0
- package/dist/modules/communication_channels/subscribers/outbound-bridge.js.map +7 -0
- package/dist/modules/communication_channels/subscribers/user-deleted-cascade.js +51 -0
- package/dist/modules/communication_channels/subscribers/user-deleted-cascade.js.map +7 -0
- package/dist/modules/communication_channels/widgets/components.js +7 -0
- package/dist/modules/communication_channels/widgets/components.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.client.js +18 -0
- package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.client.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.js +30 -0
- package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.js +185 -0
- package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.js +17 -0
- package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js +44 -0
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js +17 -0
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/profile-channels-menu/widget.js +23 -0
- package/dist/modules/communication_channels/widgets/injection/profile-channels-menu/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.client.js +141 -0
- package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.client.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.js +17 -0
- package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection-table.js +38 -0
- package/dist/modules/communication_channels/widgets/injection-table.js.map +7 -0
- package/dist/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.js +25 -0
- package/dist/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.js.map +7 -0
- package/dist/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.js +19 -0
- package/dist/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.js.map +7 -0
- package/dist/modules/communication_channels/widgets/notifications/index.js +7 -0
- package/dist/modules/communication_channels/widgets/notifications/index.js.map +7 -0
- package/dist/modules/communication_channels/workers/channel-import-history.js +185 -0
- package/dist/modules/communication_channels/workers/channel-import-history.js.map +7 -0
- package/dist/modules/communication_channels/workers/gmail-history-sync.js +154 -0
- package/dist/modules/communication_channels/workers/gmail-history-sync.js.map +7 -0
- package/dist/modules/communication_channels/workers/gmail-renew-watch.js +95 -0
- package/dist/modules/communication_channels/workers/gmail-renew-watch.js.map +7 -0
- package/dist/modules/communication_channels/workers/inbound-processor.js +56 -0
- package/dist/modules/communication_channels/workers/inbound-processor.js.map +7 -0
- package/dist/modules/communication_channels/workers/outbound-delivery.js +85 -0
- package/dist/modules/communication_channels/workers/outbound-delivery.js.map +7 -0
- package/dist/modules/communication_channels/workers/poll-channel.js +240 -0
- package/dist/modules/communication_channels/workers/poll-channel.js.map +7 -0
- package/dist/modules/communication_channels/workers/poll-tick.js +132 -0
- package/dist/modules/communication_channels/workers/poll-tick.js.map +7 -0
- package/dist/modules/communication_channels/workers/reaction-processor.js +192 -0
- package/dist/modules/communication_channels/workers/reaction-processor.js.map +7 -0
- package/dist/modules/customers/acl.js +18 -0
- package/dist/modules/customers/acl.js.map +2 -2
- package/dist/modules/customers/api/activities/route.js +9 -0
- package/dist/modules/customers/api/activities/route.js.map +2 -2
- package/dist/modules/customers/api/companies/[id]/route.js +18 -7
- package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/[id]/visibility/route.js +151 -0
- package/dist/modules/customers/api/interactions/[id]/visibility/route.js.map +7 -0
- package/dist/modules/customers/api/interactions/counts/route.js +6 -0
- package/dist/modules/customers/api/interactions/counts/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/route.js +26 -7
- package/dist/modules/customers/api/interactions/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/email-threads/route.js +82 -0
- package/dist/modules/customers/api/people/[id]/email-threads/route.js.map +7 -0
- package/dist/modules/customers/api/people/[id]/emails/route.js +157 -0
- package/dist/modules/customers/api/people/[id]/emails/route.js.map +7 -0
- package/dist/modules/customers/api/people/[id]/route.js +12 -4
- package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +10 -0
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +46 -5
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +16 -0
- package/dist/modules/customers/commands/interactions.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivityCard.js +32 -0
- package/dist/modules/customers/components/detail/ActivityCard.js.map +2 -2
- package/dist/modules/customers/components/detail/ComposeEmailDialog.js +242 -0
- package/dist/modules/customers/components/detail/ComposeEmailDialog.js.map +7 -0
- package/dist/modules/customers/components/detail/DealForm.js +2 -1
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/DealsSection.js +10 -0
- package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
- package/dist/modules/customers/components/detail/EmailCardActions.js +179 -0
- package/dist/modules/customers/components/detail/EmailCardActions.js.map +7 -0
- package/dist/modules/customers/components/detail/EmailReplyForwardActions.js +52 -0
- package/dist/modules/customers/components/detail/EmailReplyForwardActions.js.map +7 -0
- package/dist/modules/customers/components/detail/PersonDetailTabs.js +7 -1
- package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonEmailThreadsTab.js +366 -0
- package/dist/modules/customers/components/detail/PersonEmailThreadsTab.js.map +7 -0
- package/dist/modules/customers/data/enrichers.js +133 -2
- package/dist/modules/customers/data/enrichers.js.map +2 -2
- package/dist/modules/customers/data/entities.js +18 -0
- package/dist/modules/customers/data/entities.js.map +2 -2
- package/dist/modules/customers/data/extensions.js +16 -0
- package/dist/modules/customers/data/extensions.js.map +7 -0
- package/dist/modules/customers/encryption.js +11 -0
- package/dist/modules/customers/encryption.js.map +2 -2
- package/dist/modules/customers/events.js +4 -1
- package/dist/modules/customers/events.js.map +2 -2
- package/dist/modules/customers/lib/findPeopleByAddresses.js +64 -0
- package/dist/modules/customers/lib/findPeopleByAddresses.js.map +7 -0
- package/dist/modules/customers/lib/kysely.js.map +2 -2
- package/dist/modules/customers/lib/link-channel-message-handler.js +303 -0
- package/dist/modules/customers/lib/link-channel-message-handler.js.map +7 -0
- package/dist/modules/customers/lib/personEmailThreads.js +205 -0
- package/dist/modules/customers/lib/personEmailThreads.js.map +7 -0
- package/dist/modules/customers/lib/visibilityFilter.js +51 -0
- package/dist/modules/customers/lib/visibilityFilter.js.map +7 -0
- package/dist/modules/customers/migrations/Migration20260527012240_customers.js +20 -0
- package/dist/modules/customers/migrations/Migration20260527012240_customers.js.map +7 -0
- package/dist/modules/customers/setup.js +2 -1
- package/dist/modules/customers/setup.js.map +2 -2
- package/dist/modules/customers/subscribers/link-channel-message-received.js +12 -0
- package/dist/modules/customers/subscribers/link-channel-message-received.js.map +7 -0
- package/dist/modules/customers/subscribers/link-channel-message-sent.js +12 -0
- package/dist/modules/customers/subscribers/link-channel-message-sent.js.map +7 -0
- package/dist/modules/integrations/data/entities.js +8 -1
- package/dist/modules/integrations/data/entities.js.map +2 -2
- package/dist/modules/integrations/lib/credentials-service.js +29 -14
- package/dist/modules/integrations/lib/credentials-service.js.map +2 -2
- package/dist/modules/integrations/migrations/Migration20260526154136_integrations.js +15 -0
- package/dist/modules/integrations/migrations/Migration20260526154136_integrations.js.map +7 -0
- package/dist/modules/messages/commands/messages.js +70 -8
- package/dist/modules/messages/commands/messages.js.map +2 -2
- package/dist/modules/messages/components/ComposeMessagePageClient.js +24 -13
- package/dist/modules/messages/components/ComposeMessagePageClient.js.map +2 -2
- package/dist/modules/messages/components/MessageDetailPageClient.js +39 -2
- package/dist/modules/messages/components/MessageDetailPageClient.js.map +2 -2
- package/dist/modules/messages/components/MessagesInboxPageClient.js +1 -0
- package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
- package/dist/modules/messages/data/entities.js +8 -1
- package/dist/modules/messages/data/entities.js.map +2 -2
- package/dist/modules/messages/migrations/Migration20260531130000.js +15 -0
- package/dist/modules/messages/migrations/Migration20260531130000.js.map +7 -0
- package/dist/modules/messages/widgets/injection-table.js +7 -0
- package/dist/modules/messages/widgets/injection-table.js.map +7 -0
- package/generated/entities/channel_ingest_dead_letter/index.ts +11 -0
- package/generated/entities/channel_thread_mapping/index.ts +11 -0
- package/generated/entities/channel_thread_token/index.ts +7 -0
- package/generated/entities/communication_channel/index.ts +20 -0
- package/generated/entities/customer_interaction/index.ts +2 -0
- package/generated/entities/external_conversation/index.ts +11 -0
- package/generated/entities/external_message/index.ts +11 -0
- package/generated/entities/integration_credentials/index.ts +1 -0
- package/generated/entities/message/index.ts +1 -0
- package/generated/entities/message_channel_link/index.ts +15 -0
- package/generated/entities/message_reaction/index.ts +11 -0
- package/generated/entities.ids.generated.ts +11 -0
- package/generated/entity-fields-registry.ts +117 -0
- package/package.json +9 -7
- package/src/helpers/integration/authFixtures.ts +4 -1
- package/src/helpers/integration/communicationChannelsFixtures.ts +124 -0
- package/src/modules/communication_channels/acl.ts +43 -0
- package/src/modules/communication_channels/api/delete/channels/[id]/route.ts +163 -0
- package/src/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.ts +143 -0
- package/src/modules/communication_channels/api/get/channels/[id]/health/route.ts +173 -0
- package/src/modules/communication_channels/api/get/channels/[id]/route.ts +111 -0
- package/src/modules/communication_channels/api/get/channels/route.ts +109 -0
- package/src/modules/communication_channels/api/get/me/channels/route.ts +100 -0
- package/src/modules/communication_channels/api/get/oauth/[provider]/callback/route.ts +355 -0
- package/src/modules/communication_channels/api/post/channels/[id]/import-history/route.ts +206 -0
- package/src/modules/communication_channels/api/post/channels/[id]/poll-now/route.ts +174 -0
- package/src/modules/communication_channels/api/post/channels/[id]/push/register/route.ts +158 -0
- package/src/modules/communication_channels/api/post/channels/[id]/set-primary/route.ts +114 -0
- package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +241 -0
- package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +134 -0
- package/src/modules/communication_channels/api/post/messages/[messageId]/reactions/route.ts +143 -0
- package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +192 -0
- package/src/modules/communication_channels/api/post/send-as-user/route.ts +125 -0
- package/src/modules/communication_channels/api/post/test-seed/route.ts +267 -0
- package/src/modules/communication_channels/api/post/webhook/[provider]/route.ts +227 -0
- package/src/modules/communication_channels/api/post/webhooks/gmail/route.ts +161 -0
- package/src/modules/communication_channels/api/put/threads/[threadId]/assign/route.ts +132 -0
- package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.ts +34 -0
- package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.tsx +250 -0
- package/src/modules/communication_channels/backend/communication_channels/channels/page.meta.ts +36 -0
- package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +137 -0
- package/src/modules/communication_channels/backend/profile/communication-channels/page.meta.ts +36 -0
- package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +907 -0
- package/src/modules/communication_channels/commands/connect-credential-channel.ts +243 -0
- package/src/modules/communication_channels/commands/delete-channel.ts +193 -0
- package/src/modules/communication_channels/commands/deliver-outbound-message.ts +579 -0
- package/src/modules/communication_channels/commands/disconnect-channel.ts +241 -0
- package/src/modules/communication_channels/commands/ingest-inbound-message.ts +602 -0
- package/src/modules/communication_channels/commands/interceptors.ts +104 -0
- package/src/modules/communication_channels/commands/process-inbound-reaction.ts +265 -0
- package/src/modules/communication_channels/commands/push-register.ts +203 -0
- package/src/modules/communication_channels/commands/push-renew.ts +49 -0
- package/src/modules/communication_channels/commands/push-unregister.ts +168 -0
- package/src/modules/communication_channels/commands/queue-import-history.ts +180 -0
- package/src/modules/communication_channels/commands/reassign-conversation.ts +273 -0
- package/src/modules/communication_channels/commands/set-primary-channel.ts +154 -0
- package/src/modules/communication_channels/commands/toggle-outbound-reaction.ts +347 -0
- package/src/modules/communication_channels/data/enrichers.ts +413 -0
- package/src/modules/communication_channels/data/entities.ts +546 -0
- package/src/modules/communication_channels/data/extensions.ts +76 -0
- package/src/modules/communication_channels/data/validators.ts +138 -0
- package/src/modules/communication_channels/di.ts +40 -0
- package/src/modules/communication_channels/encryption.ts +44 -0
- package/src/modules/communication_channels/events.ts +122 -0
- package/src/modules/communication_channels/i18n/de.json +138 -0
- package/src/modules/communication_channels/i18n/en.json +138 -0
- package/src/modules/communication_channels/i18n/es.json +138 -0
- package/src/modules/communication_channels/i18n/pl.json +138 -0
- package/src/modules/communication_channels/index.ts +19 -0
- package/src/modules/communication_channels/lib/access-control.ts +110 -0
- package/src/modules/communication_channels/lib/adapter-compat.ts +57 -0
- package/src/modules/communication_channels/lib/adapter-registry-singleton.ts +35 -0
- package/src/modules/communication_channels/lib/adapter.ts +605 -0
- package/src/modules/communication_channels/lib/connect-channel.ts +163 -0
- package/src/modules/communication_channels/lib/contact-resolver.ts +162 -0
- package/src/modules/communication_channels/lib/credential-refresh.ts +197 -0
- package/src/modules/communication_channels/lib/dead-letter.ts +87 -0
- package/src/modules/communication_channels/lib/email-capabilities.ts +60 -0
- package/src/modules/communication_channels/lib/email-contact.ts +17 -0
- package/src/modules/communication_channels/lib/email-mime.ts +425 -0
- package/src/modules/communication_channels/lib/error-classification.ts +144 -0
- package/src/modules/communication_channels/lib/gmail-pubsub-jwt.ts +278 -0
- package/src/modules/communication_channels/lib/mutation-guards.ts +215 -0
- package/src/modules/communication_channels/lib/oauth-client-config.ts +79 -0
- package/src/modules/communication_channels/lib/oauth-state.ts +228 -0
- package/src/modules/communication_channels/lib/oauth-token.ts +81 -0
- package/src/modules/communication_channels/lib/pg-errors.ts +12 -0
- package/src/modules/communication_channels/lib/provider-health.ts +47 -0
- package/src/modules/communication_channels/lib/push-state.ts +38 -0
- package/src/modules/communication_channels/lib/queue.ts +66 -0
- package/src/modules/communication_channels/lib/reaction-processor-types.ts +51 -0
- package/src/modules/communication_channels/lib/reaction-semantics.ts +48 -0
- package/src/modules/communication_channels/lib/registry.ts +99 -0
- package/src/modules/communication_channels/lib/route-mutation-guard.ts +68 -0
- package/src/modules/communication_channels/lib/sanitize-channel-html.ts +129 -0
- package/src/modules/communication_channels/lib/send-as-user.ts +284 -0
- package/src/modules/communication_channels/lib/system-user.ts +74 -0
- package/src/modules/communication_channels/lib/test-seed.ts +140 -0
- package/src/modules/communication_channels/lib/thread-matcher.ts +430 -0
- package/src/modules/communication_channels/lib/thread-token.ts +355 -0
- package/src/modules/communication_channels/lib/use-connect-channel.ts +73 -0
- package/src/modules/communication_channels/migrations/.snapshot-open-mercato.json +2142 -0
- package/src/modules/communication_channels/migrations/Migration20260526134719_communication_channels.ts +55 -0
- package/src/modules/communication_channels/migrations/Migration20260527195446_communication_channels.ts +20 -0
- package/src/modules/communication_channels/migrations/Migration20260529231848_communication_channels.ts +13 -0
- package/src/modules/communication_channels/migrations/Migration20260531120000_communication_channels.ts +24 -0
- package/src/modules/communication_channels/notifications.client.ts +50 -0
- package/src/modules/communication_channels/notifications.handlers.ts +86 -0
- package/src/modules/communication_channels/notifications.ts +52 -0
- package/src/modules/communication_channels/setup.ts +158 -0
- package/src/modules/communication_channels/subscribers/channel-requires-reauth-notification.ts +118 -0
- package/src/modules/communication_channels/subscribers/outbound-bridge.ts +175 -0
- package/src/modules/communication_channels/subscribers/user-deleted-cascade.ts +100 -0
- package/src/modules/communication_channels/widgets/components.ts +36 -0
- package/src/modules/communication_channels/widgets/injection/channel-badge/widget.client.tsx +38 -0
- package/src/modules/communication_channels/widgets/injection/channel-badge/widget.ts +51 -0
- package/src/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.tsx +278 -0
- package/src/modules/communication_channels/widgets/injection/channel-info-panel/widget.ts +24 -0
- package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.tsx +63 -0
- package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.ts +29 -0
- package/src/modules/communication_channels/widgets/injection/profile-channels-menu/widget.ts +34 -0
- package/src/modules/communication_channels/widgets/injection/reaction-bar/widget.client.tsx +177 -0
- package/src/modules/communication_channels/widgets/injection/reaction-bar/widget.ts +26 -0
- package/src/modules/communication_channels/widgets/injection-table.ts +47 -0
- package/src/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.tsx +48 -0
- package/src/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.tsx +45 -0
- package/src/modules/communication_channels/widgets/notifications/index.ts +2 -0
- package/src/modules/communication_channels/workers/channel-import-history.ts +252 -0
- package/src/modules/communication_channels/workers/gmail-history-sync.ts +223 -0
- package/src/modules/communication_channels/workers/gmail-renew-watch.ts +141 -0
- package/src/modules/communication_channels/workers/inbound-processor.ts +114 -0
- package/src/modules/communication_channels/workers/outbound-delivery.ts +155 -0
- package/src/modules/communication_channels/workers/poll-channel.ts +391 -0
- package/src/modules/communication_channels/workers/poll-tick.ts +210 -0
- package/src/modules/communication_channels/workers/reaction-processor.ts +264 -0
- package/src/modules/customers/acl.ts +18 -0
- package/src/modules/customers/api/activities/route.ts +13 -0
- package/src/modules/customers/api/companies/[id]/route.ts +21 -1
- package/src/modules/customers/api/interactions/[id]/visibility/route.ts +179 -0
- package/src/modules/customers/api/interactions/counts/route.ts +10 -0
- package/src/modules/customers/api/interactions/route.ts +51 -5
- package/src/modules/customers/api/people/[id]/email-threads/route.ts +92 -0
- package/src/modules/customers/api/people/[id]/emails/route.ts +184 -0
- package/src/modules/customers/api/people/[id]/route.ts +17 -2
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +11 -1
- package/src/modules/customers/commands/deals.ts +65 -6
- package/src/modules/customers/commands/interactions.ts +30 -0
- package/src/modules/customers/components/detail/ActivityCard.tsx +48 -0
- package/src/modules/customers/components/detail/ComposeEmailDialog.tsx +329 -0
- package/src/modules/customers/components/detail/DealForm.tsx +2 -1
- package/src/modules/customers/components/detail/DealsSection.tsx +26 -0
- package/src/modules/customers/components/detail/EmailCardActions.tsx +258 -0
- package/src/modules/customers/components/detail/EmailReplyForwardActions.tsx +53 -0
- package/src/modules/customers/components/detail/PersonDetailTabs.tsx +8 -1
- package/src/modules/customers/components/detail/PersonEmailThreadsTab.tsx +448 -0
- package/src/modules/customers/data/enrichers.ts +252 -1
- package/src/modules/customers/data/entities.ts +46 -1
- package/src/modules/customers/data/extensions.ts +26 -0
- package/src/modules/customers/encryption.ts +11 -0
- package/src/modules/customers/events.ts +4 -0
- package/src/modules/customers/i18n/de.json +41 -0
- package/src/modules/customers/i18n/en.json +41 -0
- package/src/modules/customers/i18n/es.json +41 -0
- package/src/modules/customers/i18n/pl.json +41 -0
- package/src/modules/customers/lib/findPeopleByAddresses.ts +107 -0
- package/src/modules/customers/lib/kysely.ts +16 -0
- package/src/modules/customers/lib/link-channel-message-handler.ts +571 -0
- package/src/modules/customers/lib/personEmailThreads.ts +325 -0
- package/src/modules/customers/lib/visibilityFilter.ts +152 -0
- package/src/modules/customers/migrations/.snapshot-open-mercato.json +61 -0
- package/src/modules/customers/migrations/Migration20260527012240_customers.ts +23 -0
- package/src/modules/customers/setup.ts +1 -0
- package/src/modules/customers/subscribers/link-channel-message-received.ts +21 -0
- package/src/modules/customers/subscribers/link-channel-message-sent.ts +21 -0
- package/src/modules/integrations/AGENTS.md +9 -0
- package/src/modules/integrations/data/entities.ts +21 -1
- package/src/modules/integrations/lib/credentials-service.ts +49 -13
- package/src/modules/integrations/migrations/.snapshot-open-mercato.json +26 -1
- package/src/modules/integrations/migrations/Migration20260526154136_integrations.ts +15 -0
- package/src/modules/messages/commands/messages.ts +101 -8
- package/src/modules/messages/components/ComposeMessagePageClient.tsx +17 -0
- package/src/modules/messages/components/MessageDetailPageClient.tsx +43 -0
- package/src/modules/messages/components/MessagesInboxPageClient.tsx +4 -0
- package/src/modules/messages/data/entities.ts +11 -0
- package/src/modules/messages/migrations/.snapshot-open-mercato.json +18 -0
- package/src/modules/messages/migrations/Migration20260531130000.ts +15 -0
- package/src/modules/messages/widgets/injection-table.ts +29 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createCrudFormError } from "@open-mercato/ui/backend/utils/serverErrors";
|
|
3
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
4
|
+
import { CommunicationChannel } from "../data/entities.js";
|
|
5
|
+
import { getChannelAdapterRegistry } from "../lib/adapter-registry-singleton.js";
|
|
6
|
+
import { COMMUNICATION_CHANNELS_QUEUES, getCommunicationChannelsQueue } from "../lib/queue.js";
|
|
7
|
+
const queueImportHistorySchema = z.object({
|
|
8
|
+
channelId: z.string().uuid(),
|
|
9
|
+
sinceDays: z.number().int().min(1).max(365).default(30),
|
|
10
|
+
contactEmails: z.array(z.string().email().max(255)).max(200).optional(),
|
|
11
|
+
maxMessages: z.number().int().min(1).max(5e3).default(1e3)
|
|
12
|
+
});
|
|
13
|
+
const CHANNEL_IMPORT_HISTORY_JOB_TYPE = "communication_channels.channel.import_history";
|
|
14
|
+
async function queueImportHistory(params) {
|
|
15
|
+
const input = queueImportHistorySchema.parse(params.input);
|
|
16
|
+
const { container, scope } = params;
|
|
17
|
+
const em = container.resolve("em").fork();
|
|
18
|
+
const dscope = { tenantId: scope.tenantId, organizationId: scope.organizationId };
|
|
19
|
+
const channel = await findOneWithDecryption(
|
|
20
|
+
em,
|
|
21
|
+
CommunicationChannel,
|
|
22
|
+
{
|
|
23
|
+
id: input.channelId,
|
|
24
|
+
tenantId: scope.tenantId,
|
|
25
|
+
organizationId: scope.organizationId,
|
|
26
|
+
deletedAt: null
|
|
27
|
+
},
|
|
28
|
+
void 0,
|
|
29
|
+
dscope
|
|
30
|
+
);
|
|
31
|
+
if (!channel) {
|
|
32
|
+
throw createCrudFormError(
|
|
33
|
+
"Channel not found",
|
|
34
|
+
{ channelId: "Channel not found" },
|
|
35
|
+
{ status: 404 }
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
if (!channel.isActive || channel.status !== "connected") {
|
|
39
|
+
throw createCrudFormError(
|
|
40
|
+
"Channel is not connected",
|
|
41
|
+
{ channelId: "Channel must be connected to import history" },
|
|
42
|
+
{ status: 409 }
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const adapter = getChannelAdapterRegistry().get(channel.providerKey);
|
|
46
|
+
if (!adapter) {
|
|
47
|
+
throw createCrudFormError(
|
|
48
|
+
"Channel provider is not available",
|
|
49
|
+
{ channelId: `No adapter registered for provider "${channel.providerKey}"` },
|
|
50
|
+
{ status: 400 }
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
if (typeof adapter.importHistory !== "function") {
|
|
54
|
+
throw createCrudFormError(
|
|
55
|
+
"History import is not supported on this provider",
|
|
56
|
+
{ channelId: `Provider "${channel.providerKey}" does not support history import yet` },
|
|
57
|
+
{ status: 400 }
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
const progressService = container.resolve("progressService");
|
|
61
|
+
const progressContext = {
|
|
62
|
+
tenantId: scope.tenantId,
|
|
63
|
+
organizationId: scope.organizationId,
|
|
64
|
+
userId: scope.userId ?? null
|
|
65
|
+
};
|
|
66
|
+
const active = await progressService.getActiveJobs(progressContext);
|
|
67
|
+
const conflict = active.find((job) => {
|
|
68
|
+
if (job.jobType !== CHANNEL_IMPORT_HISTORY_JOB_TYPE) return false;
|
|
69
|
+
const meta = job.meta ?? {};
|
|
70
|
+
return meta.channelId === channel.id;
|
|
71
|
+
});
|
|
72
|
+
if (conflict) {
|
|
73
|
+
throw createCrudFormError(
|
|
74
|
+
"An import is already in progress for this channel",
|
|
75
|
+
{ channelId: "Another history import is already running" },
|
|
76
|
+
{ status: 429 }
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const progressJob = await progressService.createJob(
|
|
80
|
+
{
|
|
81
|
+
jobType: CHANNEL_IMPORT_HISTORY_JOB_TYPE,
|
|
82
|
+
name: `Import history: ${channel.displayName ?? channel.externalIdentifier ?? channel.providerKey}`,
|
|
83
|
+
description: `Import up to ${input.maxMessages} messages from the last ${input.sinceDays} days`,
|
|
84
|
+
totalCount: input.maxMessages,
|
|
85
|
+
cancellable: true,
|
|
86
|
+
meta: {
|
|
87
|
+
channelId: channel.id,
|
|
88
|
+
providerKey: channel.providerKey,
|
|
89
|
+
sinceDays: input.sinceDays,
|
|
90
|
+
contactEmailsCount: input.contactEmails?.length ?? 0,
|
|
91
|
+
maxMessages: input.maxMessages
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
progressContext
|
|
95
|
+
);
|
|
96
|
+
const payload = {
|
|
97
|
+
progressJobId: progressJob.id,
|
|
98
|
+
channelId: channel.id,
|
|
99
|
+
sinceDays: input.sinceDays,
|
|
100
|
+
contactEmails: input.contactEmails,
|
|
101
|
+
maxMessages: input.maxMessages,
|
|
102
|
+
scope: { tenantId: scope.tenantId, organizationId: scope.organizationId }
|
|
103
|
+
};
|
|
104
|
+
const queue = getCommunicationChannelsQueue(COMMUNICATION_CHANNELS_QUEUES.importHistory);
|
|
105
|
+
await queue.enqueue(payload);
|
|
106
|
+
return { progressJobId: progressJob.id, totalCountHint: input.maxMessages };
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
CHANNEL_IMPORT_HISTORY_JOB_TYPE,
|
|
110
|
+
queueImportHistory,
|
|
111
|
+
queueImportHistorySchema
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=queue-import-history.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/communication_channels/commands/queue-import-history.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type {\n ProgressService,\n ProgressServiceContext,\n} from '../../progress/lib/progressService'\nimport { CommunicationChannel } from '../data/entities'\nimport { getChannelAdapterRegistry } from '../lib/adapter-registry-singleton'\nimport { COMMUNICATION_CHANNELS_QUEUES, getCommunicationChannelsQueue } from '../lib/queue'\n\n/**\n * Spec B \u00A7 Phase B6 \u2014 operator-triggered backlog import.\n *\n * Validates the channel + adapter capability, creates a `ProgressJob`, then\n * enqueues a `channel-import-history` job. Returns the job id so the UI can\n * track progress via the existing ProgressTopBar.\n *\n * Concurrency guard: at most one in-flight import per channel \u2014 a second call\n * while the first is still running returns a 429 envelope via\n * `createCrudFormError`. The worker itself runs with `concurrency: 1` so a\n * dropped guard is still safe.\n */\n\nexport const queueImportHistorySchema = z.object({\n channelId: z.string().uuid(),\n sinceDays: z.number().int().min(1).max(365).default(30),\n contactEmails: z\n .array(z.string().email().max(255))\n .max(200)\n .optional(),\n maxMessages: z.number().int().min(1).max(5000).default(1000),\n})\n\nexport type QueueImportHistoryInput = z.infer<typeof queueImportHistorySchema>\n\nexport interface QueueImportHistoryScope {\n tenantId: string\n organizationId: string\n userId?: string | null\n}\n\nexport interface QueueImportHistoryResult {\n progressJobId: string\n totalCountHint: number\n}\n\nexport const CHANNEL_IMPORT_HISTORY_JOB_TYPE = 'communication_channels.channel.import_history'\n\nexport interface ChannelImportHistoryJobPayload {\n progressJobId: string\n channelId: string\n sinceDays: number\n contactEmails?: string[]\n maxMessages: number\n scope: { tenantId: string; organizationId: string }\n}\n\nexport async function queueImportHistory(params: {\n container: AwilixContainer\n scope: QueueImportHistoryScope\n input: QueueImportHistoryInput\n}): Promise<QueueImportHistoryResult> {\n const input = queueImportHistorySchema.parse(params.input)\n const { container, scope } = params\n\n const em = (container.resolve('em') as EntityManager).fork()\n const dscope = { tenantId: scope.tenantId, organizationId: scope.organizationId }\n\n const channel = await findOneWithDecryption(\n em,\n CommunicationChannel,\n {\n id: input.channelId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n undefined,\n dscope,\n )\n if (!channel) {\n throw createCrudFormError(\n 'Channel not found',\n { channelId: 'Channel not found' },\n { status: 404 },\n )\n }\n if (!channel.isActive || channel.status !== 'connected') {\n // 409 (not 400): the request is well-formed but the channel is in a\n // conflicting state (requires_reauth / error / disconnected). The UI maps\n // this to the localized \"reconnect first\" flash; see spec \u00A7 API Contracts.\n throw createCrudFormError(\n 'Channel is not connected',\n { channelId: 'Channel must be connected to import history' },\n { status: 409 },\n )\n }\n\n // Adapter must declare `importHistory` \u2014 Gmail / IMAP adapters gain it\n // in Spec C. Other providers (chat, SMS) cannot do historical inbox sweeps.\n const adapter = getChannelAdapterRegistry().get(channel.providerKey)\n if (!adapter) {\n throw createCrudFormError(\n 'Channel provider is not available',\n { channelId: `No adapter registered for provider \"${channel.providerKey}\"` },\n { status: 400 },\n )\n }\n if (typeof adapter.importHistory !== 'function') {\n throw createCrudFormError(\n 'History import is not supported on this provider',\n { channelId: `Provider \"${channel.providerKey}\" does not support history import yet` },\n { status: 400 },\n )\n }\n\n const progressService = container.resolve('progressService') as ProgressService\n const progressContext: ProgressServiceContext = {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId ?? null,\n }\n\n // Concurrency guard (best-effort): refuse if another import is already\n // in-flight for the same channel. We can't query by meta in a portable way\n // without DB-specific JSON operators, so we scan the small active-jobs window.\n // This check and `createJob` below are NOT atomic \u2014 two near-simultaneous\n // requests for the same channel can both pass and enqueue. That is acceptable,\n // not corrupting: the import worker runs at concurrency 1 (duplicate jobs run\n // sequentially, never in parallel) and inbound ingest dedups by the\n // `(channel_id, external_message_id)` unique index, so the worst case is\n // wasted re-fetch work, never duplicate rows.\n const active = await progressService.getActiveJobs(progressContext)\n const conflict = active.find((job) => {\n if (job.jobType !== CHANNEL_IMPORT_HISTORY_JOB_TYPE) return false\n const meta = (job.meta ?? {}) as Record<string, unknown>\n return meta.channelId === channel.id\n })\n if (conflict) {\n throw createCrudFormError(\n 'An import is already in progress for this channel',\n { channelId: 'Another history import is already running' },\n { status: 429 },\n )\n }\n\n const progressJob = await progressService.createJob(\n {\n jobType: CHANNEL_IMPORT_HISTORY_JOB_TYPE,\n name: `Import history: ${channel.displayName ?? channel.externalIdentifier ?? channel.providerKey}`,\n description: `Import up to ${input.maxMessages} messages from the last ${input.sinceDays} days`,\n totalCount: input.maxMessages,\n cancellable: true,\n meta: {\n channelId: channel.id,\n providerKey: channel.providerKey,\n sinceDays: input.sinceDays,\n contactEmailsCount: input.contactEmails?.length ?? 0,\n maxMessages: input.maxMessages,\n },\n },\n progressContext,\n )\n\n const payload: ChannelImportHistoryJobPayload = {\n progressJobId: progressJob.id,\n channelId: channel.id,\n sinceDays: input.sinceDays,\n contactEmails: input.contactEmails,\n maxMessages: input.maxMessages,\n scope: { tenantId: scope.tenantId, organizationId: scope.organizationId },\n }\n const queue = getCommunicationChannelsQueue(COMMUNICATION_CHANNELS_QUEUES.importHistory)\n await queue.enqueue(payload as unknown as Record<string, unknown>)\n\n return { progressJobId: progressJob.id, totalCountHint: input.maxMessages }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { registerCommand } from "@open-mercato/shared/lib/commands";
|
|
3
|
+
import { extractUndoPayload as extractSharedUndoPayload } from "@open-mercato/shared/lib/commands/undo";
|
|
4
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
5
|
+
import { ChannelThreadMapping, ExternalConversation } from "../data/entities.js";
|
|
6
|
+
import { emitCommunicationChannelsEvent } from "../events.js";
|
|
7
|
+
const reassignConversationSchema = z.object({
|
|
8
|
+
threadId: z.string().uuid(),
|
|
9
|
+
/** Set to `null` to unassign the conversation. */
|
|
10
|
+
assignedUserId: z.string().uuid().nullable(),
|
|
11
|
+
scope: z.object({
|
|
12
|
+
tenantId: z.string().uuid(),
|
|
13
|
+
organizationId: z.string().uuid().nullable()
|
|
14
|
+
})
|
|
15
|
+
});
|
|
16
|
+
const COMMUNICATION_CHANNELS_REASSIGN_CONVERSATION_COMMAND_ID = "communication_channels.conversation.reassign";
|
|
17
|
+
const reassignConversationCommand = {
|
|
18
|
+
id: COMMUNICATION_CHANNELS_REASSIGN_CONVERSATION_COMMAND_ID,
|
|
19
|
+
// Explicitly undoable (the bus also infers this from `undo` below, but
|
|
20
|
+
// declaring it keeps undoability from silently dropping under a refactor).
|
|
21
|
+
isUndoable: true,
|
|
22
|
+
async execute(rawInput, ctx) {
|
|
23
|
+
const input = reassignConversationSchema.parse(rawInput);
|
|
24
|
+
const em = ctx.container.resolve("em").fork();
|
|
25
|
+
const dscope = {
|
|
26
|
+
tenantId: input.scope.tenantId,
|
|
27
|
+
organizationId: input.scope.organizationId ?? null
|
|
28
|
+
};
|
|
29
|
+
const mapping = await findOneWithDecryption(
|
|
30
|
+
em,
|
|
31
|
+
ChannelThreadMapping,
|
|
32
|
+
{
|
|
33
|
+
messageThreadId: input.threadId,
|
|
34
|
+
tenantId: input.scope.tenantId,
|
|
35
|
+
organizationId: input.scope.organizationId ?? null
|
|
36
|
+
},
|
|
37
|
+
// `message_thread_id` is non-unique (only `(external_conversation_id,
|
|
38
|
+
// tenant_id)` is unique). The 1:1 thread↔mapping invariant holds in v1,
|
|
39
|
+
// but order deterministically so a future many-conversations-per-thread
|
|
40
|
+
// merge can never resolve an arbitrary mapping here.
|
|
41
|
+
{ orderBy: { createdAt: "asc" } },
|
|
42
|
+
dscope
|
|
43
|
+
);
|
|
44
|
+
if (!mapping) {
|
|
45
|
+
return {
|
|
46
|
+
status: "no_channel_link",
|
|
47
|
+
reason: `no ChannelThreadMapping for thread ${input.threadId}`
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const previousAssignedUserId = mapping.assignedUserId ?? null;
|
|
51
|
+
if (previousAssignedUserId === input.assignedUserId) {
|
|
52
|
+
return { status: "noop", reason: "assigned user unchanged" };
|
|
53
|
+
}
|
|
54
|
+
if (input.assignedUserId) {
|
|
55
|
+
const assignee = await findOneWithDecryption(
|
|
56
|
+
em,
|
|
57
|
+
"User",
|
|
58
|
+
{ id: input.assignedUserId, tenantId: input.scope.tenantId, deletedAt: null },
|
|
59
|
+
void 0,
|
|
60
|
+
dscope
|
|
61
|
+
);
|
|
62
|
+
if (!assignee) {
|
|
63
|
+
return {
|
|
64
|
+
status: "invalid_assignee",
|
|
65
|
+
reason: "assigned user is not a member of this tenant"
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const conversation = await findOneWithDecryption(
|
|
70
|
+
em,
|
|
71
|
+
ExternalConversation,
|
|
72
|
+
{
|
|
73
|
+
id: mapping.externalConversationId,
|
|
74
|
+
tenantId: input.scope.tenantId,
|
|
75
|
+
organizationId: input.scope.organizationId ?? null
|
|
76
|
+
},
|
|
77
|
+
void 0,
|
|
78
|
+
dscope
|
|
79
|
+
);
|
|
80
|
+
if (!conversation) {
|
|
81
|
+
return {
|
|
82
|
+
status: "no_channel_link",
|
|
83
|
+
reason: `no ExternalConversation for thread ${input.threadId}`
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
mapping.assignedUserId = input.assignedUserId;
|
|
87
|
+
conversation.assignedUserId = input.assignedUserId;
|
|
88
|
+
await em.flush();
|
|
89
|
+
try {
|
|
90
|
+
await emitCommunicationChannelsEvent(
|
|
91
|
+
"communication_channels.conversation.reassigned",
|
|
92
|
+
{
|
|
93
|
+
conversationId: conversation.id,
|
|
94
|
+
channelId: mapping.channelId,
|
|
95
|
+
messageThreadId: input.threadId,
|
|
96
|
+
previousAssignedUserId,
|
|
97
|
+
assignedUserId: input.assignedUserId,
|
|
98
|
+
tenantId: input.scope.tenantId,
|
|
99
|
+
organizationId: input.scope.organizationId ?? null
|
|
100
|
+
},
|
|
101
|
+
{ persistent: true }
|
|
102
|
+
);
|
|
103
|
+
} catch (emitErr) {
|
|
104
|
+
console.warn(
|
|
105
|
+
"[communication_channels:reassign-conversation] reassigned event emit failed:",
|
|
106
|
+
emitErr instanceof Error ? emitErr.message : emitErr
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
const undo = {
|
|
110
|
+
threadMappingId: mapping.id,
|
|
111
|
+
conversationId: conversation.id,
|
|
112
|
+
tenantId: mapping.tenantId,
|
|
113
|
+
previousAssignedUserId,
|
|
114
|
+
newAssignedUserId: input.assignedUserId
|
|
115
|
+
};
|
|
116
|
+
return {
|
|
117
|
+
status: "reassigned",
|
|
118
|
+
threadId: input.threadId,
|
|
119
|
+
previousAssignedUserId,
|
|
120
|
+
nextAssignedUserId: input.assignedUserId,
|
|
121
|
+
conversationId: conversation.id,
|
|
122
|
+
undo
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
// Persist the undo snapshot into the action log. Without this, the command bus
|
|
126
|
+
// mints an undo token (so the UI offers "Undo") but the snapshot returned from
|
|
127
|
+
// execute() is never stored, and undo() would silently no-op.
|
|
128
|
+
async buildLog({ input, result }) {
|
|
129
|
+
if (result.status !== "reassigned") return null;
|
|
130
|
+
return {
|
|
131
|
+
resourceKind: "communication_channels.channel",
|
|
132
|
+
resourceId: result.conversationId,
|
|
133
|
+
tenantId: result.undo.tenantId ?? input.scope.tenantId,
|
|
134
|
+
organizationId: input.scope.organizationId ?? null,
|
|
135
|
+
payload: { undo: result.undo },
|
|
136
|
+
snapshotBefore: result.undo
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
async undo({ ctx, logEntry }) {
|
|
140
|
+
const snapshot = extractSnapshotFromLog(logEntry);
|
|
141
|
+
if (!snapshot) return;
|
|
142
|
+
const em = ctx.container.resolve("em").fork();
|
|
143
|
+
if (!snapshot.tenantId) return;
|
|
144
|
+
const dscope = { tenantId: snapshot.tenantId, organizationId: null };
|
|
145
|
+
const mapping = await findOneWithDecryption(
|
|
146
|
+
em,
|
|
147
|
+
ChannelThreadMapping,
|
|
148
|
+
{ id: snapshot.threadMappingId, tenantId: snapshot.tenantId },
|
|
149
|
+
void 0,
|
|
150
|
+
dscope
|
|
151
|
+
);
|
|
152
|
+
const conversation = await findOneWithDecryption(
|
|
153
|
+
em,
|
|
154
|
+
ExternalConversation,
|
|
155
|
+
{ id: snapshot.conversationId, tenantId: snapshot.tenantId },
|
|
156
|
+
void 0,
|
|
157
|
+
dscope
|
|
158
|
+
);
|
|
159
|
+
if (mapping) mapping.assignedUserId = snapshot.previousAssignedUserId;
|
|
160
|
+
if (conversation) conversation.assignedUserId = snapshot.previousAssignedUserId;
|
|
161
|
+
if (mapping || conversation) await em.flush();
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
function extractUndoPayload(value) {
|
|
165
|
+
if (!value || typeof value !== "object") return null;
|
|
166
|
+
const candidate = value.undo ?? value;
|
|
167
|
+
if (!candidate || typeof candidate !== "object") return null;
|
|
168
|
+
const obj = candidate;
|
|
169
|
+
if (typeof obj.threadMappingId !== "string" || typeof obj.conversationId !== "string") return null;
|
|
170
|
+
return {
|
|
171
|
+
threadMappingId: obj.threadMappingId,
|
|
172
|
+
conversationId: obj.conversationId,
|
|
173
|
+
tenantId: typeof obj.tenantId === "string" ? obj.tenantId : void 0,
|
|
174
|
+
previousAssignedUserId: typeof obj.previousAssignedUserId === "string" ? obj.previousAssignedUserId : null,
|
|
175
|
+
newAssignedUserId: typeof obj.newAssignedUserId === "string" ? obj.newAssignedUserId : null
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function extractSnapshotFromLog(logEntry) {
|
|
179
|
+
const undo = extractSharedUndoPayload(
|
|
180
|
+
logEntry ?? null
|
|
181
|
+
);
|
|
182
|
+
if (undo) return extractUndoPayload(undo);
|
|
183
|
+
return extractUndoPayload(logEntry);
|
|
184
|
+
}
|
|
185
|
+
registerCommand(reassignConversationCommand);
|
|
186
|
+
var reassign_conversation_default = reassignConversationCommand;
|
|
187
|
+
export {
|
|
188
|
+
COMMUNICATION_CHANNELS_REASSIGN_CONVERSATION_COMMAND_ID,
|
|
189
|
+
reassign_conversation_default as default,
|
|
190
|
+
extractSnapshotFromLog,
|
|
191
|
+
extractUndoPayload
|
|
192
|
+
};
|
|
193
|
+
//# sourceMappingURL=reassign-conversation.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/communication_channels/commands/reassign-conversation.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { extractUndoPayload as extractSharedUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { ChannelThreadMapping, ExternalConversation } from '../data/entities'\nimport { emitCommunicationChannelsEvent } from '../events'\n\nconst reassignConversationSchema = z.object({\n threadId: z.string().uuid(),\n /** Set to `null` to unassign the conversation. */\n assignedUserId: z.string().uuid().nullable(),\n scope: z.object({\n tenantId: z.string().uuid(),\n organizationId: z.string().uuid().nullable(),\n }),\n})\n\nexport type ReassignConversationInput = z.infer<typeof reassignConversationSchema>\n\nexport type ReassignConversationResult =\n | {\n status: 'reassigned'\n threadId: string\n previousAssignedUserId: string | null\n nextAssignedUserId: string | null\n conversationId: string\n undo: ReassignConversationUndoSnapshot\n }\n | { status: 'no_channel_link'; reason: string }\n | { status: 'invalid_assignee'; reason: string }\n | { status: 'noop'; reason: string }\n\nexport interface ReassignConversationUndoSnapshot {\n threadMappingId: string\n conversationId: string\n // Optional for backward compatibility with log entries written before tenant\n // scoping was added to the undo lookup; new snapshots always set it.\n tenantId?: string\n previousAssignedUserId: string | null\n newAssignedUserId: string | null\n}\n\nexport const COMMUNICATION_CHANNELS_REASSIGN_CONVERSATION_COMMAND_ID =\n 'communication_channels.conversation.reassign'\n\n/**\n * Reassign the owning user of a channel-linked conversation.\n *\n * Updates both `ChannelThreadMapping.assignedUserId` and the linked\n * `ExternalConversation.assignedUserId` so subscribers (notification handlers,\n * future dashboards) see a consistent owner. No external provider call \u2014\n * reassignment is an internal-routing concern.\n *\n * Idempotent: when the new owner matches the existing one, returns `noop`.\n *\n * The command is undoable: the `before` snapshot captures the previous owner on\n * both rows so undo can restore them atomically.\n */\nconst reassignConversationCommand: CommandHandler<\n ReassignConversationInput,\n ReassignConversationResult\n> = {\n id: COMMUNICATION_CHANNELS_REASSIGN_CONVERSATION_COMMAND_ID,\n // Explicitly undoable (the bus also infers this from `undo` below, but\n // declaring it keeps undoability from silently dropping under a refactor).\n isUndoable: true,\n async execute(rawInput, ctx) {\n const input = reassignConversationSchema.parse(rawInput) as ReassignConversationInput\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const dscope = {\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId ?? null,\n }\n\n const mapping = await findOneWithDecryption(\n em,\n ChannelThreadMapping,\n {\n messageThreadId: input.threadId,\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId ?? null,\n },\n // `message_thread_id` is non-unique (only `(external_conversation_id,\n // tenant_id)` is unique). The 1:1 thread\u2194mapping invariant holds in v1,\n // but order deterministically so a future many-conversations-per-thread\n // merge can never resolve an arbitrary mapping here.\n { orderBy: { createdAt: 'asc' } },\n dscope,\n )\n if (!mapping) {\n return {\n status: 'no_channel_link',\n reason: `no ChannelThreadMapping for thread ${input.threadId}`,\n }\n }\n\n const previousAssignedUserId = mapping.assignedUserId ?? null\n if (previousAssignedUserId === input.assignedUserId) {\n return { status: 'noop', reason: 'assigned user unchanged' }\n }\n\n // Reject an assignee that is not a live user of this tenant \u2014 a UUID-shaped\n // body alone must not create a cross-tenant / dangling owner reference.\n if (input.assignedUserId) {\n // Reference the `auth` user row by string entity name so this command does\n // not import the auth module's entities (module independence); `as never`\n // matches the codebase pattern for cross-module decrypted reads.\n const assignee = await findOneWithDecryption(\n em,\n 'User' as never,\n { id: input.assignedUserId, tenantId: input.scope.tenantId, deletedAt: null } as never,\n undefined,\n dscope,\n )\n if (!assignee) {\n return {\n status: 'invalid_assignee',\n reason: 'assigned user is not a member of this tenant',\n }\n }\n }\n\n const conversation = await findOneWithDecryption(\n em,\n ExternalConversation,\n {\n id: mapping.externalConversationId,\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId ?? null,\n },\n undefined,\n dscope,\n )\n if (!conversation) {\n return {\n status: 'no_channel_link',\n reason: `no ExternalConversation for thread ${input.threadId}`,\n }\n }\n\n // `assignedUserId` is an advisory routing pointer, not a DB foreign key\n // (modules don't share ORM relations). The assignee-existence check above\n // and this write are not atomic, so a user deleted in between leaves a\n // harmless dangling pointer \u2014 consistent with the module's no-FK design.\n mapping.assignedUserId = input.assignedUserId\n conversation.assignedUserId = input.assignedUserId\n await em.flush()\n\n try {\n await emitCommunicationChannelsEvent(\n 'communication_channels.conversation.reassigned',\n {\n conversationId: conversation.id,\n channelId: mapping.channelId,\n messageThreadId: input.threadId,\n previousAssignedUserId,\n assignedUserId: input.assignedUserId,\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId ?? null,\n },\n { persistent: true },\n )\n } catch (emitErr) {\n // Best-effort lifecycle/workflow-trigger signal \u2014 a bus failure must not\n // abort the reassignment (the rows are already committed above).\n console.warn(\n '[communication_channels:reassign-conversation] reassigned event emit failed:',\n emitErr instanceof Error ? emitErr.message : emitErr,\n )\n }\n\n const undo: ReassignConversationUndoSnapshot = {\n threadMappingId: mapping.id,\n conversationId: conversation.id,\n tenantId: mapping.tenantId,\n previousAssignedUserId,\n newAssignedUserId: input.assignedUserId,\n }\n\n return {\n status: 'reassigned',\n threadId: input.threadId,\n previousAssignedUserId,\n nextAssignedUserId: input.assignedUserId,\n conversationId: conversation.id,\n undo,\n }\n },\n // Persist the undo snapshot into the action log. Without this, the command bus\n // mints an undo token (so the UI offers \"Undo\") but the snapshot returned from\n // execute() is never stored, and undo() would silently no-op.\n async buildLog({ input, result }) {\n if (result.status !== 'reassigned') return null\n return {\n resourceKind: 'communication_channels.channel',\n resourceId: result.conversationId,\n tenantId: result.undo.tenantId ?? input.scope.tenantId,\n organizationId: input.scope.organizationId ?? null,\n payload: { undo: result.undo },\n snapshotBefore: result.undo,\n }\n },\n async undo({ ctx, logEntry }) {\n const snapshot = extractSnapshotFromLog(logEntry)\n if (!snapshot) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n // Never resolve by bare id (cross-tenant). New snapshots always carry\n // tenantId; refuse the undo if a legacy snapshot lacks it.\n if (!snapshot.tenantId) return\n const dscope = { tenantId: snapshot.tenantId, organizationId: null }\n\n const mapping = await findOneWithDecryption(\n em,\n ChannelThreadMapping,\n { id: snapshot.threadMappingId, tenantId: snapshot.tenantId },\n undefined,\n dscope,\n )\n const conversation = await findOneWithDecryption(\n em,\n ExternalConversation,\n { id: snapshot.conversationId, tenantId: snapshot.tenantId },\n undefined,\n dscope,\n )\n if (mapping) mapping.assignedUserId = snapshot.previousAssignedUserId\n if (conversation) conversation.assignedUserId = snapshot.previousAssignedUserId\n if (mapping || conversation) await em.flush()\n },\n}\n\n/**\n * Read the undo payload defensively \u2014 wraps the shared\n * `@open-mercato/shared/lib/commands/undo.ts` helper with a narrow-by-shape\n * validation so callers get a strongly-typed snapshot or `null`.\n *\n * Kept as a separate export for test ergonomics (tests can mock the snapshot\n * shape directly without round-tripping through a CommandLogEntry).\n */\nexport function extractUndoPayload(value: unknown): ReassignConversationUndoSnapshot | null {\n if (!value || typeof value !== 'object') return null\n const candidate = (value as { undo?: unknown }).undo ?? value\n if (!candidate || typeof candidate !== 'object') return null\n const obj = candidate as Record<string, unknown>\n if (typeof obj.threadMappingId !== 'string' || typeof obj.conversationId !== 'string') return null\n return {\n threadMappingId: obj.threadMappingId,\n conversationId: obj.conversationId,\n tenantId: typeof obj.tenantId === 'string' ? obj.tenantId : undefined,\n previousAssignedUserId:\n typeof obj.previousAssignedUserId === 'string' ? obj.previousAssignedUserId : null,\n newAssignedUserId: typeof obj.newAssignedUserId === 'string' ? obj.newAssignedUserId : null,\n }\n}\n\n/**\n * Pulls the reassignment snapshot from a command log entry \u2014 first via the\n * shared `extractUndoPayload` helper, then through the local shape-validator.\n * Always falls back to `null` so the undo handler can no-op safely.\n */\nexport function extractSnapshotFromLog(logEntry: unknown): ReassignConversationUndoSnapshot | null {\n const undo = extractSharedUndoPayload<ReassignConversationUndoSnapshot>(\n (logEntry ?? null) as never,\n )\n if (undo) return extractUndoPayload(undo)\n return extractUndoPayload(logEntry)\n}\n\nregisterCommand(reassignConversationCommand)\n\nexport default reassignConversationCommand\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { registerCommand } from "@open-mercato/shared/lib/commands";
|
|
3
|
+
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
|
|
4
|
+
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
5
|
+
import { CommunicationChannel } from "../data/entities.js";
|
|
6
|
+
import { emitCommunicationChannelsEvent } from "../events.js";
|
|
7
|
+
const setPrimaryChannelSchema = z.object({
|
|
8
|
+
channelId: z.string().uuid(),
|
|
9
|
+
userId: z.string().uuid(),
|
|
10
|
+
scope: z.object({
|
|
11
|
+
tenantId: z.string().uuid(),
|
|
12
|
+
organizationId: z.string().uuid().nullable()
|
|
13
|
+
})
|
|
14
|
+
});
|
|
15
|
+
const COMMUNICATION_CHANNELS_SET_PRIMARY_COMMAND_ID = "communication_channels.channel.set_primary";
|
|
16
|
+
const setPrimaryChannelCommand = {
|
|
17
|
+
id: COMMUNICATION_CHANNELS_SET_PRIMARY_COMMAND_ID,
|
|
18
|
+
// Intentionally NOT undoable. Restoring a prior primary is order-sensitive
|
|
19
|
+
// against the partial unique index `communication_channels_one_primary_per_user_uq`
|
|
20
|
+
// (it would need the same clear-then-set, cross-org, two-phase dance as
|
|
21
|
+
// `execute`), and a stale snapshot could re-primary a channel the user has
|
|
22
|
+
// since disconnected. Sibling lifecycle commands (disconnect/delete) likewise
|
|
23
|
+
// do not re-claim primary on undo. Declared explicitly so the omission reads
|
|
24
|
+
// as a decision, not an oversight.
|
|
25
|
+
isUndoable: false,
|
|
26
|
+
async execute(rawInput, ctx) {
|
|
27
|
+
const input = setPrimaryChannelSchema.parse(rawInput);
|
|
28
|
+
const em = ctx.container.resolve("em").fork();
|
|
29
|
+
const dscope = {
|
|
30
|
+
tenantId: input.scope.tenantId,
|
|
31
|
+
organizationId: input.scope.organizationId ?? null
|
|
32
|
+
};
|
|
33
|
+
const channel = await findOneWithDecryption(
|
|
34
|
+
em,
|
|
35
|
+
CommunicationChannel,
|
|
36
|
+
{
|
|
37
|
+
id: input.channelId,
|
|
38
|
+
tenantId: input.scope.tenantId,
|
|
39
|
+
organizationId: input.scope.organizationId ?? null,
|
|
40
|
+
deletedAt: null
|
|
41
|
+
},
|
|
42
|
+
void 0,
|
|
43
|
+
dscope
|
|
44
|
+
);
|
|
45
|
+
if (!channel) {
|
|
46
|
+
return { status: "not_owner", reason: "channel not found" };
|
|
47
|
+
}
|
|
48
|
+
if (channel.userId !== input.userId) {
|
|
49
|
+
return { status: "not_owner", reason: "channel is not owned by the current user" };
|
|
50
|
+
}
|
|
51
|
+
if (channel.isPrimary) {
|
|
52
|
+
return { status: "noop", reason: "channel is already primary" };
|
|
53
|
+
}
|
|
54
|
+
const previousPrimaries = await findWithDecryption(
|
|
55
|
+
em,
|
|
56
|
+
CommunicationChannel,
|
|
57
|
+
{
|
|
58
|
+
// The one-primary-per-user partial unique index keys on `user_id` only,
|
|
59
|
+
// so it spans ALL organizations for a user. The prior primary must be
|
|
60
|
+
// cleared regardless of which org it lives in — otherwise a multi-org
|
|
61
|
+
// user setting a new primary in org B would collide (23505) with their
|
|
62
|
+
// existing primary in org A. `CommunicationChannel` has no encrypted
|
|
63
|
+
// fields, so the cross-org read needs no per-org decryption scope.
|
|
64
|
+
tenantId: input.scope.tenantId,
|
|
65
|
+
userId: input.userId,
|
|
66
|
+
isPrimary: true,
|
|
67
|
+
deletedAt: null
|
|
68
|
+
},
|
|
69
|
+
void 0,
|
|
70
|
+
dscope
|
|
71
|
+
);
|
|
72
|
+
let previousPrimaryChannelId = null;
|
|
73
|
+
await withAtomicFlush(
|
|
74
|
+
em,
|
|
75
|
+
[
|
|
76
|
+
() => {
|
|
77
|
+
for (const prev of previousPrimaries) {
|
|
78
|
+
previousPrimaryChannelId = prev.id;
|
|
79
|
+
prev.isPrimary = false;
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
() => {
|
|
83
|
+
channel.isPrimary = true;
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
{ transaction: true }
|
|
87
|
+
);
|
|
88
|
+
await emitCommunicationChannelsEvent(
|
|
89
|
+
"communication_channels.channel.primary_changed",
|
|
90
|
+
{
|
|
91
|
+
channelId: channel.id,
|
|
92
|
+
userId: input.userId,
|
|
93
|
+
providerKey: channel.providerKey,
|
|
94
|
+
channelType: channel.channelType,
|
|
95
|
+
previousPrimaryChannelId,
|
|
96
|
+
tenantId: input.scope.tenantId,
|
|
97
|
+
organizationId: input.scope.organizationId ?? null
|
|
98
|
+
},
|
|
99
|
+
{ persistent: true }
|
|
100
|
+
);
|
|
101
|
+
return {
|
|
102
|
+
status: "set",
|
|
103
|
+
channelId: channel.id,
|
|
104
|
+
previousPrimaryChannelId
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
registerCommand(setPrimaryChannelCommand);
|
|
109
|
+
var set_primary_channel_default = setPrimaryChannelCommand;
|
|
110
|
+
export {
|
|
111
|
+
COMMUNICATION_CHANNELS_SET_PRIMARY_COMMAND_ID,
|
|
112
|
+
set_primary_channel_default as default
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=set-primary-channel.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/communication_channels/commands/set-primary-channel.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CommunicationChannel } from '../data/entities'\nimport { emitCommunicationChannelsEvent } from '../events'\n\nconst setPrimaryChannelSchema = z.object({\n channelId: z.string().uuid(),\n userId: z.string().uuid(),\n scope: z.object({\n tenantId: z.string().uuid(),\n organizationId: z.string().uuid().nullable(),\n }),\n})\n\nexport type SetPrimaryChannelInput = z.infer<typeof setPrimaryChannelSchema>\n\nexport type SetPrimaryChannelResult =\n | { status: 'set'; channelId: string; previousPrimaryChannelId: string | null }\n | { status: 'noop'; reason: string }\n | { status: 'not_owner'; reason: string }\n\nexport const COMMUNICATION_CHANNELS_SET_PRIMARY_COMMAND_ID =\n 'communication_channels.channel.set_primary'\n\n/**\n * Mark a per-user channel as primary. Clears the primary flag on every other\n * channel owned by the same user. Enforced as one-primary-per-user by the\n * partial unique index `communication_channels_one_primary_per_user_uq`.\n *\n * Ownership-checked: refuses to set a primary on someone else's channel.\n */\nconst setPrimaryChannelCommand: CommandHandler<\n SetPrimaryChannelInput,\n SetPrimaryChannelResult\n> = {\n id: COMMUNICATION_CHANNELS_SET_PRIMARY_COMMAND_ID,\n // Intentionally NOT undoable. Restoring a prior primary is order-sensitive\n // against the partial unique index `communication_channels_one_primary_per_user_uq`\n // (it would need the same clear-then-set, cross-org, two-phase dance as\n // `execute`), and a stale snapshot could re-primary a channel the user has\n // since disconnected. Sibling lifecycle commands (disconnect/delete) likewise\n // do not re-claim primary on undo. Declared explicitly so the omission reads\n // as a decision, not an oversight.\n isUndoable: false,\n async execute(rawInput, ctx) {\n const input = setPrimaryChannelSchema.parse(rawInput) as SetPrimaryChannelInput\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const dscope = {\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId ?? null,\n }\n\n const channel = await findOneWithDecryption(\n em,\n CommunicationChannel,\n {\n id: input.channelId,\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId ?? null,\n deletedAt: null,\n },\n undefined,\n dscope,\n )\n if (!channel) {\n // Existence masking: a non-existent channel is indistinguishable from one\n // owned by another user \u2014 both surface as 404 (not_owner) at the route,\n // consistent with the other channel-scoped routes (import-history, etc.).\n return { status: 'not_owner', reason: 'channel not found' }\n }\n if (channel.userId !== input.userId) {\n return { status: 'not_owner', reason: 'channel is not owned by the current user' }\n }\n if (channel.isPrimary) {\n return { status: 'noop', reason: 'channel is already primary' }\n }\n\n // The partial unique index `communication_channels_one_primary_per_user_uq`\n // forbids two rows where `is_primary AND user_id = X`. PostgreSQL does NOT\n // defer partial unique checks \u2014 every UPDATE statement is checked against\n // the live partial index. So a SINGLE `em.flush()` containing both\n // `is_primary=false` and `is_primary=true` updates is unsafe: MikroORM\n // does not guarantee SET-false statements execute before SET-true.\n //\n // Fix (review R2-H1 / F2, 2026-05-26): two phases inside one transaction.\n // `withAtomicFlush` runs each phase and flushes between them (the platform\n // helper for exactly this multi-phase mutation \u2014 see packages/core/AGENTS.md\n // \"Entity Update Safety\"). Phase 1 clears + flushes so Postgres observes\n // `is_primary=false` before Phase 2's UPDATE runs; the transaction wraps both\n // for all-or-nothing semantics.\n const previousPrimaries = await findWithDecryption(\n em,\n CommunicationChannel,\n {\n // The one-primary-per-user partial unique index keys on `user_id` only,\n // so it spans ALL organizations for a user. The prior primary must be\n // cleared regardless of which org it lives in \u2014 otherwise a multi-org\n // user setting a new primary in org B would collide (23505) with their\n // existing primary in org A. `CommunicationChannel` has no encrypted\n // fields, so the cross-org read needs no per-org decryption scope.\n tenantId: input.scope.tenantId,\n userId: input.userId,\n isPrimary: true,\n deletedAt: null,\n },\n undefined,\n dscope,\n )\n let previousPrimaryChannelId: string | null = null\n await withAtomicFlush(\n em,\n [\n () => {\n for (const prev of previousPrimaries as CommunicationChannel[]) {\n previousPrimaryChannelId = prev.id\n prev.isPrimary = false\n }\n },\n () => {\n channel.isPrimary = true\n },\n ],\n { transaction: true },\n )\n\n await emitCommunicationChannelsEvent(\n 'communication_channels.channel.primary_changed',\n {\n channelId: channel.id,\n userId: input.userId,\n providerKey: channel.providerKey,\n channelType: channel.channelType,\n previousPrimaryChannelId,\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId ?? null,\n },\n { persistent: true },\n )\n\n return {\n status: 'set',\n channelId: channel.id,\n previousPrimaryChannelId,\n }\n },\n}\n\nregisterCommand(setPrimaryChannelCommand)\n\nexport default setPrimaryChannelCommand\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAGlB,SAAS,uBAAuB;AAChC,SAAS,uBAAuB;AAChC,SAAS,uBAAuB,0BAA0B;AAC1D,SAAS,4BAA4B;AACrC,SAAS,sCAAsC;AAE/C,MAAM,0BAA0B,EAAE,OAAO;AAAA,EACvC,WAAW,EAAE,OAAO,EAAE,KAAK;AAAA,EAC3B,QAAQ,EAAE,OAAO,EAAE,KAAK;AAAA,EACxB,OAAO,EAAE,OAAO;AAAA,IACd,UAAU,EAAE,OAAO,EAAE,KAAK;AAAA,IAC1B,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC7C,CAAC;AACH,CAAC;AASM,MAAM,gDACX;AASF,MAAM,2BAGF;AAAA,EACF,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQJ,YAAY;AAAA,EACZ,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,QAAQ,wBAAwB,MAAM,QAAQ;AACpD,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,SAAS;AAAA,MACb,UAAU,MAAM,MAAM;AAAA,MACtB,gBAAgB,MAAM,MAAM,kBAAkB;AAAA,IAChD;AAEA,UAAM,UAAU,MAAM;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,QACE,IAAI,MAAM;AAAA,QACV,UAAU,MAAM,MAAM;AAAA,QACtB,gBAAgB,MAAM,MAAM,kBAAkB;AAAA,QAC9C,WAAW;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,QAAI,CAAC,SAAS;AAIZ,aAAO,EAAE,QAAQ,aAAa,QAAQ,oBAAoB;AAAA,IAC5D;AACA,QAAI,QAAQ,WAAW,MAAM,QAAQ;AACnC,aAAO,EAAE,QAAQ,aAAa,QAAQ,2CAA2C;AAAA,IACnF;AACA,QAAI,QAAQ,WAAW;AACrB,aAAO,EAAE,QAAQ,QAAQ,QAAQ,6BAA6B;AAAA,IAChE;AAeA,UAAM,oBAAoB,MAAM;AAAA,MAC9B;AAAA,MACA;AAAA,MACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOE,UAAU,MAAM,MAAM;AAAA,QACtB,QAAQ,MAAM;AAAA,QACd,WAAW;AAAA,QACX,WAAW;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,QAAI,2BAA0C;AAC9C,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,QACE,MAAM;AACJ,qBAAW,QAAQ,mBAA6C;AAC9D,uCAA2B,KAAK;AAChC,iBAAK,YAAY;AAAA,UACnB;AAAA,QACF;AAAA,QACA,MAAM;AACJ,kBAAQ,YAAY;AAAA,QACtB;AAAA,MACF;AAAA,MACA,EAAE,aAAa,KAAK;AAAA,IACtB;AAEA,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,QACE,WAAW,QAAQ;AAAA,QACnB,QAAQ,MAAM;AAAA,QACd,aAAa,QAAQ;AAAA,QACrB,aAAa,QAAQ;AAAA,QACrB;AAAA,QACA,UAAU,MAAM,MAAM;AAAA,QACtB,gBAAgB,MAAM,MAAM,kBAAkB;AAAA,MAChD;AAAA,MACA,EAAE,YAAY,KAAK;AAAA,IACrB;AAEA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW,QAAQ;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACF;AAEA,gBAAgB,wBAAwB;AAExC,IAAO,8BAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|