@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,168 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import type { AwilixContainer } from 'awilix'
|
|
3
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
4
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
5
|
+
import { CommunicationChannel } from '../data/entities'
|
|
6
|
+
import { getChannelAdapterRegistry } from '../lib/adapter-registry-singleton'
|
|
7
|
+
import { refreshCredentialsIfNeeded } from '../lib/credential-refresh'
|
|
8
|
+
import { POLLING_ONLY_DEFAULT_INTERVAL_SECONDS } from '../lib/connect-channel'
|
|
9
|
+
import { emitCommunicationChannelsEvent } from '../events'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Spec C § Phase C5 — Tear down a previously-registered push delivery.
|
|
13
|
+
*
|
|
14
|
+
* Called from `disconnect-channel.ts` (per-user disconnect path) so the
|
|
15
|
+
* provider-side `users.watch` subscription doesn't keep firing
|
|
16
|
+
* notifications at a webhook that no longer recognises the channel.
|
|
17
|
+
*
|
|
18
|
+
* Idempotent + best-effort: a missing registration, a 404 from the provider,
|
|
19
|
+
* or any adapter error is logged but never raised. The caller still proceeds
|
|
20
|
+
* with the disconnect (the channel row is cleared regardless of whether the
|
|
21
|
+
* provider-side teardown succeeded).
|
|
22
|
+
*
|
|
23
|
+
* Companion to `commands/push-register.ts` — same lib-style helper shape,
|
|
24
|
+
* not a `registerCommand`-style command (push lifecycle has no undo).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export const pushUnregisterSchema = z.object({
|
|
28
|
+
channelId: z.string().uuid(),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export interface PushUnregisterScope {
|
|
32
|
+
tenantId: string
|
|
33
|
+
organizationId: string
|
|
34
|
+
userId?: string | null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface PushUnregisterResult {
|
|
38
|
+
channelId: string
|
|
39
|
+
status: 'unregistered' | 'noop' | 'failed'
|
|
40
|
+
error?: { code: string; message: string }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type CredentialsServiceLike = {
|
|
44
|
+
resolve: (
|
|
45
|
+
integrationId: string,
|
|
46
|
+
scope: { organizationId: string; tenantId: string; userId?: string | null },
|
|
47
|
+
) => Promise<Record<string, unknown> | null>
|
|
48
|
+
save?: (
|
|
49
|
+
integrationId: string,
|
|
50
|
+
credentials: Record<string, unknown>,
|
|
51
|
+
scope: { organizationId: string; tenantId: string; userId?: string | null },
|
|
52
|
+
) => Promise<void>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function pushUnregister(params: {
|
|
56
|
+
container: AwilixContainer
|
|
57
|
+
scope: PushUnregisterScope
|
|
58
|
+
input: { channelId: string }
|
|
59
|
+
}): Promise<PushUnregisterResult> {
|
|
60
|
+
const input = pushUnregisterSchema.parse(params.input)
|
|
61
|
+
const { container, scope } = params
|
|
62
|
+
|
|
63
|
+
const em = (container.resolve('em') as EntityManager).fork()
|
|
64
|
+
const dscope = { tenantId: scope.tenantId, organizationId: scope.organizationId }
|
|
65
|
+
|
|
66
|
+
const channel = await findOneWithDecryption(
|
|
67
|
+
em,
|
|
68
|
+
CommunicationChannel,
|
|
69
|
+
{
|
|
70
|
+
id: input.channelId,
|
|
71
|
+
tenantId: scope.tenantId,
|
|
72
|
+
organizationId: scope.organizationId,
|
|
73
|
+
},
|
|
74
|
+
undefined,
|
|
75
|
+
dscope,
|
|
76
|
+
)
|
|
77
|
+
if (!channel) {
|
|
78
|
+
return { channelId: input.channelId, status: 'noop' }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const adapter = getChannelAdapterRegistry().get(channel.providerKey)
|
|
82
|
+
if (!adapter || typeof adapter.unregisterPush !== 'function') {
|
|
83
|
+
return { channelId: channel.id, status: 'noop' }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const channelState = (channel.channelState as Record<string, unknown> | null) ?? {}
|
|
87
|
+
const pushStatus = typeof channelState.pushStatus === 'string' ? channelState.pushStatus : null
|
|
88
|
+
if (!pushStatus || pushStatus === 'inactive') {
|
|
89
|
+
return { channelId: channel.id, status: 'noop' }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let credentialsService: CredentialsServiceLike | null = null
|
|
93
|
+
try {
|
|
94
|
+
credentialsService = container.resolve('integrationCredentialsService') as CredentialsServiceLike
|
|
95
|
+
} catch {
|
|
96
|
+
credentialsService = null
|
|
97
|
+
}
|
|
98
|
+
const credentialsScope = {
|
|
99
|
+
tenantId: scope.tenantId,
|
|
100
|
+
organizationId: scope.organizationId,
|
|
101
|
+
userId: channel.userId ?? null,
|
|
102
|
+
}
|
|
103
|
+
let credentials: Record<string, unknown> = {}
|
|
104
|
+
if (channel.credentialsRef && credentialsService) {
|
|
105
|
+
try {
|
|
106
|
+
credentials =
|
|
107
|
+
(await credentialsService.resolve(`channel_${channel.providerKey}`, credentialsScope)) ?? {}
|
|
108
|
+
} catch {
|
|
109
|
+
credentials = {}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const refreshed = await refreshCredentialsIfNeeded(
|
|
114
|
+
{ adapter, channelId: channel.id, credentials, scope: credentialsScope },
|
|
115
|
+
{ credentialsService },
|
|
116
|
+
)
|
|
117
|
+
credentials = refreshed.credentials
|
|
118
|
+
} catch {
|
|
119
|
+
// Refresh failure is non-fatal — unregister tolerates expired tokens
|
|
120
|
+
// because the provider will simply return 401, which the adapter swallows
|
|
121
|
+
// or we ignore below.
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await adapter.unregisterPush({
|
|
126
|
+
channelId: channel.id,
|
|
127
|
+
credentials,
|
|
128
|
+
scope: { tenantId: scope.tenantId, organizationId: scope.organizationId },
|
|
129
|
+
channelState,
|
|
130
|
+
})
|
|
131
|
+
} catch (err) {
|
|
132
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
133
|
+
console.warn(
|
|
134
|
+
`[push-unregister] adapter.unregisterPush failed for channel ${channel.id}: ${message}`,
|
|
135
|
+
)
|
|
136
|
+
return {
|
|
137
|
+
channelId: channel.id,
|
|
138
|
+
status: 'failed',
|
|
139
|
+
error: { code: 'adapter_unregister_failed', message: message.slice(0, 500) },
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Clear push markers from channel state so a subsequent reconnect starts
|
|
144
|
+
// fresh.
|
|
145
|
+
channel.channelState = {
|
|
146
|
+
...channelState,
|
|
147
|
+
pushStatus: 'inactive',
|
|
148
|
+
watchExpirationMs: null,
|
|
149
|
+
}
|
|
150
|
+
// Restore the polling-only default cadence (matching connect) so the channel
|
|
151
|
+
// keeps working until disconnect itself flips `isActive: false`.
|
|
152
|
+
channel.pollIntervalSeconds = POLLING_ONLY_DEFAULT_INTERVAL_SECONDS
|
|
153
|
+
await em.flush()
|
|
154
|
+
|
|
155
|
+
await emitCommunicationChannelsEvent(
|
|
156
|
+
'communication_channels.push.deactivated',
|
|
157
|
+
{
|
|
158
|
+
channelId: channel.id,
|
|
159
|
+
providerKey: channel.providerKey,
|
|
160
|
+
tenantId: scope.tenantId,
|
|
161
|
+
organizationId: scope.organizationId,
|
|
162
|
+
reason: 'unregistered',
|
|
163
|
+
},
|
|
164
|
+
{ persistent: true },
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return { channelId: channel.id, status: 'unregistered' }
|
|
168
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import type { AwilixContainer } from 'awilix'
|
|
3
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
4
|
+
import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
|
|
5
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
6
|
+
import type {
|
|
7
|
+
ProgressService,
|
|
8
|
+
ProgressServiceContext,
|
|
9
|
+
} from '../../progress/lib/progressService'
|
|
10
|
+
import { CommunicationChannel } from '../data/entities'
|
|
11
|
+
import { getChannelAdapterRegistry } from '../lib/adapter-registry-singleton'
|
|
12
|
+
import { COMMUNICATION_CHANNELS_QUEUES, getCommunicationChannelsQueue } from '../lib/queue'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Spec B § Phase B6 — operator-triggered backlog import.
|
|
16
|
+
*
|
|
17
|
+
* Validates the channel + adapter capability, creates a `ProgressJob`, then
|
|
18
|
+
* enqueues a `channel-import-history` job. Returns the job id so the UI can
|
|
19
|
+
* track progress via the existing ProgressTopBar.
|
|
20
|
+
*
|
|
21
|
+
* Concurrency guard: at most one in-flight import per channel — a second call
|
|
22
|
+
* while the first is still running returns a 429 envelope via
|
|
23
|
+
* `createCrudFormError`. The worker itself runs with `concurrency: 1` so a
|
|
24
|
+
* dropped guard is still safe.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export const queueImportHistorySchema = z.object({
|
|
28
|
+
channelId: z.string().uuid(),
|
|
29
|
+
sinceDays: z.number().int().min(1).max(365).default(30),
|
|
30
|
+
contactEmails: z
|
|
31
|
+
.array(z.string().email().max(255))
|
|
32
|
+
.max(200)
|
|
33
|
+
.optional(),
|
|
34
|
+
maxMessages: z.number().int().min(1).max(5000).default(1000),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export type QueueImportHistoryInput = z.infer<typeof queueImportHistorySchema>
|
|
38
|
+
|
|
39
|
+
export interface QueueImportHistoryScope {
|
|
40
|
+
tenantId: string
|
|
41
|
+
organizationId: string
|
|
42
|
+
userId?: string | null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface QueueImportHistoryResult {
|
|
46
|
+
progressJobId: string
|
|
47
|
+
totalCountHint: number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const CHANNEL_IMPORT_HISTORY_JOB_TYPE = 'communication_channels.channel.import_history'
|
|
51
|
+
|
|
52
|
+
export interface ChannelImportHistoryJobPayload {
|
|
53
|
+
progressJobId: string
|
|
54
|
+
channelId: string
|
|
55
|
+
sinceDays: number
|
|
56
|
+
contactEmails?: string[]
|
|
57
|
+
maxMessages: number
|
|
58
|
+
scope: { tenantId: string; organizationId: string }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function queueImportHistory(params: {
|
|
62
|
+
container: AwilixContainer
|
|
63
|
+
scope: QueueImportHistoryScope
|
|
64
|
+
input: QueueImportHistoryInput
|
|
65
|
+
}): Promise<QueueImportHistoryResult> {
|
|
66
|
+
const input = queueImportHistorySchema.parse(params.input)
|
|
67
|
+
const { container, scope } = params
|
|
68
|
+
|
|
69
|
+
const em = (container.resolve('em') as EntityManager).fork()
|
|
70
|
+
const dscope = { tenantId: scope.tenantId, organizationId: scope.organizationId }
|
|
71
|
+
|
|
72
|
+
const channel = await findOneWithDecryption(
|
|
73
|
+
em,
|
|
74
|
+
CommunicationChannel,
|
|
75
|
+
{
|
|
76
|
+
id: input.channelId,
|
|
77
|
+
tenantId: scope.tenantId,
|
|
78
|
+
organizationId: scope.organizationId,
|
|
79
|
+
deletedAt: null,
|
|
80
|
+
},
|
|
81
|
+
undefined,
|
|
82
|
+
dscope,
|
|
83
|
+
)
|
|
84
|
+
if (!channel) {
|
|
85
|
+
throw createCrudFormError(
|
|
86
|
+
'Channel not found',
|
|
87
|
+
{ channelId: 'Channel not found' },
|
|
88
|
+
{ status: 404 },
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
if (!channel.isActive || channel.status !== 'connected') {
|
|
92
|
+
// 409 (not 400): the request is well-formed but the channel is in a
|
|
93
|
+
// conflicting state (requires_reauth / error / disconnected). The UI maps
|
|
94
|
+
// this to the localized "reconnect first" flash; see spec § API Contracts.
|
|
95
|
+
throw createCrudFormError(
|
|
96
|
+
'Channel is not connected',
|
|
97
|
+
{ channelId: 'Channel must be connected to import history' },
|
|
98
|
+
{ status: 409 },
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Adapter must declare `importHistory` — Gmail / IMAP adapters gain it
|
|
103
|
+
// in Spec C. Other providers (chat, SMS) cannot do historical inbox sweeps.
|
|
104
|
+
const adapter = getChannelAdapterRegistry().get(channel.providerKey)
|
|
105
|
+
if (!adapter) {
|
|
106
|
+
throw createCrudFormError(
|
|
107
|
+
'Channel provider is not available',
|
|
108
|
+
{ channelId: `No adapter registered for provider "${channel.providerKey}"` },
|
|
109
|
+
{ status: 400 },
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
if (typeof adapter.importHistory !== 'function') {
|
|
113
|
+
throw createCrudFormError(
|
|
114
|
+
'History import is not supported on this provider',
|
|
115
|
+
{ channelId: `Provider "${channel.providerKey}" does not support history import yet` },
|
|
116
|
+
{ status: 400 },
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const progressService = container.resolve('progressService') as ProgressService
|
|
121
|
+
const progressContext: ProgressServiceContext = {
|
|
122
|
+
tenantId: scope.tenantId,
|
|
123
|
+
organizationId: scope.organizationId,
|
|
124
|
+
userId: scope.userId ?? null,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Concurrency guard (best-effort): refuse if another import is already
|
|
128
|
+
// in-flight for the same channel. We can't query by meta in a portable way
|
|
129
|
+
// without DB-specific JSON operators, so we scan the small active-jobs window.
|
|
130
|
+
// This check and `createJob` below are NOT atomic — two near-simultaneous
|
|
131
|
+
// requests for the same channel can both pass and enqueue. That is acceptable,
|
|
132
|
+
// not corrupting: the import worker runs at concurrency 1 (duplicate jobs run
|
|
133
|
+
// sequentially, never in parallel) and inbound ingest dedups by the
|
|
134
|
+
// `(channel_id, external_message_id)` unique index, so the worst case is
|
|
135
|
+
// wasted re-fetch work, never duplicate rows.
|
|
136
|
+
const active = await progressService.getActiveJobs(progressContext)
|
|
137
|
+
const conflict = active.find((job) => {
|
|
138
|
+
if (job.jobType !== CHANNEL_IMPORT_HISTORY_JOB_TYPE) return false
|
|
139
|
+
const meta = (job.meta ?? {}) as Record<string, unknown>
|
|
140
|
+
return meta.channelId === channel.id
|
|
141
|
+
})
|
|
142
|
+
if (conflict) {
|
|
143
|
+
throw createCrudFormError(
|
|
144
|
+
'An import is already in progress for this channel',
|
|
145
|
+
{ channelId: 'Another history import is already running' },
|
|
146
|
+
{ status: 429 },
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const progressJob = await progressService.createJob(
|
|
151
|
+
{
|
|
152
|
+
jobType: CHANNEL_IMPORT_HISTORY_JOB_TYPE,
|
|
153
|
+
name: `Import history: ${channel.displayName ?? channel.externalIdentifier ?? channel.providerKey}`,
|
|
154
|
+
description: `Import up to ${input.maxMessages} messages from the last ${input.sinceDays} days`,
|
|
155
|
+
totalCount: input.maxMessages,
|
|
156
|
+
cancellable: true,
|
|
157
|
+
meta: {
|
|
158
|
+
channelId: channel.id,
|
|
159
|
+
providerKey: channel.providerKey,
|
|
160
|
+
sinceDays: input.sinceDays,
|
|
161
|
+
contactEmailsCount: input.contactEmails?.length ?? 0,
|
|
162
|
+
maxMessages: input.maxMessages,
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
progressContext,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
const payload: ChannelImportHistoryJobPayload = {
|
|
169
|
+
progressJobId: progressJob.id,
|
|
170
|
+
channelId: channel.id,
|
|
171
|
+
sinceDays: input.sinceDays,
|
|
172
|
+
contactEmails: input.contactEmails,
|
|
173
|
+
maxMessages: input.maxMessages,
|
|
174
|
+
scope: { tenantId: scope.tenantId, organizationId: scope.organizationId },
|
|
175
|
+
}
|
|
176
|
+
const queue = getCommunicationChannelsQueue(COMMUNICATION_CHANNELS_QUEUES.importHistory)
|
|
177
|
+
await queue.enqueue(payload as unknown as Record<string, unknown>)
|
|
178
|
+
|
|
179
|
+
return { progressJobId: progressJob.id, totalCountHint: input.maxMessages }
|
|
180
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
3
|
+
import type { CommandHandler } from '@open-mercato/shared/lib/commands'
|
|
4
|
+
import { registerCommand } from '@open-mercato/shared/lib/commands'
|
|
5
|
+
import { extractUndoPayload as extractSharedUndoPayload } from '@open-mercato/shared/lib/commands/undo'
|
|
6
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
7
|
+
import { ChannelThreadMapping, ExternalConversation } from '../data/entities'
|
|
8
|
+
import { emitCommunicationChannelsEvent } from '../events'
|
|
9
|
+
|
|
10
|
+
const reassignConversationSchema = z.object({
|
|
11
|
+
threadId: z.string().uuid(),
|
|
12
|
+
/** Set to `null` to unassign the conversation. */
|
|
13
|
+
assignedUserId: z.string().uuid().nullable(),
|
|
14
|
+
scope: z.object({
|
|
15
|
+
tenantId: z.string().uuid(),
|
|
16
|
+
organizationId: z.string().uuid().nullable(),
|
|
17
|
+
}),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
export type ReassignConversationInput = z.infer<typeof reassignConversationSchema>
|
|
21
|
+
|
|
22
|
+
export type ReassignConversationResult =
|
|
23
|
+
| {
|
|
24
|
+
status: 'reassigned'
|
|
25
|
+
threadId: string
|
|
26
|
+
previousAssignedUserId: string | null
|
|
27
|
+
nextAssignedUserId: string | null
|
|
28
|
+
conversationId: string
|
|
29
|
+
undo: ReassignConversationUndoSnapshot
|
|
30
|
+
}
|
|
31
|
+
| { status: 'no_channel_link'; reason: string }
|
|
32
|
+
| { status: 'invalid_assignee'; reason: string }
|
|
33
|
+
| { status: 'noop'; reason: string }
|
|
34
|
+
|
|
35
|
+
export interface ReassignConversationUndoSnapshot {
|
|
36
|
+
threadMappingId: string
|
|
37
|
+
conversationId: string
|
|
38
|
+
// Optional for backward compatibility with log entries written before tenant
|
|
39
|
+
// scoping was added to the undo lookup; new snapshots always set it.
|
|
40
|
+
tenantId?: string
|
|
41
|
+
previousAssignedUserId: string | null
|
|
42
|
+
newAssignedUserId: string | null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const COMMUNICATION_CHANNELS_REASSIGN_CONVERSATION_COMMAND_ID =
|
|
46
|
+
'communication_channels.conversation.reassign'
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Reassign the owning user of a channel-linked conversation.
|
|
50
|
+
*
|
|
51
|
+
* Updates both `ChannelThreadMapping.assignedUserId` and the linked
|
|
52
|
+
* `ExternalConversation.assignedUserId` so subscribers (notification handlers,
|
|
53
|
+
* future dashboards) see a consistent owner. No external provider call —
|
|
54
|
+
* reassignment is an internal-routing concern.
|
|
55
|
+
*
|
|
56
|
+
* Idempotent: when the new owner matches the existing one, returns `noop`.
|
|
57
|
+
*
|
|
58
|
+
* The command is undoable: the `before` snapshot captures the previous owner on
|
|
59
|
+
* both rows so undo can restore them atomically.
|
|
60
|
+
*/
|
|
61
|
+
const reassignConversationCommand: CommandHandler<
|
|
62
|
+
ReassignConversationInput,
|
|
63
|
+
ReassignConversationResult
|
|
64
|
+
> = {
|
|
65
|
+
id: COMMUNICATION_CHANNELS_REASSIGN_CONVERSATION_COMMAND_ID,
|
|
66
|
+
// Explicitly undoable (the bus also infers this from `undo` below, but
|
|
67
|
+
// declaring it keeps undoability from silently dropping under a refactor).
|
|
68
|
+
isUndoable: true,
|
|
69
|
+
async execute(rawInput, ctx) {
|
|
70
|
+
const input = reassignConversationSchema.parse(rawInput) as ReassignConversationInput
|
|
71
|
+
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
72
|
+
const dscope = {
|
|
73
|
+
tenantId: input.scope.tenantId,
|
|
74
|
+
organizationId: input.scope.organizationId ?? null,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const mapping = await findOneWithDecryption(
|
|
78
|
+
em,
|
|
79
|
+
ChannelThreadMapping,
|
|
80
|
+
{
|
|
81
|
+
messageThreadId: input.threadId,
|
|
82
|
+
tenantId: input.scope.tenantId,
|
|
83
|
+
organizationId: input.scope.organizationId ?? null,
|
|
84
|
+
},
|
|
85
|
+
// `message_thread_id` is non-unique (only `(external_conversation_id,
|
|
86
|
+
// tenant_id)` is unique). The 1:1 thread↔mapping invariant holds in v1,
|
|
87
|
+
// but order deterministically so a future many-conversations-per-thread
|
|
88
|
+
// merge can never resolve an arbitrary mapping here.
|
|
89
|
+
{ orderBy: { createdAt: 'asc' } },
|
|
90
|
+
dscope,
|
|
91
|
+
)
|
|
92
|
+
if (!mapping) {
|
|
93
|
+
return {
|
|
94
|
+
status: 'no_channel_link',
|
|
95
|
+
reason: `no ChannelThreadMapping for thread ${input.threadId}`,
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const previousAssignedUserId = mapping.assignedUserId ?? null
|
|
100
|
+
if (previousAssignedUserId === input.assignedUserId) {
|
|
101
|
+
return { status: 'noop', reason: 'assigned user unchanged' }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Reject an assignee that is not a live user of this tenant — a UUID-shaped
|
|
105
|
+
// body alone must not create a cross-tenant / dangling owner reference.
|
|
106
|
+
if (input.assignedUserId) {
|
|
107
|
+
// Reference the `auth` user row by string entity name so this command does
|
|
108
|
+
// not import the auth module's entities (module independence); `as never`
|
|
109
|
+
// matches the codebase pattern for cross-module decrypted reads.
|
|
110
|
+
const assignee = await findOneWithDecryption(
|
|
111
|
+
em,
|
|
112
|
+
'User' as never,
|
|
113
|
+
{ id: input.assignedUserId, tenantId: input.scope.tenantId, deletedAt: null } as never,
|
|
114
|
+
undefined,
|
|
115
|
+
dscope,
|
|
116
|
+
)
|
|
117
|
+
if (!assignee) {
|
|
118
|
+
return {
|
|
119
|
+
status: 'invalid_assignee',
|
|
120
|
+
reason: 'assigned user is not a member of this tenant',
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const conversation = await findOneWithDecryption(
|
|
126
|
+
em,
|
|
127
|
+
ExternalConversation,
|
|
128
|
+
{
|
|
129
|
+
id: mapping.externalConversationId,
|
|
130
|
+
tenantId: input.scope.tenantId,
|
|
131
|
+
organizationId: input.scope.organizationId ?? null,
|
|
132
|
+
},
|
|
133
|
+
undefined,
|
|
134
|
+
dscope,
|
|
135
|
+
)
|
|
136
|
+
if (!conversation) {
|
|
137
|
+
return {
|
|
138
|
+
status: 'no_channel_link',
|
|
139
|
+
reason: `no ExternalConversation for thread ${input.threadId}`,
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// `assignedUserId` is an advisory routing pointer, not a DB foreign key
|
|
144
|
+
// (modules don't share ORM relations). The assignee-existence check above
|
|
145
|
+
// and this write are not atomic, so a user deleted in between leaves a
|
|
146
|
+
// harmless dangling pointer — consistent with the module's no-FK design.
|
|
147
|
+
mapping.assignedUserId = input.assignedUserId
|
|
148
|
+
conversation.assignedUserId = input.assignedUserId
|
|
149
|
+
await em.flush()
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
await emitCommunicationChannelsEvent(
|
|
153
|
+
'communication_channels.conversation.reassigned',
|
|
154
|
+
{
|
|
155
|
+
conversationId: conversation.id,
|
|
156
|
+
channelId: mapping.channelId,
|
|
157
|
+
messageThreadId: input.threadId,
|
|
158
|
+
previousAssignedUserId,
|
|
159
|
+
assignedUserId: input.assignedUserId,
|
|
160
|
+
tenantId: input.scope.tenantId,
|
|
161
|
+
organizationId: input.scope.organizationId ?? null,
|
|
162
|
+
},
|
|
163
|
+
{ persistent: true },
|
|
164
|
+
)
|
|
165
|
+
} catch (emitErr) {
|
|
166
|
+
// Best-effort lifecycle/workflow-trigger signal — a bus failure must not
|
|
167
|
+
// abort the reassignment (the rows are already committed above).
|
|
168
|
+
console.warn(
|
|
169
|
+
'[communication_channels:reassign-conversation] reassigned event emit failed:',
|
|
170
|
+
emitErr instanceof Error ? emitErr.message : emitErr,
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const undo: ReassignConversationUndoSnapshot = {
|
|
175
|
+
threadMappingId: mapping.id,
|
|
176
|
+
conversationId: conversation.id,
|
|
177
|
+
tenantId: mapping.tenantId,
|
|
178
|
+
previousAssignedUserId,
|
|
179
|
+
newAssignedUserId: input.assignedUserId,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
status: 'reassigned',
|
|
184
|
+
threadId: input.threadId,
|
|
185
|
+
previousAssignedUserId,
|
|
186
|
+
nextAssignedUserId: input.assignedUserId,
|
|
187
|
+
conversationId: conversation.id,
|
|
188
|
+
undo,
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
// Persist the undo snapshot into the action log. Without this, the command bus
|
|
192
|
+
// mints an undo token (so the UI offers "Undo") but the snapshot returned from
|
|
193
|
+
// execute() is never stored, and undo() would silently no-op.
|
|
194
|
+
async buildLog({ input, result }) {
|
|
195
|
+
if (result.status !== 'reassigned') return null
|
|
196
|
+
return {
|
|
197
|
+
resourceKind: 'communication_channels.channel',
|
|
198
|
+
resourceId: result.conversationId,
|
|
199
|
+
tenantId: result.undo.tenantId ?? input.scope.tenantId,
|
|
200
|
+
organizationId: input.scope.organizationId ?? null,
|
|
201
|
+
payload: { undo: result.undo },
|
|
202
|
+
snapshotBefore: result.undo,
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
async undo({ ctx, logEntry }) {
|
|
206
|
+
const snapshot = extractSnapshotFromLog(logEntry)
|
|
207
|
+
if (!snapshot) return
|
|
208
|
+
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
209
|
+
// Never resolve by bare id (cross-tenant). New snapshots always carry
|
|
210
|
+
// tenantId; refuse the undo if a legacy snapshot lacks it.
|
|
211
|
+
if (!snapshot.tenantId) return
|
|
212
|
+
const dscope = { tenantId: snapshot.tenantId, organizationId: null }
|
|
213
|
+
|
|
214
|
+
const mapping = await findOneWithDecryption(
|
|
215
|
+
em,
|
|
216
|
+
ChannelThreadMapping,
|
|
217
|
+
{ id: snapshot.threadMappingId, tenantId: snapshot.tenantId },
|
|
218
|
+
undefined,
|
|
219
|
+
dscope,
|
|
220
|
+
)
|
|
221
|
+
const conversation = await findOneWithDecryption(
|
|
222
|
+
em,
|
|
223
|
+
ExternalConversation,
|
|
224
|
+
{ id: snapshot.conversationId, tenantId: snapshot.tenantId },
|
|
225
|
+
undefined,
|
|
226
|
+
dscope,
|
|
227
|
+
)
|
|
228
|
+
if (mapping) mapping.assignedUserId = snapshot.previousAssignedUserId
|
|
229
|
+
if (conversation) conversation.assignedUserId = snapshot.previousAssignedUserId
|
|
230
|
+
if (mapping || conversation) await em.flush()
|
|
231
|
+
},
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Read the undo payload defensively — wraps the shared
|
|
236
|
+
* `@open-mercato/shared/lib/commands/undo.ts` helper with a narrow-by-shape
|
|
237
|
+
* validation so callers get a strongly-typed snapshot or `null`.
|
|
238
|
+
*
|
|
239
|
+
* Kept as a separate export for test ergonomics (tests can mock the snapshot
|
|
240
|
+
* shape directly without round-tripping through a CommandLogEntry).
|
|
241
|
+
*/
|
|
242
|
+
export function extractUndoPayload(value: unknown): ReassignConversationUndoSnapshot | null {
|
|
243
|
+
if (!value || typeof value !== 'object') return null
|
|
244
|
+
const candidate = (value as { undo?: unknown }).undo ?? value
|
|
245
|
+
if (!candidate || typeof candidate !== 'object') return null
|
|
246
|
+
const obj = candidate as Record<string, unknown>
|
|
247
|
+
if (typeof obj.threadMappingId !== 'string' || typeof obj.conversationId !== 'string') return null
|
|
248
|
+
return {
|
|
249
|
+
threadMappingId: obj.threadMappingId,
|
|
250
|
+
conversationId: obj.conversationId,
|
|
251
|
+
tenantId: typeof obj.tenantId === 'string' ? obj.tenantId : undefined,
|
|
252
|
+
previousAssignedUserId:
|
|
253
|
+
typeof obj.previousAssignedUserId === 'string' ? obj.previousAssignedUserId : null,
|
|
254
|
+
newAssignedUserId: typeof obj.newAssignedUserId === 'string' ? obj.newAssignedUserId : null,
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Pulls the reassignment snapshot from a command log entry — first via the
|
|
260
|
+
* shared `extractUndoPayload` helper, then through the local shape-validator.
|
|
261
|
+
* Always falls back to `null` so the undo handler can no-op safely.
|
|
262
|
+
*/
|
|
263
|
+
export function extractSnapshotFromLog(logEntry: unknown): ReassignConversationUndoSnapshot | null {
|
|
264
|
+
const undo = extractSharedUndoPayload<ReassignConversationUndoSnapshot>(
|
|
265
|
+
(logEntry ?? null) as never,
|
|
266
|
+
)
|
|
267
|
+
if (undo) return extractUndoPayload(undo)
|
|
268
|
+
return extractUndoPayload(logEntry)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
registerCommand(reassignConversationCommand)
|
|
272
|
+
|
|
273
|
+
export default reassignConversationCommand
|