@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,210 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'
|
|
3
|
+
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
4
|
+
import { CommunicationChannel } from '../data/entities'
|
|
5
|
+
import { COMMUNICATION_CHANNELS_QUEUES, getCommunicationChannelsQueue } from '../lib/queue'
|
|
6
|
+
import type { PollChannelJobPayload } from './poll-channel'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Scheduler tick payload. Fired by the `@open-mercato/scheduler` cron entry
|
|
10
|
+
* registered in the hub's `setup.ts` (`communication_channels:poll-tick` schedule).
|
|
11
|
+
*/
|
|
12
|
+
export type PollTickPayload = {
|
|
13
|
+
scope: {
|
|
14
|
+
tenantId: string
|
|
15
|
+
organizationId: string | null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const POLL_ENUMERATION_CAP = Math.max(
|
|
20
|
+
1,
|
|
21
|
+
Number.parseInt(process.env.COMMUNICATION_CHANNELS_POLL_ENUMERATION_CAP ?? '500', 10) || 500,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
export const metadata: WorkerMeta = {
|
|
25
|
+
queue: COMMUNICATION_CHANNELS_QUEUES.pollTick,
|
|
26
|
+
id: 'communication_channels:poll-tick',
|
|
27
|
+
concurrency: 1, // single-flight per tenant — one tick at a time
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type HandlerContext = JobContext & {
|
|
31
|
+
resolve: <T = unknown>(name: string) => T
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Enumerate channels due for polling and enqueue per-channel jobs.
|
|
36
|
+
*
|
|
37
|
+
* Per email integration spec § Hub Deltas → Delta 6:
|
|
38
|
+
* SELECT id FROM communication_channels
|
|
39
|
+
* WHERE is_active = true
|
|
40
|
+
* AND deleted_at IS NULL
|
|
41
|
+
* AND status = 'connected'
|
|
42
|
+
* AND poll_interval_seconds IS NOT NULL
|
|
43
|
+
* AND (last_polled_at IS NULL
|
|
44
|
+
* OR last_polled_at + poll_interval_seconds * '1 sec' <= NOW())
|
|
45
|
+
* ORDER BY last_polled_at NULLS FIRST
|
|
46
|
+
* LIMIT 500
|
|
47
|
+
*
|
|
48
|
+
* Implementation note: MikroORM's QueryBuilder is the canonical entry point;
|
|
49
|
+
* the raw SQL above is the conceptual query. We use the entity-level filter to
|
|
50
|
+
* keep things portable and let MikroORM compile it.
|
|
51
|
+
*/
|
|
52
|
+
export default async function handle(
|
|
53
|
+
job: QueuedJob<PollTickPayload>,
|
|
54
|
+
ctx: HandlerContext,
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
// The scheduler module (`@open-mercato/scheduler`) spreads the configured
|
|
57
|
+
// `targetPayload` and then adds `tenantId` / `organizationId` at the TOP
|
|
58
|
+
// level of the enqueued payload (see
|
|
59
|
+
// packages/scheduler/.../execute-schedule.worker.ts). Our setup.ts originally
|
|
60
|
+
// stored `{ scope: { tenantId, organizationId } }` under targetPayload, so
|
|
61
|
+
// at runtime the payload looks like:
|
|
62
|
+
// { scope: { tenantId, organizationId }, tenantId, organizationId, _idempotencyKey }
|
|
63
|
+
// Accept either path so the handler is robust to both how operators originally
|
|
64
|
+
// configured the schedule (nested `scope`) and how the scheduler flattens it.
|
|
65
|
+
const raw = (job?.payload ?? {}) as Partial<PollTickPayload> & {
|
|
66
|
+
tenantId?: string | null
|
|
67
|
+
organizationId?: string | null
|
|
68
|
+
}
|
|
69
|
+
const tenantId = raw.scope?.tenantId ?? raw.tenantId ?? null
|
|
70
|
+
const organizationId =
|
|
71
|
+
raw.scope?.organizationId ?? raw.organizationId ?? null
|
|
72
|
+
if (!tenantId) {
|
|
73
|
+
console.warn(
|
|
74
|
+
'[communication_channels:poll-tick] skipping tick — payload has no tenantId',
|
|
75
|
+
{ payload: raw },
|
|
76
|
+
)
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
const scope = { tenantId, organizationId }
|
|
80
|
+
const em = (ctx.resolve('em') as EntityManager).fork()
|
|
81
|
+
|
|
82
|
+
const now = new Date()
|
|
83
|
+
// Find candidate channels — we enumerate two pools:
|
|
84
|
+
// (1) status='connected' channels due for their normal poll cycle.
|
|
85
|
+
// (2) Spec B § Auto-recovery sweep: status='error' channels whose
|
|
86
|
+
// `lastPolledAt` is older than OM_CHANNEL_AUTO_RECOVER_MINUTES
|
|
87
|
+
// (default 30 min). At most one retry per recovery window per
|
|
88
|
+
// channel — when we enqueue a recovery job below we bump that
|
|
89
|
+
// channel's `lastPolledAt` to `now` so it falls back under the
|
|
90
|
+
// cutoff and is NOT re-selected on the immediately-following ticks
|
|
91
|
+
// (poll-channel only advances `lastPolledAt` on a SUCCESSFUL poll,
|
|
92
|
+
// so without this a persistently-failing channel would re-enqueue
|
|
93
|
+
// every tick). On success `poll-channel` flips the status back to
|
|
94
|
+
// 'connected' so the channel rejoins the normal pool.
|
|
95
|
+
//
|
|
96
|
+
// Due-ness for (1) is computed in JS to avoid cross-DB interval
|
|
97
|
+
// arithmetic; the (2) cutoff is a single timestamp compare.
|
|
98
|
+
const connectedCandidates = await findWithDecryption(
|
|
99
|
+
em,
|
|
100
|
+
CommunicationChannel,
|
|
101
|
+
{
|
|
102
|
+
tenantId: scope.tenantId,
|
|
103
|
+
organizationId: scope.organizationId ?? null,
|
|
104
|
+
isActive: true,
|
|
105
|
+
deletedAt: null,
|
|
106
|
+
status: 'connected',
|
|
107
|
+
pollIntervalSeconds: { $ne: null },
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
limit: POLL_ENUMERATION_CAP,
|
|
111
|
+
orderBy: { lastPolledAt: 'asc' },
|
|
112
|
+
},
|
|
113
|
+
scope,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
const recoverMinutesRaw = Number.parseInt(
|
|
117
|
+
process.env.OM_CHANNEL_AUTO_RECOVER_MINUTES ?? '',
|
|
118
|
+
10,
|
|
119
|
+
)
|
|
120
|
+
// `0` is a valid override meaning "recover on the very next tick" (used by
|
|
121
|
+
// TC-CHANNEL-EMAIL-027 and operators who want aggressive recovery). Only a
|
|
122
|
+
// negative or non-numeric value falls back to the 30-minute default.
|
|
123
|
+
const recoverMinutes =
|
|
124
|
+
Number.isFinite(recoverMinutesRaw) && recoverMinutesRaw >= 0 ? recoverMinutesRaw : 30
|
|
125
|
+
const recoverCutoff = new Date(now.getTime() - recoverMinutes * 60 * 1000)
|
|
126
|
+
const errorCandidates = await findWithDecryption(
|
|
127
|
+
em,
|
|
128
|
+
CommunicationChannel,
|
|
129
|
+
{
|
|
130
|
+
tenantId: scope.tenantId,
|
|
131
|
+
organizationId: scope.organizationId ?? null,
|
|
132
|
+
isActive: true,
|
|
133
|
+
deletedAt: null,
|
|
134
|
+
status: 'error',
|
|
135
|
+
// Polling channels only. Push-only channels (Gmail) have
|
|
136
|
+
// `pollIntervalSeconds = null` and are intentionally excluded: poll-channel
|
|
137
|
+
// returns early for push providers, so their recovery is owner-driven
|
|
138
|
+
// (re-register push / reconnect), not this poll-recovery sweep.
|
|
139
|
+
pollIntervalSeconds: { $ne: null },
|
|
140
|
+
// `lastPolledAt` advances only on a SUCCESSFUL poll (poll-channel does
|
|
141
|
+
// not touch it in `handlePollError`). To keep recovery to one retry per
|
|
142
|
+
// window — rather than re-enqueuing a persistently-failing channel every
|
|
143
|
+
// tick — the recovery-enqueue loop below bumps `lastPolledAt` to `now`
|
|
144
|
+
// when it schedules a recovery job, so the channel only re-enters this
|
|
145
|
+
// pool after another `recoverMinutes` have elapsed.
|
|
146
|
+
//
|
|
147
|
+
// A channel that fails its FIRST poll (before any success) still has
|
|
148
|
+
// `lastPolledAt = null`; a bare `$lt` would exclude it forever (SQL
|
|
149
|
+
// `NULL < ts` is NULL, not true), stranding it in `error`. Include the
|
|
150
|
+
// null case so never-polled error channels get their first recovery
|
|
151
|
+
// attempt on the next tick (the enqueue bump below then throttles it).
|
|
152
|
+
$or: [{ lastPolledAt: null }, { lastPolledAt: { $lt: recoverCutoff } }],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
limit: POLL_ENUMERATION_CAP,
|
|
156
|
+
orderBy: { lastPolledAt: 'asc' },
|
|
157
|
+
},
|
|
158
|
+
scope,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
const queue = getCommunicationChannelsQueue(COMMUNICATION_CHANNELS_QUEUES.poll)
|
|
162
|
+
let enqueued = 0
|
|
163
|
+
let recovered = 0
|
|
164
|
+
for (const channel of connectedCandidates as CommunicationChannel[]) {
|
|
165
|
+
const intervalSeconds = channel.pollIntervalSeconds
|
|
166
|
+
if (!intervalSeconds || intervalSeconds <= 0) continue
|
|
167
|
+
if (!isDue(channel.lastPolledAt ?? null, intervalSeconds, now)) continue
|
|
168
|
+
const payload: PollChannelJobPayload = {
|
|
169
|
+
channelId: channel.id,
|
|
170
|
+
scope: {
|
|
171
|
+
tenantId: channel.tenantId,
|
|
172
|
+
organizationId: channel.organizationId ?? scope.organizationId ?? null,
|
|
173
|
+
},
|
|
174
|
+
attempt: 1,
|
|
175
|
+
}
|
|
176
|
+
await queue.enqueue(payload as unknown as Record<string, unknown>)
|
|
177
|
+
enqueued += 1
|
|
178
|
+
}
|
|
179
|
+
// Auto-recovery: at most one retry per recovery window per error-state
|
|
180
|
+
// channel. We bump `lastPolledAt` to `now` as we enqueue so the same channel
|
|
181
|
+
// drops back under `recoverCutoff` and is NOT re-selected on the next ticks
|
|
182
|
+
// (poll-channel leaves `lastPolledAt` untouched on failure, so without this a
|
|
183
|
+
// persistently-failing channel would be re-enqueued every tick).
|
|
184
|
+
for (const channel of errorCandidates as CommunicationChannel[]) {
|
|
185
|
+
const payload: PollChannelJobPayload = {
|
|
186
|
+
channelId: channel.id,
|
|
187
|
+
scope: {
|
|
188
|
+
tenantId: channel.tenantId,
|
|
189
|
+
organizationId: channel.organizationId ?? scope.organizationId ?? null,
|
|
190
|
+
},
|
|
191
|
+
attempt: 1,
|
|
192
|
+
}
|
|
193
|
+
await queue.enqueue(payload as unknown as Record<string, unknown>)
|
|
194
|
+
channel.lastPolledAt = now
|
|
195
|
+
recovered += 1
|
|
196
|
+
}
|
|
197
|
+
if (recovered > 0) await em.flush()
|
|
198
|
+
|
|
199
|
+
if (enqueued > 0 || recovered > 0) {
|
|
200
|
+
console.log(
|
|
201
|
+
`[communication_channels:poll-tick] enqueued ${enqueued} normal + ${recovered} auto-recover poll job(s) for tenant ${scope.tenantId}`,
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function isDue(lastPolledAt: Date | null, intervalSeconds: number, now: Date): boolean {
|
|
207
|
+
if (!lastPolledAt) return true
|
|
208
|
+
const dueAt = new Date(lastPolledAt.getTime() + intervalSeconds * 1000)
|
|
209
|
+
return now >= dueAt
|
|
210
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'
|
|
2
|
+
import type { CommandBus } from '@open-mercato/shared/lib/commands'
|
|
3
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
4
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
5
|
+
import {
|
|
6
|
+
COMMUNICATION_CHANNELS_PROCESS_INBOUND_REACTION_COMMAND_ID,
|
|
7
|
+
type ProcessInboundReactionInput,
|
|
8
|
+
type ProcessInboundReactionResult,
|
|
9
|
+
} from '../commands/process-inbound-reaction'
|
|
10
|
+
import { COMMUNICATION_CHANNELS_QUEUES, getCommunicationChannelsQueue } from '../lib/queue'
|
|
11
|
+
import { classifyOutboundError, computeBackoffMs } from '../lib/error-classification'
|
|
12
|
+
import { CommunicationChannel } from '../data/entities'
|
|
13
|
+
import {
|
|
14
|
+
REACTION_PROCESSOR_MAX_ATTEMPTS,
|
|
15
|
+
type ReactionInboundJob,
|
|
16
|
+
type ReactionOutboundRemoveJob,
|
|
17
|
+
type ReactionOutboundSendJob,
|
|
18
|
+
type ReactionProcessorPayload,
|
|
19
|
+
} from '../lib/reaction-processor-types'
|
|
20
|
+
import { refreshCredentialsIfNeeded } from '../lib/credential-refresh'
|
|
21
|
+
import type { ChannelAdapterRegistry } from '../lib/registry'
|
|
22
|
+
|
|
23
|
+
export type { ReactionProcessorPayload }
|
|
24
|
+
|
|
25
|
+
export const metadata: WorkerMeta = {
|
|
26
|
+
queue: COMMUNICATION_CHANNELS_QUEUES.reactions,
|
|
27
|
+
id: 'communication_channels:reaction-processor',
|
|
28
|
+
concurrency: 10,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type HandlerContext = JobContext & {
|
|
32
|
+
resolve: <T = unknown>(name: string) => T
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Unified reaction worker — handles inbound, outbound-send, and outbound-remove
|
|
37
|
+
* jobs from the `communication-channels-reactions` queue via a discriminated payload.
|
|
38
|
+
*
|
|
39
|
+
* - `inbound` — dispatches the `process_inbound_reaction` command which
|
|
40
|
+
* applies provider semantics and emits `.reaction.added/.removed`.
|
|
41
|
+
* - `outbound_send` — calls `adapter.sendReaction?(...)`. The local
|
|
42
|
+
* `MessageReaction` row was already created by the API/command layer
|
|
43
|
+
* for UX responsiveness; this worker just notifies the provider.
|
|
44
|
+
* - `outbound_remove` — calls `adapter.removeReaction?(...)`. The local
|
|
45
|
+
* row was already deleted by the API/command layer.
|
|
46
|
+
*
|
|
47
|
+
* Retry: transient failures re-enqueue with exponential backoff up to
|
|
48
|
+
* `REACTION_PROCESSOR_MAX_ATTEMPTS = 3`. Permanent failures stop. Reactions
|
|
49
|
+
* are inherently low-stakes (a missed reaction is annoying, not data loss),
|
|
50
|
+
* so we do not surface failures via a dedicated event; provider errors are
|
|
51
|
+
* logged through `integrationLogService` when available.
|
|
52
|
+
*/
|
|
53
|
+
export default async function handle(
|
|
54
|
+
job: QueuedJob<ReactionProcessorPayload>,
|
|
55
|
+
ctx: HandlerContext,
|
|
56
|
+
): Promise<void> {
|
|
57
|
+
switch (job.payload.kind) {
|
|
58
|
+
case 'inbound':
|
|
59
|
+
await handleInbound(job.payload, ctx)
|
|
60
|
+
return
|
|
61
|
+
case 'outbound_send':
|
|
62
|
+
await handleOutboundSend(job.payload, ctx)
|
|
63
|
+
return
|
|
64
|
+
case 'outbound_remove':
|
|
65
|
+
await handleOutboundRemove(job.payload, ctx)
|
|
66
|
+
return
|
|
67
|
+
default: {
|
|
68
|
+
// exhaustiveness check
|
|
69
|
+
const exhaustive: never = job.payload
|
|
70
|
+
throw new Error(`Unknown reaction job kind: ${JSON.stringify(exhaustive)}`)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function handleInbound(payload: ReactionInboundJob, ctx: HandlerContext): Promise<void> {
|
|
76
|
+
const commandBus = ctx.resolve<CommandBus>('commandBus')
|
|
77
|
+
const containerProxy = { resolve: ctx.resolve.bind(ctx) }
|
|
78
|
+
const commandCtx = {
|
|
79
|
+
container: containerProxy as never,
|
|
80
|
+
auth: null,
|
|
81
|
+
organizationScope: null,
|
|
82
|
+
selectedOrganizationId: payload.scope.organizationId ?? null,
|
|
83
|
+
organizationIds: payload.scope.organizationId ? [payload.scope.organizationId] : null,
|
|
84
|
+
}
|
|
85
|
+
const input: ProcessInboundReactionInput = {
|
|
86
|
+
channelId: payload.channelId,
|
|
87
|
+
providerKey: payload.providerKey,
|
|
88
|
+
channelType: payload.channelType,
|
|
89
|
+
scope: payload.scope,
|
|
90
|
+
event: payload.event,
|
|
91
|
+
}
|
|
92
|
+
await commandBus.execute<ProcessInboundReactionInput, ProcessInboundReactionResult>(
|
|
93
|
+
COMMUNICATION_CHANNELS_PROCESS_INBOUND_REACTION_COMMAND_ID,
|
|
94
|
+
{ input, ctx: commandCtx as never },
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function handleOutboundSend(payload: ReactionOutboundSendJob, ctx: HandlerContext): Promise<void> {
|
|
99
|
+
const result = await callAdapterOutbound(payload, ctx, 'send')
|
|
100
|
+
await maybeRetry(result, payload, ctx)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function handleOutboundRemove(
|
|
104
|
+
payload: ReactionOutboundRemoveJob,
|
|
105
|
+
ctx: HandlerContext,
|
|
106
|
+
): Promise<void> {
|
|
107
|
+
const result = await callAdapterOutbound(payload, ctx, 'remove')
|
|
108
|
+
await maybeRetry(result, payload, ctx)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
type AdapterCallResult =
|
|
112
|
+
| { status: 'ok' }
|
|
113
|
+
| { status: 'no_adapter'; message: string }
|
|
114
|
+
| { status: 'channel_inactive'; message: string }
|
|
115
|
+
| { status: 'failed'; transient: boolean; message: string }
|
|
116
|
+
|
|
117
|
+
async function callAdapterOutbound(
|
|
118
|
+
payload: ReactionOutboundSendJob | ReactionOutboundRemoveJob,
|
|
119
|
+
ctx: HandlerContext,
|
|
120
|
+
action: 'send' | 'remove',
|
|
121
|
+
): Promise<AdapterCallResult> {
|
|
122
|
+
const adapterRegistry = ctx.resolve<ChannelAdapterRegistry>('channelAdapterRegistry')
|
|
123
|
+
const adapter = adapterRegistry?.get(payload.providerKey)
|
|
124
|
+
if (!adapter) {
|
|
125
|
+
return { status: 'no_adapter', message: `No adapter for provider '${payload.providerKey}'` }
|
|
126
|
+
}
|
|
127
|
+
const em = (ctx.resolve('em') as EntityManager).fork()
|
|
128
|
+
const channel = await findOneWithDecryption(
|
|
129
|
+
em,
|
|
130
|
+
CommunicationChannel,
|
|
131
|
+
{
|
|
132
|
+
id: payload.channelId,
|
|
133
|
+
tenantId: payload.scope.tenantId,
|
|
134
|
+
organizationId: payload.scope.organizationId ?? null,
|
|
135
|
+
deletedAt: null,
|
|
136
|
+
},
|
|
137
|
+
undefined,
|
|
138
|
+
payload.scope,
|
|
139
|
+
)
|
|
140
|
+
if (!channel) {
|
|
141
|
+
return { status: 'no_adapter', message: `Channel ${payload.channelId} not found` }
|
|
142
|
+
}
|
|
143
|
+
if (!channel.isActive) {
|
|
144
|
+
return { status: 'channel_inactive', message: `Channel ${payload.channelId} is inactive` }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Credentials. Per-user scoping via `channel.userId` (review R2-C1 / N1).
|
|
148
|
+
type CredentialsServiceLike = {
|
|
149
|
+
resolve: (
|
|
150
|
+
integrationId: string,
|
|
151
|
+
scope: { organizationId: string; tenantId: string; userId?: string | null },
|
|
152
|
+
) => Promise<Record<string, unknown> | null>
|
|
153
|
+
save?: (
|
|
154
|
+
integrationId: string,
|
|
155
|
+
credentials: Record<string, unknown>,
|
|
156
|
+
scope: { organizationId: string; tenantId: string; userId?: string | null },
|
|
157
|
+
) => Promise<void>
|
|
158
|
+
}
|
|
159
|
+
let credentials: Record<string, unknown> = {}
|
|
160
|
+
let credentialsService: CredentialsServiceLike | null = null
|
|
161
|
+
try {
|
|
162
|
+
credentialsService = ctx.resolve<CredentialsServiceLike>('integrationCredentialsService')
|
|
163
|
+
} catch {
|
|
164
|
+
credentialsService = null
|
|
165
|
+
}
|
|
166
|
+
const credentialsScope = {
|
|
167
|
+
tenantId: payload.scope.tenantId,
|
|
168
|
+
organizationId: payload.scope.organizationId ?? payload.scope.tenantId,
|
|
169
|
+
userId: channel.userId ?? null,
|
|
170
|
+
}
|
|
171
|
+
if (channel.credentialsRef && credentialsService) {
|
|
172
|
+
try {
|
|
173
|
+
credentials =
|
|
174
|
+
(await credentialsService.resolve(`channel_${channel.providerKey}`, credentialsScope)) ?? {}
|
|
175
|
+
} catch {
|
|
176
|
+
credentials = {}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const refreshed = await refreshCredentialsIfNeeded(
|
|
180
|
+
{
|
|
181
|
+
adapter,
|
|
182
|
+
channelId: channel.id,
|
|
183
|
+
credentials,
|
|
184
|
+
scope: credentialsScope,
|
|
185
|
+
},
|
|
186
|
+
{ credentialsService },
|
|
187
|
+
)
|
|
188
|
+
credentials = refreshed.credentials
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
if (action === 'send') {
|
|
192
|
+
if (typeof adapter.sendReaction !== 'function') {
|
|
193
|
+
return { status: 'no_adapter', message: `Adapter '${adapter.providerKey}' has no sendReaction` }
|
|
194
|
+
}
|
|
195
|
+
const sendPayload = payload as ReactionOutboundSendJob
|
|
196
|
+
await adapter.sendReaction({
|
|
197
|
+
externalMessageId: sendPayload.messageId, // platform message id; the adapter maps to provider id via its own channel-link lookup if needed
|
|
198
|
+
conversationId: sendPayload.conversationId ?? '',
|
|
199
|
+
emoji: sendPayload.emoji,
|
|
200
|
+
credentials,
|
|
201
|
+
scope: {
|
|
202
|
+
tenantId: payload.scope.tenantId,
|
|
203
|
+
organizationId: payload.scope.organizationId ?? payload.scope.tenantId,
|
|
204
|
+
},
|
|
205
|
+
})
|
|
206
|
+
} else {
|
|
207
|
+
if (typeof adapter.removeReaction !== 'function') {
|
|
208
|
+
return { status: 'no_adapter', message: `Adapter '${adapter.providerKey}' has no removeReaction` }
|
|
209
|
+
}
|
|
210
|
+
const removePayload = payload as ReactionOutboundRemoveJob
|
|
211
|
+
await adapter.removeReaction({
|
|
212
|
+
externalMessageId: removePayload.externalReactionId ?? removePayload.messageId,
|
|
213
|
+
conversationId: removePayload.conversationId ?? '',
|
|
214
|
+
emoji: removePayload.emoji,
|
|
215
|
+
credentials,
|
|
216
|
+
scope: {
|
|
217
|
+
tenantId: payload.scope.tenantId,
|
|
218
|
+
organizationId: payload.scope.organizationId ?? payload.scope.tenantId,
|
|
219
|
+
},
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
return { status: 'ok' }
|
|
223
|
+
} catch (err) {
|
|
224
|
+
const classification = classifyOutboundError(err)
|
|
225
|
+
return {
|
|
226
|
+
status: 'failed',
|
|
227
|
+
transient: classification.transient,
|
|
228
|
+
message: classification.message,
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function maybeRetry(
|
|
234
|
+
result: AdapterCallResult,
|
|
235
|
+
payload: ReactionProcessorPayload,
|
|
236
|
+
ctx: HandlerContext,
|
|
237
|
+
): Promise<void> {
|
|
238
|
+
if (result.status === 'ok') return
|
|
239
|
+
if (result.status === 'no_adapter' || result.status === 'channel_inactive') {
|
|
240
|
+
// Permanent — log and stop.
|
|
241
|
+
console.error(
|
|
242
|
+
`[communication_channels:reaction-processor] ${result.status}: ${result.message}`,
|
|
243
|
+
)
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
if (!result.transient) {
|
|
247
|
+
console.error(
|
|
248
|
+
`[communication_channels:reaction-processor] permanent failure: ${result.message}`,
|
|
249
|
+
)
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
const attempt = payload.attempt ?? 1
|
|
253
|
+
if (attempt >= REACTION_PROCESSOR_MAX_ATTEMPTS) {
|
|
254
|
+
console.error(
|
|
255
|
+
`[communication_channels:reaction-processor] giving up after attempt ${attempt}: ${result.message}`,
|
|
256
|
+
)
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
const next: ReactionProcessorPayload = { ...payload, attempt: attempt + 1 }
|
|
260
|
+
const queue = getCommunicationChannelsQueue(COMMUNICATION_CHANNELS_QUEUES.reactions)
|
|
261
|
+
await queue.enqueue(next as unknown as Record<string, unknown>, {
|
|
262
|
+
delayMs: computeBackoffMs(attempt),
|
|
263
|
+
})
|
|
264
|
+
}
|
|
@@ -78,6 +78,24 @@ export const features = [
|
|
|
78
78
|
module: 'customers',
|
|
79
79
|
dependsOn: ['customers.roles.view'],
|
|
80
80
|
},
|
|
81
|
+
// Email integration (2026-05-27)
|
|
82
|
+
{
|
|
83
|
+
id: 'customers.email.compose',
|
|
84
|
+
title: 'Compose / send emails from CRM',
|
|
85
|
+
module: 'customers',
|
|
86
|
+
dependsOn: ['customers.people.view'],
|
|
87
|
+
},
|
|
88
|
+
// Reserved for a future v2 admin-oversight capability. In v1 the email
|
|
89
|
+
// privacy model is strict owner-only with NO admin bypass, so this feature is
|
|
90
|
+
// declared but INERT — granting it does not unlock other users' private emails
|
|
91
|
+
// (the visibility filter and the visibility-change gate ignore it). See
|
|
92
|
+
// .ai/specs/2026-05-27-crm-email-integration.md (v1 strict owner-only).
|
|
93
|
+
{
|
|
94
|
+
id: 'customers.email.view_private',
|
|
95
|
+
title: 'View other users\' private emails (reserved — inert in v1)',
|
|
96
|
+
module: 'customers',
|
|
97
|
+
dependsOn: ['customers.interactions.view'],
|
|
98
|
+
},
|
|
81
99
|
]
|
|
82
100
|
|
|
83
101
|
export default features
|
|
@@ -28,6 +28,7 @@ import { resolveCustomerInteractionFeatureFlags } from '../../lib/interactionFea
|
|
|
28
28
|
import { resolveCustomersRequestContext } from '../../lib/interactionRequestContext'
|
|
29
29
|
import { hydrateCanonicalInteractions } from '../../lib/interactionReadModel'
|
|
30
30
|
import { resolveCanonicalActivityTargetId } from '../../lib/legacyActivityBridge'
|
|
31
|
+
import { buildEmailVisibilityMikroFilter } from '../../lib/visibilityFilter'
|
|
31
32
|
|
|
32
33
|
const listSchema = z.object({
|
|
33
34
|
page: z.coerce.number().min(1).default(1),
|
|
@@ -291,6 +292,18 @@ async function listCanonicalActivities(
|
|
|
291
292
|
where.source = Array.isArray(options.source) ? { $in: options.source } : options.source
|
|
292
293
|
}
|
|
293
294
|
|
|
295
|
+
// Per-user email privacy: exclude other users' private email interactions from
|
|
296
|
+
// the deprecated /activities surface (mirrors the /interactions Layer-1 filter).
|
|
297
|
+
// v1 strict owner-only — no admin bypass (the filter ignores caller features).
|
|
298
|
+
const activitiesViewerUserId = auth.keyId ? null : (auth.sub ?? auth.userId ?? null)
|
|
299
|
+
Object.assign(
|
|
300
|
+
where,
|
|
301
|
+
buildEmailVisibilityMikroFilter({
|
|
302
|
+
currentUserId: activitiesViewerUserId,
|
|
303
|
+
userFeatures: undefined,
|
|
304
|
+
}),
|
|
305
|
+
)
|
|
306
|
+
|
|
294
307
|
const findOptions = {
|
|
295
308
|
orderBy: buildCanonicalOrderBy(query.sortField, query.sortDir ?? 'desc'),
|
|
296
309
|
...(options?.paginate === false
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
} from '../../../lib/interactionCompatibility'
|
|
38
38
|
import { resolveCustomerInteractionFeatureFlags } from '../../../lib/interactionFeatureFlags'
|
|
39
39
|
import { hydrateCanonicalInteractions } from '../../../lib/interactionReadModel'
|
|
40
|
+
import { buildEmailVisibilityMikroFilter } from '../../../lib/visibilityFilter'
|
|
40
41
|
import type { QueryEngine } from '@open-mercato/shared/lib/query/types'
|
|
41
42
|
import type { EntityId } from '@open-mercato/shared/modules/entities'
|
|
42
43
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
@@ -396,6 +397,20 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
396
397
|
organizationId: company.organizationId ?? scope?.selectedId ?? auth.orgId ?? null,
|
|
397
398
|
}
|
|
398
399
|
|
|
400
|
+
// Per-user email privacy (CRM email integration): exclude private email
|
|
401
|
+
// interactions owned by other users from every read path that returns
|
|
402
|
+
// customer_interactions. Non-email rows, shared emails, and legacy
|
|
403
|
+
// null-visibility rows pass through. v1 is strict owner-only: there is NO
|
|
404
|
+
// admin bypass — the filter ignores caller features, and
|
|
405
|
+
// `customers.email.view_private` is reserved (inert) for v2 oversight.
|
|
406
|
+
// Mirrors the people detail route. API-key callers resolve to null
|
|
407
|
+
// (no author match — shared/non-email rows only).
|
|
408
|
+
const viewerUserId = auth.isApiKey ? null : (auth.sub ?? null)
|
|
409
|
+
const emailVisibilityFilter = buildEmailVisibilityMikroFilter({
|
|
410
|
+
currentUserId: viewerUserId,
|
|
411
|
+
userFeatures: undefined,
|
|
412
|
+
})
|
|
413
|
+
|
|
399
414
|
const profile = company.companyProfile
|
|
400
415
|
? await findOneWithDecryption(
|
|
401
416
|
em,
|
|
@@ -480,11 +495,13 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
480
495
|
tenantId: company.tenantId,
|
|
481
496
|
organizationId: company.organizationId,
|
|
482
497
|
deletedAt: null,
|
|
498
|
+
...emailVisibilityFilter,
|
|
483
499
|
}
|
|
484
500
|
: {
|
|
485
501
|
entity: company.id,
|
|
486
502
|
tenantId: company.tenantId,
|
|
487
503
|
organizationId: company.organizationId,
|
|
504
|
+
...emailVisibilityFilter,
|
|
488
505
|
},
|
|
489
506
|
{ orderBy: { scheduledAt: 'asc', createdAt: 'desc' }, limit: 100 },
|
|
490
507
|
companyScope,
|
|
@@ -523,6 +540,7 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
523
540
|
deletedAt: null,
|
|
524
541
|
status: 'planned',
|
|
525
542
|
interactionType: { $ne: 'task' },
|
|
543
|
+
...emailVisibilityFilter,
|
|
526
544
|
},
|
|
527
545
|
{ orderBy: { scheduledAt: 'ASC', createdAt: 'ASC' }, limit: plannedPreviewLimit },
|
|
528
546
|
companyScope,
|
|
@@ -609,7 +627,6 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
609
627
|
if (comment.authorUserId) authorIds.add(comment.authorUserId)
|
|
610
628
|
}
|
|
611
629
|
}
|
|
612
|
-
const viewerUserId = auth.isApiKey ? null : auth.sub ?? null
|
|
613
630
|
if (viewerUserId) authorIds.add(viewerUserId)
|
|
614
631
|
|
|
615
632
|
let userMap = new Map<string, { name: string | null; email: string | null }>()
|
|
@@ -795,12 +812,14 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
795
812
|
tenantId: company.tenantId,
|
|
796
813
|
deletedAt: null,
|
|
797
814
|
interactionType: { $ne: 'task' },
|
|
815
|
+
...emailVisibilityFilter,
|
|
798
816
|
})
|
|
799
817
|
const interactionCount = await em.count(CustomerInteraction, {
|
|
800
818
|
entity: company.id,
|
|
801
819
|
organizationId: company.organizationId,
|
|
802
820
|
tenantId: company.tenantId,
|
|
803
821
|
deletedAt: null,
|
|
822
|
+
...emailVisibilityFilter,
|
|
804
823
|
})
|
|
805
824
|
const todoCount = interactionFlags.unified
|
|
806
825
|
? await em.count(CustomerInteraction, {
|
|
@@ -858,6 +877,7 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
858
877
|
organizationId: company.organizationId,
|
|
859
878
|
tenantId: company.tenantId,
|
|
860
879
|
deletedAt: null,
|
|
880
|
+
...emailVisibilityFilter,
|
|
861
881
|
},
|
|
862
882
|
{
|
|
863
883
|
fields: ['id', 'occurredAt', 'scheduledAt', 'createdAt'],
|