@open-mercato/core 0.6.5-develop.4384.1.ce2ec6eaaa → 0.6.5-develop.4397.1.9a65481757
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 +269 -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 +442 -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,325 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
3
|
+
import { CustomerInteraction } from '../data/entities'
|
|
4
|
+
import { buildEmailVisibilityMikroFilter } from './visibilityFilter'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Read model that turns a Person's email `CustomerInteraction` rows into
|
|
8
|
+
* Gmail-style threads for the CRM Person page.
|
|
9
|
+
*
|
|
10
|
+
* The Person↔email anchor lives only on `CustomerInteraction`
|
|
11
|
+
* (`interactionType='email'`, `externalMessageId` → `MessageChannelLink.id`).
|
|
12
|
+
* The thread grouping key and the rich per-message content (From/To/Cc, body,
|
|
13
|
+
* direction) live in the communication_channels hub. We read the hub entities
|
|
14
|
+
* by their string class names — the same cross-module pattern used by
|
|
15
|
+
* `link-channel-message-handler.ts` — so the customers module never imports the
|
|
16
|
+
* hub's entity classes (root AGENTS.md: no direct ORM relationships between
|
|
17
|
+
* modules).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export type EmailThreadDirection = 'inbound' | 'outbound'
|
|
21
|
+
|
|
22
|
+
export type PersonEmailMessage = {
|
|
23
|
+
id: string
|
|
24
|
+
/** Open Mercato `messages.message` id — used as `parentMessageId` when replying so the reply joins this thread. */
|
|
25
|
+
messageId: string | null
|
|
26
|
+
/** RFC2822 Message-ID of this email — used as `In-Reply-To` on a reply. */
|
|
27
|
+
rfcMessageId: string | null
|
|
28
|
+
/** RFC2822 References chain for this email. */
|
|
29
|
+
references: string[]
|
|
30
|
+
direction: EmailThreadDirection
|
|
31
|
+
fromName: string | null
|
|
32
|
+
fromEmail: string | null
|
|
33
|
+
to: string[]
|
|
34
|
+
cc: string[]
|
|
35
|
+
subject: string | null
|
|
36
|
+
bodyText: string | null
|
|
37
|
+
sentAt: string
|
|
38
|
+
providerKey: string | null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type PersonEmailThread = {
|
|
42
|
+
threadKey: string
|
|
43
|
+
subject: string | null
|
|
44
|
+
preview: string | null
|
|
45
|
+
participants: string[]
|
|
46
|
+
lastMessageAt: string
|
|
47
|
+
messageCount: number
|
|
48
|
+
providerKey: string | null
|
|
49
|
+
lastDirection: EmailThreadDirection
|
|
50
|
+
messages: PersonEmailMessage[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type BuildPersonEmailThreadsOptions = {
|
|
54
|
+
personId: string
|
|
55
|
+
tenantId: string
|
|
56
|
+
organizationId: string | null
|
|
57
|
+
viewerUserId: string | null
|
|
58
|
+
userFeatures: string[] | null | undefined
|
|
59
|
+
maxThreads?: number
|
|
60
|
+
maxMessagesPerThread?: number
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const DEFAULT_MAX_THREADS = 50
|
|
64
|
+
const DEFAULT_MAX_MESSAGES_PER_THREAD = 200
|
|
65
|
+
const PREVIEW_LENGTH = 140
|
|
66
|
+
|
|
67
|
+
type JsonRecord = Record<string, unknown>
|
|
68
|
+
|
|
69
|
+
/** Extracts `{ email, name }[]` from the many shapes a channel address field can take. */
|
|
70
|
+
function extractAddresses(value: unknown): Array<{ email: string; name: string | null }> {
|
|
71
|
+
const out: Array<{ email: string; name: string | null }> = []
|
|
72
|
+
const pushOne = (item: unknown): void => {
|
|
73
|
+
if (typeof item === 'string') {
|
|
74
|
+
const trimmed = item.trim()
|
|
75
|
+
if (trimmed) out.push({ email: trimmed, name: null })
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
if (item && typeof item === 'object') {
|
|
79
|
+
const rec = item as JsonRecord
|
|
80
|
+
const address = typeof rec.address === 'string' ? rec.address.trim() : null
|
|
81
|
+
const name = typeof rec.name === 'string' && rec.name.trim() ? rec.name.trim() : null
|
|
82
|
+
if (address) out.push({ email: address, name })
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (Array.isArray(value)) {
|
|
86
|
+
for (const item of value) pushOne(item)
|
|
87
|
+
} else {
|
|
88
|
+
pushOne(value)
|
|
89
|
+
}
|
|
90
|
+
return out
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function firstString(...values: unknown[]): string | null {
|
|
94
|
+
for (const value of values) {
|
|
95
|
+
if (typeof value === 'string' && value.trim().length > 0) return value
|
|
96
|
+
}
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function toStringArray(...values: unknown[]): string[] {
|
|
101
|
+
for (const value of values) {
|
|
102
|
+
if (Array.isArray(value)) {
|
|
103
|
+
const strings = value.filter((item): item is string => typeof item === 'string' && item.trim().length > 0)
|
|
104
|
+
if (strings.length > 0) return strings
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return []
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function truncate(value: string | null): string | null {
|
|
111
|
+
if (!value) return value
|
|
112
|
+
const collapsed = value.replace(/\s+/g, ' ').trim()
|
|
113
|
+
if (collapsed.length <= PREVIEW_LENGTH) return collapsed
|
|
114
|
+
return `${collapsed.slice(0, PREVIEW_LENGTH - 1)}…`
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Builds the per-Person email-thread read model. Pure data assembly — callers
|
|
119
|
+
* are responsible for auth and scoping (tenantId/organizationId required).
|
|
120
|
+
*/
|
|
121
|
+
export async function buildPersonEmailThreads(
|
|
122
|
+
em: EntityManager,
|
|
123
|
+
opts: BuildPersonEmailThreadsOptions,
|
|
124
|
+
): Promise<PersonEmailThread[]> {
|
|
125
|
+
const {
|
|
126
|
+
personId,
|
|
127
|
+
tenantId,
|
|
128
|
+
organizationId,
|
|
129
|
+
viewerUserId,
|
|
130
|
+
userFeatures,
|
|
131
|
+
maxThreads = DEFAULT_MAX_THREADS,
|
|
132
|
+
maxMessagesPerThread = DEFAULT_MAX_MESSAGES_PER_THREAD,
|
|
133
|
+
} = opts
|
|
134
|
+
|
|
135
|
+
// ── (1) Load the Person's email interactions (the Person↔email anchor) ──
|
|
136
|
+
const interactionWhere: JsonRecord = {
|
|
137
|
+
entity: personId,
|
|
138
|
+
interactionType: 'email',
|
|
139
|
+
deletedAt: null,
|
|
140
|
+
tenantId,
|
|
141
|
+
}
|
|
142
|
+
if (organizationId) interactionWhere.organizationId = organizationId
|
|
143
|
+
|
|
144
|
+
// Per-email visibility (v1: strict owner-only, no admin bypass) — the CRM
|
|
145
|
+
// Person page applies the SAME rule as every other interactions read path via
|
|
146
|
+
// `buildEmailVisibilityMikroFilter`, so the Emails tab and the `/interactions`
|
|
147
|
+
// timeline can never disagree about who sees an email:
|
|
148
|
+
// - `visibility = 'shared'` is visible to every user with CRM access to this
|
|
149
|
+
// Person (lets a teammate pick up a handed-off thread),
|
|
150
|
+
// - `visibility = 'private'` is visible ONLY to its author (the mailbox
|
|
151
|
+
// owner) — never to teammates, not even an admin/superadmin (team
|
|
152
|
+
// oversight is a deliberate v2 follow-up),
|
|
153
|
+
// - legacy/unset rows (`visibility IS NULL`) stay visible so pre-existing
|
|
154
|
+
// CRM history is never silently hidden.
|
|
155
|
+
// Fail-closed: a null viewer (API-key caller) never matches the author arm, so
|
|
156
|
+
// it only ever sees shared/legacy rows — never anyone's private email.
|
|
157
|
+
interactionWhere.$or = buildEmailVisibilityMikroFilter({
|
|
158
|
+
currentUserId: viewerUserId,
|
|
159
|
+
userFeatures,
|
|
160
|
+
}).$or
|
|
161
|
+
|
|
162
|
+
// `customer_interaction.title`/`body` are encrypted at rest, so reads go
|
|
163
|
+
// through `findWithDecryption` even though we only consume non-encrypted
|
|
164
|
+
// columns here — this keeps the encrypted-entity contract intact.
|
|
165
|
+
const dscope = { tenantId, organizationId: organizationId ?? null }
|
|
166
|
+
const interactions = (await findWithDecryption(
|
|
167
|
+
em,
|
|
168
|
+
CustomerInteraction,
|
|
169
|
+
interactionWhere as never,
|
|
170
|
+
{ orderBy: { occurredAt: 'desc', createdAt: 'desc' }, limit: maxThreads * 20 },
|
|
171
|
+
dscope,
|
|
172
|
+
)) as CustomerInteraction[]
|
|
173
|
+
|
|
174
|
+
const linkIdByInteraction = new Map<string, { occurredAt: Date }>()
|
|
175
|
+
const linkIds: string[] = []
|
|
176
|
+
for (const interaction of interactions) {
|
|
177
|
+
const linkId = interaction.externalMessageId
|
|
178
|
+
if (!linkId || linkIdByInteraction.has(linkId)) continue
|
|
179
|
+
linkIdByInteraction.set(linkId, { occurredAt: interaction.occurredAt ?? interaction.createdAt })
|
|
180
|
+
linkIds.push(linkId)
|
|
181
|
+
}
|
|
182
|
+
if (linkIds.length === 0) return []
|
|
183
|
+
|
|
184
|
+
// ── (2) Resolve MessageChannelLink rows (rich content + direction) ──────
|
|
185
|
+
const linkWhere: JsonRecord = { id: { $in: linkIds }, tenantId }
|
|
186
|
+
if (organizationId) linkWhere.organizationId = organizationId
|
|
187
|
+
const links = (await findWithDecryption(
|
|
188
|
+
em,
|
|
189
|
+
'MessageChannelLink' as never,
|
|
190
|
+
linkWhere as never,
|
|
191
|
+
undefined,
|
|
192
|
+
dscope,
|
|
193
|
+
)) as JsonRecord[]
|
|
194
|
+
|
|
195
|
+
// ── (3) Resolve hub Message rows (the authoritative thread grouping) ────
|
|
196
|
+
const messageIds = Array.from(
|
|
197
|
+
new Set(
|
|
198
|
+
links
|
|
199
|
+
.map((link) => (typeof link.messageId === 'string' ? (link.messageId as string) : null))
|
|
200
|
+
.filter((value): value is string => !!value),
|
|
201
|
+
),
|
|
202
|
+
)
|
|
203
|
+
const messageById = new Map<string, JsonRecord>()
|
|
204
|
+
if (messageIds.length > 0) {
|
|
205
|
+
const messageWhere: JsonRecord = { id: { $in: messageIds }, tenantId }
|
|
206
|
+
if (organizationId) messageWhere.organizationId = organizationId
|
|
207
|
+
const messages = (await findWithDecryption(
|
|
208
|
+
em,
|
|
209
|
+
'Message' as never,
|
|
210
|
+
messageWhere as never,
|
|
211
|
+
undefined,
|
|
212
|
+
dscope,
|
|
213
|
+
)) as JsonRecord[]
|
|
214
|
+
for (const message of messages) {
|
|
215
|
+
if (typeof message.id === 'string') messageById.set(message.id, message)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ── (4) Build per-message DTOs grouped by thread ────────────────────────
|
|
220
|
+
const threadsByKey = new Map<string, PersonEmailThread>()
|
|
221
|
+
|
|
222
|
+
for (const link of links) {
|
|
223
|
+
const linkId = typeof link.id === 'string' ? (link.id as string) : null
|
|
224
|
+
if (!linkId) continue
|
|
225
|
+
const direction: EmailThreadDirection = link.direction === 'outbound' ? 'outbound' : 'inbound'
|
|
226
|
+
const providerKey = typeof link.providerKey === 'string' ? (link.providerKey as string) : null
|
|
227
|
+
const payload = (link.channelPayload ?? null) as JsonRecord | null
|
|
228
|
+
const meta = (link.channelMetadata ?? null) as JsonRecord | null
|
|
229
|
+
const messageId = typeof link.messageId === 'string' ? (link.messageId as string) : null
|
|
230
|
+
const message = messageId ? messageById.get(messageId) ?? null : null
|
|
231
|
+
|
|
232
|
+
// Outbound addresses live in channelMetadata; inbound in channelPayload.
|
|
233
|
+
// Prefer the direction-appropriate source, fall back to the other.
|
|
234
|
+
const primary = direction === 'outbound' ? meta : payload
|
|
235
|
+
const secondary = direction === 'outbound' ? payload : meta
|
|
236
|
+
const fromList = extractAddresses(primary?.from ?? secondary?.from)
|
|
237
|
+
const toList = extractAddresses(primary?.to ?? secondary?.to)
|
|
238
|
+
const ccList = extractAddresses(primary?.cc ?? secondary?.cc)
|
|
239
|
+
|
|
240
|
+
const subject = firstString(
|
|
241
|
+
primary?.subject,
|
|
242
|
+
secondary?.subject,
|
|
243
|
+
typeof message?.subject === 'string' ? message.subject : null,
|
|
244
|
+
)
|
|
245
|
+
const bodyText = firstString(
|
|
246
|
+
direction === 'outbound' ? meta?.bodyText : payload?.text,
|
|
247
|
+
direction === 'outbound' ? payload?.text : meta?.bodyText,
|
|
248
|
+
typeof message?.body === 'string' ? message.body : null,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
const sentAtRaw =
|
|
252
|
+
(typeof message?.sentAt === 'string' || message?.sentAt instanceof Date
|
|
253
|
+
? message.sentAt
|
|
254
|
+
: null) ??
|
|
255
|
+
(link.createdAt instanceof Date || typeof link.createdAt === 'string' ? link.createdAt : null) ??
|
|
256
|
+
linkIdByInteraction.get(linkId)?.occurredAt ??
|
|
257
|
+
new Date()
|
|
258
|
+
const sentAt = (sentAtRaw instanceof Date ? sentAtRaw : new Date(sentAtRaw)).toISOString()
|
|
259
|
+
|
|
260
|
+
const threadKey =
|
|
261
|
+
(typeof message?.threadId === 'string' && message.threadId ? message.threadId : null) ??
|
|
262
|
+
(messageId ? `message:${messageId}` : `link:${linkId}`)
|
|
263
|
+
|
|
264
|
+
const dto: PersonEmailMessage = {
|
|
265
|
+
id: linkId,
|
|
266
|
+
messageId,
|
|
267
|
+
rfcMessageId: firstString(primary?.messageId, secondary?.messageId),
|
|
268
|
+
references: toStringArray(primary?.references, secondary?.references),
|
|
269
|
+
direction,
|
|
270
|
+
fromName: fromList[0]?.name ?? null,
|
|
271
|
+
fromEmail: fromList[0]?.email ?? null,
|
|
272
|
+
to: toList.map((a) => a.email),
|
|
273
|
+
cc: ccList.map((a) => a.email),
|
|
274
|
+
subject,
|
|
275
|
+
bodyText,
|
|
276
|
+
sentAt,
|
|
277
|
+
providerKey,
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const existing = threadsByKey.get(threadKey)
|
|
281
|
+
if (existing) {
|
|
282
|
+
existing.messages.push(dto)
|
|
283
|
+
} else {
|
|
284
|
+
threadsByKey.set(threadKey, {
|
|
285
|
+
threadKey,
|
|
286
|
+
subject,
|
|
287
|
+
preview: null,
|
|
288
|
+
participants: [],
|
|
289
|
+
lastMessageAt: sentAt,
|
|
290
|
+
messageCount: 0,
|
|
291
|
+
providerKey,
|
|
292
|
+
lastDirection: direction,
|
|
293
|
+
messages: [dto],
|
|
294
|
+
})
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ── (5) Finalize thread summaries ───────────────────────────────────────
|
|
299
|
+
const threads: PersonEmailThread[] = []
|
|
300
|
+
for (const thread of threadsByKey.values()) {
|
|
301
|
+
thread.messages.sort((a, b) => a.sentAt.localeCompare(b.sentAt))
|
|
302
|
+
if (thread.messages.length > maxMessagesPerThread) {
|
|
303
|
+
thread.messages = thread.messages.slice(thread.messages.length - maxMessagesPerThread)
|
|
304
|
+
}
|
|
305
|
+
const last = thread.messages[thread.messages.length - 1]
|
|
306
|
+
const firstWithSubject = thread.messages.find((m) => m.subject)
|
|
307
|
+
const participantSet = new Set<string>()
|
|
308
|
+
for (const message of thread.messages) {
|
|
309
|
+
// External counterpart = the "from" for inbound, the "to" for outbound.
|
|
310
|
+
if (message.direction === 'inbound' && message.fromEmail) participantSet.add(message.fromEmail)
|
|
311
|
+
if (message.direction === 'outbound') message.to.forEach((email) => participantSet.add(email))
|
|
312
|
+
}
|
|
313
|
+
thread.subject = firstWithSubject?.subject ?? thread.subject
|
|
314
|
+
thread.preview = truncate(last?.bodyText ?? null)
|
|
315
|
+
thread.participants = Array.from(participantSet)
|
|
316
|
+
thread.lastMessageAt = last?.sentAt ?? thread.lastMessageAt
|
|
317
|
+
thread.lastDirection = last?.direction ?? thread.lastDirection
|
|
318
|
+
thread.providerKey = last?.providerKey ?? thread.providerKey
|
|
319
|
+
thread.messageCount = thread.messages.length
|
|
320
|
+
threads.push(thread)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
threads.sort((a, b) => b.lastMessageAt.localeCompare(a.lastMessageAt))
|
|
324
|
+
return threads.slice(0, maxThreads)
|
|
325
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { FilterQuery } from '@mikro-orm/postgresql'
|
|
2
|
+
import { hasFeature } from '@open-mercato/shared/security/features'
|
|
3
|
+
import { CustomerInteraction } from '../data/entities'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The ACL feature that grants admins the right to see private emails authored
|
|
7
|
+
* by other users. Declared in `acl.ts` but granted to NO role in v1 (reserved
|
|
8
|
+
* for the v2 oversight feature — see `callerHasEmailViewPrivate`).
|
|
9
|
+
*/
|
|
10
|
+
export const EMAIL_VIEW_PRIVATE_FEATURE = 'customers.email.view_private'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns true when the caller holds the admin override to see ALL private email
|
|
14
|
+
* interactions. Honours wildcards (`customers.*`, `*`).
|
|
15
|
+
*
|
|
16
|
+
* RESERVED FOR v2 — NOT wired in v1. The v1 model is strict owner-only with no
|
|
17
|
+
* admin bypass: the visibility filters and `canChangeEmailVisibility` ignore
|
|
18
|
+
* caller features, and `customers.email.view_private` is granted to no role.
|
|
19
|
+
* Kept (with {@link EMAIL_VIEW_PRIVATE_FEATURE}) so v2 oversight can opt back in
|
|
20
|
+
* without re-introducing the helper. Do NOT wire this into a read path without
|
|
21
|
+
* an explicit v2 spec.
|
|
22
|
+
*/
|
|
23
|
+
export function callerHasEmailViewPrivate(userFeatures: string[] | null | undefined): boolean {
|
|
24
|
+
if (!Array.isArray(userFeatures) || userFeatures.length === 0) return false
|
|
25
|
+
return hasFeature(userFeatures, EMAIL_VIEW_PRIVATE_FEATURE)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Authorization predicate for CHANGING an email interaction's visibility.
|
|
30
|
+
*
|
|
31
|
+
* Personal mailbox privacy (v1: strict owner-only): ONLY the interaction's
|
|
32
|
+
* author may flip their own email between private/shared — there is no admin
|
|
33
|
+
* bypass. Non-email rows and no-op changes are always allowed. Mirrors the gate
|
|
34
|
+
* in the dedicated `PATCH .../visibility` route so the generic interaction-update
|
|
35
|
+
* path cannot bypass the privacy control. `userFeatures` is reserved for v2.
|
|
36
|
+
*/
|
|
37
|
+
export function canChangeEmailVisibility(opts: {
|
|
38
|
+
interactionType: string
|
|
39
|
+
currentVisibility: string | null | undefined
|
|
40
|
+
nextVisibility: string | null | undefined
|
|
41
|
+
authorUserId: string | null | undefined
|
|
42
|
+
actorUserId: string | null | undefined
|
|
43
|
+
userFeatures: string[] | null | undefined
|
|
44
|
+
}): boolean {
|
|
45
|
+
if (opts.interactionType !== 'email') return true
|
|
46
|
+
if ((opts.nextVisibility ?? null) === (opts.currentVisibility ?? null)) return true
|
|
47
|
+
return Boolean(opts.actorUserId) && opts.authorUserId === opts.actorUserId
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ApplyEmailVisibilityFilterOptions {
|
|
51
|
+
currentUserId: string | null
|
|
52
|
+
userFeatures: string[] | null | undefined
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Adds a `WHERE` predicate to a kysely query so that:
|
|
57
|
+
* - Non-email interactions (calls, meetings, tasks) pass through unchanged.
|
|
58
|
+
* - Email interactions with `visibility = 'shared'` are visible to all.
|
|
59
|
+
* - Email interactions with `visibility = 'private'` are visible ONLY to the
|
|
60
|
+
* `authorUserId` (channel owner).
|
|
61
|
+
*
|
|
62
|
+
* Personal mailbox privacy (v1: strict owner-only) — there is NO admin bypass:
|
|
63
|
+
* a private email is hidden from everyone except its author, including
|
|
64
|
+
* admins/superadmins. `opts.userFeatures` is retained for signature stability
|
|
65
|
+
* and reserved for the v2 admin-oversight feature.
|
|
66
|
+
*
|
|
67
|
+
* The function expects a kysely-style builder whose `.where()` accepts an
|
|
68
|
+
* expression-builder callback. Returns the same builder for chaining.
|
|
69
|
+
*/
|
|
70
|
+
export function applyEmailVisibilityFilter<T extends { where: (...args: any[]) => T }>(
|
|
71
|
+
query: T,
|
|
72
|
+
opts: ApplyEmailVisibilityFilterOptions,
|
|
73
|
+
): T {
|
|
74
|
+
const currentUserId = opts.currentUserId
|
|
75
|
+
// A row is hidden ONLY when it is an email explicitly marked `private` and the
|
|
76
|
+
// caller is not its author. Everything else stays visible, including:
|
|
77
|
+
// - non-email interactions (calls, meetings, tasks),
|
|
78
|
+
// - emails marked `shared`,
|
|
79
|
+
// - legacy/unset rows where `visibility IS NULL` (e.g. email-log entries
|
|
80
|
+
// created before per-email visibility shipped) — these must remain
|
|
81
|
+
// visible to avoid silently hiding pre-existing CRM history.
|
|
82
|
+
return query.where((eb: any) =>
|
|
83
|
+
eb.or([
|
|
84
|
+
eb('interaction_type', '!=', 'email'),
|
|
85
|
+
eb('visibility', 'is', null),
|
|
86
|
+
eb('visibility', '!=', 'private'),
|
|
87
|
+
currentUserId
|
|
88
|
+
? eb('author_user_id', '=', currentUserId)
|
|
89
|
+
: eb.val(false),
|
|
90
|
+
]),
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
type RbacServiceLike = {
|
|
95
|
+
getGrantedFeatures?: (
|
|
96
|
+
userId: string,
|
|
97
|
+
scope: { tenantId: string | null; organizationId: string | null },
|
|
98
|
+
) => Promise<string[] | undefined>
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Resolve the caller's granted features (wildcard-aware downstream) so a v2
|
|
103
|
+
* visibility filter could honour the `customers.email.view_private` admin
|
|
104
|
+
* override. Returns `undefined` when there is no user or the RBAC service is
|
|
105
|
+
* unavailable — callers MUST treat `undefined` as "no bypass" (fail closed).
|
|
106
|
+
*
|
|
107
|
+
* RESERVED FOR v2 — NOT called by any v1 read path. v1 is strict owner-only, so
|
|
108
|
+
* the read routes pass `userFeatures: undefined` to the filters rather than
|
|
109
|
+
* resolving features here (which would be a wasted RBAC round-trip). Re-wire
|
|
110
|
+
* only under an explicit v2 oversight spec.
|
|
111
|
+
*/
|
|
112
|
+
export async function resolveCallerEmailFeatures(
|
|
113
|
+
container: { resolve: (name: string) => unknown },
|
|
114
|
+
userId: string | null,
|
|
115
|
+
tenantId: string | null,
|
|
116
|
+
organizationId: string | null,
|
|
117
|
+
): Promise<string[] | undefined> {
|
|
118
|
+
if (!userId) return undefined
|
|
119
|
+
try {
|
|
120
|
+
const rbac = container.resolve('rbacService') as RbacServiceLike | undefined
|
|
121
|
+
if (!rbac?.getGrantedFeatures) return undefined
|
|
122
|
+
return await rbac.getGrantedFeatures(userId, { tenantId, organizationId })
|
|
123
|
+
} catch {
|
|
124
|
+
return undefined
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* MikroORM equivalent of {@link applyEmailVisibilityFilter}. Returns a
|
|
130
|
+
* `FilterQuery` fragment to merge (implicit AND) into a `CustomerInteraction`
|
|
131
|
+
* where-clause so private email rows are excluded for non-owner, non-admin
|
|
132
|
+
* callers on MikroORM read paths (`findWithDecryption`/`em.find`/`em.count`).
|
|
133
|
+
*
|
|
134
|
+
* Mirrors the kysely predicate exactly, including the legacy `visibility IS NULL`
|
|
135
|
+
* passthrough so pre-existing CRM history is never hidden. Personal mailbox
|
|
136
|
+
* privacy (v1: strict owner-only): no admin bypass — a private email is hidden
|
|
137
|
+
* from everyone except its author. `opts.userFeatures` is reserved for v2.
|
|
138
|
+
*/
|
|
139
|
+
export type EmailVisibilityMikroFilter = { $or?: FilterQuery<CustomerInteraction>[] }
|
|
140
|
+
|
|
141
|
+
export function buildEmailVisibilityMikroFilter(
|
|
142
|
+
opts: ApplyEmailVisibilityFilterOptions,
|
|
143
|
+
): EmailVisibilityMikroFilter {
|
|
144
|
+
return {
|
|
145
|
+
$or: [
|
|
146
|
+
{ interactionType: { $ne: 'email' } },
|
|
147
|
+
{ visibility: null },
|
|
148
|
+
{ visibility: { $ne: 'private' } },
|
|
149
|
+
...(opts.currentUserId ? [{ authorUserId: opts.currentUserId }] : []),
|
|
150
|
+
],
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -3703,6 +3703,23 @@
|
|
|
3703
3703
|
"enumItems": [],
|
|
3704
3704
|
"mappedType": "text"
|
|
3705
3705
|
},
|
|
3706
|
+
"channel_provider_key": {
|
|
3707
|
+
"name": "channel_provider_key",
|
|
3708
|
+
"type": "text",
|
|
3709
|
+
"unsigned": false,
|
|
3710
|
+
"autoincrement": false,
|
|
3711
|
+
"primary": false,
|
|
3712
|
+
"nullable": true,
|
|
3713
|
+
"unique": false,
|
|
3714
|
+
"length": null,
|
|
3715
|
+
"precision": null,
|
|
3716
|
+
"scale": null,
|
|
3717
|
+
"default": null,
|
|
3718
|
+
"comment": null,
|
|
3719
|
+
"collation": null,
|
|
3720
|
+
"enumItems": [],
|
|
3721
|
+
"mappedType": "text"
|
|
3722
|
+
},
|
|
3706
3723
|
"created_at": {
|
|
3707
3724
|
"name": "created_at",
|
|
3708
3725
|
"type": "timestamptz(6)",
|
|
@@ -3788,6 +3805,23 @@
|
|
|
3788
3805
|
"enumItems": [],
|
|
3789
3806
|
"mappedType": "uuid"
|
|
3790
3807
|
},
|
|
3808
|
+
"external_message_id": {
|
|
3809
|
+
"name": "external_message_id",
|
|
3810
|
+
"type": "uuid",
|
|
3811
|
+
"unsigned": false,
|
|
3812
|
+
"autoincrement": false,
|
|
3813
|
+
"primary": false,
|
|
3814
|
+
"nullable": true,
|
|
3815
|
+
"unique": false,
|
|
3816
|
+
"length": null,
|
|
3817
|
+
"precision": null,
|
|
3818
|
+
"scale": null,
|
|
3819
|
+
"default": null,
|
|
3820
|
+
"comment": null,
|
|
3821
|
+
"collation": null,
|
|
3822
|
+
"enumItems": [],
|
|
3823
|
+
"mappedType": "uuid"
|
|
3824
|
+
},
|
|
3791
3825
|
"guest_permissions": {
|
|
3792
3826
|
"name": "guest_permissions",
|
|
3793
3827
|
"type": "jsonb",
|
|
@@ -4194,6 +4228,33 @@
|
|
|
4194
4228
|
"keyName": "customer_interactions_type_idx",
|
|
4195
4229
|
"primary": false,
|
|
4196
4230
|
"unique": false
|
|
4231
|
+
},
|
|
4232
|
+
{
|
|
4233
|
+
"columnNames": [],
|
|
4234
|
+
"composite": false,
|
|
4235
|
+
"constraint": false,
|
|
4236
|
+
"keyName": "customer_interactions_external_msg_idx",
|
|
4237
|
+
"primary": false,
|
|
4238
|
+
"unique": false,
|
|
4239
|
+
"expression": "create index \"customer_interactions_external_msg_idx\" on \"customer_interactions\" (\"external_message_id\") where \"external_message_id\" is not null"
|
|
4240
|
+
},
|
|
4241
|
+
{
|
|
4242
|
+
"columnNames": [],
|
|
4243
|
+
"composite": false,
|
|
4244
|
+
"constraint": false,
|
|
4245
|
+
"keyName": "customer_interactions_email_dedupe_uq",
|
|
4246
|
+
"primary": false,
|
|
4247
|
+
"unique": false,
|
|
4248
|
+
"expression": "create unique index \"customer_interactions_email_dedupe_uq\" on \"customer_interactions\" (\"entity_id\", \"external_message_id\") where \"external_message_id\" is not null and \"deleted_at\" is null"
|
|
4249
|
+
},
|
|
4250
|
+
{
|
|
4251
|
+
"columnNames": [],
|
|
4252
|
+
"composite": false,
|
|
4253
|
+
"constraint": false,
|
|
4254
|
+
"keyName": "customer_interactions_email_visibility_idx",
|
|
4255
|
+
"primary": false,
|
|
4256
|
+
"unique": false,
|
|
4257
|
+
"expression": "create index \"customer_interactions_email_visibility_idx\" on \"customer_interactions\" (\"entity_id\", \"interaction_type\", \"visibility\", \"author_user_id\") where \"interaction_type\" = 'email' and \"deleted_at\" is null"
|
|
4197
4258
|
}
|
|
4198
4259
|
],
|
|
4199
4260
|
"checks": [],
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260527012240_customers extends Migration {
|
|
4
|
+
|
|
5
|
+
override up(): void | Promise<void> {
|
|
6
|
+
this.addSql(`alter table "customer_interactions" add "external_message_id" uuid null, add "channel_provider_key" text null;`);
|
|
7
|
+
// Backfill: pre-existing email-log interactions predate per-email visibility
|
|
8
|
+
// and have null visibility. Mark them 'shared' so they keep their prior
|
|
9
|
+
// visible-to-all behavior once the visibility filter is enabled.
|
|
10
|
+
this.addSql(`update "customer_interactions" set "visibility" = 'shared' where "interaction_type" = 'email' and "visibility" is null and "external_message_id" is null;`);
|
|
11
|
+
this.addSql(`create index "customer_interactions_external_msg_idx" on "customer_interactions" ("external_message_id") where "external_message_id" is not null;`);
|
|
12
|
+
this.addSql(`create unique index "customer_interactions_email_dedupe_uq" on "customer_interactions" ("entity_id", "external_message_id") where "external_message_id" is not null and "deleted_at" is null;`);
|
|
13
|
+
this.addSql(`create index "customer_interactions_email_visibility_idx" on "customer_interactions" ("entity_id", "interaction_type", "visibility", "author_user_id") where "interaction_type" = 'email' and "deleted_at" is null;`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
override down(): void | Promise<void> {
|
|
17
|
+
this.addSql(`drop index if exists "customer_interactions_email_visibility_idx";`);
|
|
18
|
+
this.addSql(`drop index if exists "customer_interactions_email_dedupe_uq";`);
|
|
19
|
+
this.addSql(`drop index if exists "customer_interactions_external_msg_idx";`);
|
|
20
|
+
this.addSql(`alter table "customer_interactions" drop column "external_message_id", drop column "channel_provider_key";`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent subscriber for `communication_channels.message.received`.
|
|
3
|
+
*
|
|
4
|
+
* When an inbound email arrives, resolves People by address from the linked
|
|
5
|
+
* MessageChannelLink and creates CustomerInteraction rows (one per match).
|
|
6
|
+
* Falls back to threading-inheritance (In-Reply-To chain) when no direct
|
|
7
|
+
* address match is found.
|
|
8
|
+
*
|
|
9
|
+
* Logic lives in `../lib/link-channel-message-handler.ts`; both this file
|
|
10
|
+
* and `link-channel-message-sent.ts` delegate there so the two subscriber
|
|
11
|
+
* registrations can share a single implementation.
|
|
12
|
+
*/
|
|
13
|
+
import linkChannelMessageHandler from '../lib/link-channel-message-handler'
|
|
14
|
+
|
|
15
|
+
export const metadata = {
|
|
16
|
+
event: 'communication_channels.message.received',
|
|
17
|
+
persistent: true,
|
|
18
|
+
id: 'customers:link-channel-message-received',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default linkChannelMessageHandler
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent subscriber for `communication_channels.message.sent`.
|
|
3
|
+
*
|
|
4
|
+
* When an outbound email is delivered, resolves People by address (or by the
|
|
5
|
+
* `crmPersonId` hint written by the compose route) and creates CustomerInteraction
|
|
6
|
+
* rows. Reads `crmVisibility` from `channelMetadata` to set the per-row
|
|
7
|
+
* visibility ('private' | 'shared').
|
|
8
|
+
*
|
|
9
|
+
* Logic lives in `../lib/link-channel-message-handler.ts`; both this file
|
|
10
|
+
* and `link-channel-message-received.ts` delegate there so the two subscriber
|
|
11
|
+
* registrations can share a single implementation.
|
|
12
|
+
*/
|
|
13
|
+
import linkChannelMessageHandler from '../lib/link-channel-message-handler'
|
|
14
|
+
|
|
15
|
+
export const metadata = {
|
|
16
|
+
event: 'communication_channels.message.sent',
|
|
17
|
+
persistent: true,
|
|
18
|
+
id: 'customers:link-channel-message-sent',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default linkChannelMessageHandler
|
|
@@ -132,6 +132,15 @@ For platform connectors with multiple integrations (e.g., MedusaJS):
|
|
|
132
132
|
2. If `bundleId` is set, fallback to bundle's credentials
|
|
133
133
|
3. Return `null` if neither exists
|
|
134
134
|
|
|
135
|
+
## Per-User Credential Scoping
|
|
136
|
+
|
|
137
|
+
`IntegrationScope` carries an optional `userId?: string | null` (added 2026-05-26 for per-user email channels). Every `createCredentialsService` method scopes by it:
|
|
138
|
+
|
|
139
|
+
- **Omit `scope.userId`** (or pass `null`) for tenant-wide credentials (shared API keys, e.g. Stripe/Akeneo) — the filter pins `user_id IS NULL`, the historical behaviour.
|
|
140
|
+
- **Pass `scope.userId`** for per-user credentials (Gmail/IMAP mailboxes) — reads and writes land on that user's own row.
|
|
141
|
+
|
|
142
|
+
Uniqueness across `(integration_id, organization_id, tenant_id, user_id)` is enforced by the partial unique index `integration_credentials_user_lookup_idx` (`WHERE user_id IS NOT NULL AND deleted_at IS NULL`). **Callers MUST thread the correct `userId` on every per-user read AND write** — a tenant-wide scope can never read a user-scoped row and vice versa, so a missing `userId` silently resolves the wrong (or no) credentials.
|
|
143
|
+
|
|
135
144
|
## Events
|
|
136
145
|
|
|
137
146
|
| Event ID | Emitted When |
|