@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,179 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
4
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
7
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
8
|
+
import type { CommandBus } from '@open-mercato/shared/lib/commands'
|
|
9
|
+
import {
|
|
10
|
+
validateCrudMutationGuard,
|
|
11
|
+
runCrudMutationGuardAfterSuccess,
|
|
12
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
13
|
+
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
14
|
+
import { CustomerInteraction } from '../../../../data/entities'
|
|
15
|
+
import type { InteractionUpdateInput } from '../../../../data/validators'
|
|
16
|
+
import { resolveAuthActorId } from '../../../../lib/interactionRequestContext'
|
|
17
|
+
import { emitCustomersEvent } from '../../../../events'
|
|
18
|
+
|
|
19
|
+
export const metadata = {
|
|
20
|
+
path: '/customers/interactions/[id]/visibility',
|
|
21
|
+
PATCH: {
|
|
22
|
+
requireAuth: true,
|
|
23
|
+
requireFeatures: ['customers.email.compose'],
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const bodySchema = z.object({ visibility: z.enum(['private', 'shared']) }).strict()
|
|
28
|
+
|
|
29
|
+
type RouteContext = { params: Promise<{ id: string }> | { id: string } }
|
|
30
|
+
|
|
31
|
+
export async function PATCH(req: Request, context: RouteContext): Promise<Response> {
|
|
32
|
+
const { id } = await context.params
|
|
33
|
+
if (!z.string().uuid().safeParse(id).success) {
|
|
34
|
+
return NextResponse.json({ error: 'Invalid interaction id' }, { status: 400 })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const auth = await getAuthFromRequest(req)
|
|
38
|
+
if (!auth?.sub || !auth?.tenantId) {
|
|
39
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let body: z.infer<typeof bodySchema>
|
|
43
|
+
try {
|
|
44
|
+
body = bodySchema.parse(await readJsonSafe(req, null))
|
|
45
|
+
} catch (err) {
|
|
46
|
+
return NextResponse.json(
|
|
47
|
+
{ error: err instanceof Error ? err.message : 'Invalid request body' },
|
|
48
|
+
{ status: 422 },
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const container = await createRequestContainer()
|
|
53
|
+
const em = (container.resolve('em') as EntityManager).fork()
|
|
54
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
55
|
+
const organizationId = scope?.selectedId ?? (auth as { orgId?: string | null }).orgId ?? null
|
|
56
|
+
const dscope = { tenantId: auth.tenantId as string, organizationId }
|
|
57
|
+
const userId = resolveAuthActorId(auth)
|
|
58
|
+
|
|
59
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
60
|
+
tenantId: auth.tenantId,
|
|
61
|
+
organizationId,
|
|
62
|
+
userId,
|
|
63
|
+
resourceKind: 'customers.interaction',
|
|
64
|
+
resourceId: id,
|
|
65
|
+
operation: 'custom',
|
|
66
|
+
requestMethod: req.method,
|
|
67
|
+
requestHeaders: req.headers,
|
|
68
|
+
})
|
|
69
|
+
if (guardResult && !guardResult.ok) {
|
|
70
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const interaction = (await findOneWithDecryption(
|
|
74
|
+
em,
|
|
75
|
+
CustomerInteraction,
|
|
76
|
+
{
|
|
77
|
+
id,
|
|
78
|
+
tenantId: auth.tenantId,
|
|
79
|
+
organizationId,
|
|
80
|
+
deletedAt: null,
|
|
81
|
+
interactionType: 'email',
|
|
82
|
+
} as any,
|
|
83
|
+
undefined,
|
|
84
|
+
dscope,
|
|
85
|
+
)) as { id: string; authorUserId?: string | null; visibility?: string | null } | null
|
|
86
|
+
|
|
87
|
+
if (!interaction) {
|
|
88
|
+
return NextResponse.json({ error: 'Email not found' }, { status: 404 })
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Personal mailbox privacy (v1: strict owner-only): ONLY the author may flip
|
|
92
|
+
// their own email's visibility — no admin bypass. Return 404 (not 403) for
|
|
93
|
+
// everyone else so we don't leak the row's existence — this also covers
|
|
94
|
+
// non-authors who cannot see a private email in the first place.
|
|
95
|
+
const isAuthor = !!interaction.authorUserId && interaction.authorUserId === auth.sub
|
|
96
|
+
if (!isAuthor) {
|
|
97
|
+
return NextResponse.json({ error: 'Email not found' }, { status: 404 })
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// No-op: visibility is already at the requested value.
|
|
101
|
+
if (interaction.visibility === body.visibility) {
|
|
102
|
+
return NextResponse.json({ ok: true, changed: false })
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const previousVisibility = (interaction.visibility ?? 'private') as 'private' | 'shared'
|
|
106
|
+
|
|
107
|
+
// Route the write through the interactions update command so the change runs
|
|
108
|
+
// the full mutation pipeline — query-index refresh, audit log and undo —
|
|
109
|
+
// instead of a raw em.flush() that would leave the indexed `entity_indexes`
|
|
110
|
+
// doc stale. Authorization (author-only, v1) was already enforced above; the
|
|
111
|
+
// command only owns persistence and side effects.
|
|
112
|
+
const commandBus = container.resolve('commandBus') as CommandBus
|
|
113
|
+
await commandBus.execute<InteractionUpdateInput, { interactionId: string }>(
|
|
114
|
+
'customers.interactions.update',
|
|
115
|
+
{
|
|
116
|
+
input: { id, visibility: body.visibility },
|
|
117
|
+
ctx: {
|
|
118
|
+
container,
|
|
119
|
+
auth: auth as never,
|
|
120
|
+
organizationScope: null,
|
|
121
|
+
selectedOrganizationId: organizationId,
|
|
122
|
+
organizationIds: organizationId ? [organizationId] : null,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
128
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
129
|
+
tenantId: auth.tenantId,
|
|
130
|
+
organizationId,
|
|
131
|
+
userId,
|
|
132
|
+
resourceKind: 'customers.interaction',
|
|
133
|
+
resourceId: id,
|
|
134
|
+
operation: 'custom',
|
|
135
|
+
requestMethod: req.method,
|
|
136
|
+
requestHeaders: req.headers,
|
|
137
|
+
metadata: guardResult.metadata ?? null,
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Emit audit event best-effort — failure must NOT roll back the DB flush.
|
|
142
|
+
try {
|
|
143
|
+
await emitCustomersEvent('customers.email.visibility_changed', {
|
|
144
|
+
interactionId: interaction.id,
|
|
145
|
+
previousVisibility,
|
|
146
|
+
nextVisibility: body.visibility,
|
|
147
|
+
authorUserId: interaction.authorUserId ?? null,
|
|
148
|
+
actorUserId: auth.sub,
|
|
149
|
+
// v1: strict owner-only — only the author reaches this point, so a
|
|
150
|
+
// visibility change is never an admin bypass.
|
|
151
|
+
adminBypass: false,
|
|
152
|
+
tenantId: auth.tenantId,
|
|
153
|
+
organizationId,
|
|
154
|
+
})
|
|
155
|
+
} catch {
|
|
156
|
+
/* swallow — audit emission must not block the response */
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return NextResponse.json({ ok: true, changed: true })
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const openApi = {
|
|
163
|
+
tags: ['Customers', 'Email'],
|
|
164
|
+
methods: {
|
|
165
|
+
PATCH: {
|
|
166
|
+
summary: 'Flip an email interaction visibility (private ↔ shared)',
|
|
167
|
+
tags: ['Customers', 'Email'],
|
|
168
|
+
responses: [
|
|
169
|
+
{ status: 200, description: 'Updated' },
|
|
170
|
+
{ status: 400, description: 'Invalid id' },
|
|
171
|
+
{ status: 401, description: 'Unauthorized' },
|
|
172
|
+
{ status: 404, description: 'Email not found or not visible to caller' },
|
|
173
|
+
{ status: 422, description: 'Invalid body' },
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export default PATCH
|
|
@@ -8,6 +8,7 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
|
8
8
|
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
9
9
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
10
10
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
11
|
+
import { applyEmailVisibilityFilter } from '../../../lib/visibilityFilter'
|
|
11
12
|
|
|
12
13
|
const querySchema = z.object({
|
|
13
14
|
entityId: z.string().uuid(),
|
|
@@ -87,6 +88,15 @@ export async function GET(req: Request) {
|
|
|
87
88
|
baseQuery = baseQuery.where('status', '=', query.status)
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
// Per-user email privacy: exclude other users' private emails from the
|
|
92
|
+
// per-type counts so the `email` total matches the visibility-filtered list.
|
|
93
|
+
// v1 strict owner-only — no admin bypass (the filter ignores caller features).
|
|
94
|
+
const viewerUserId = auth.isApiKey ? null : auth.sub ?? null
|
|
95
|
+
baseQuery = applyEmailVisibilityFilter(baseQuery, {
|
|
96
|
+
currentUserId: viewerUserId,
|
|
97
|
+
userFeatures: undefined,
|
|
98
|
+
})
|
|
99
|
+
|
|
90
100
|
// Raw SELECT: reads only unencrypted columns (id, interaction_type); title/notes are excluded to avoid ciphertext leakage.
|
|
91
101
|
const rows = await baseQuery
|
|
92
102
|
.select(['interaction_type', sql<string>`count(*)`.as('count')])
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
defaultOkResponseSchema,
|
|
23
23
|
} from '../openapi'
|
|
24
24
|
import { CUSTOMER_INTERACTION_ENTITY_ID } from '../../lib/interactionCompatibility'
|
|
25
|
+
import { applyEmailVisibilityFilter } from '../../lib/visibilityFilter'
|
|
25
26
|
import { resolveCanonicalActivityTargetId } from '../../lib/legacyActivityBridge'
|
|
26
27
|
import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
27
28
|
|
|
@@ -301,6 +302,7 @@ async function buildEnricherContext(
|
|
|
301
302
|
container: { resolve: (name: string) => unknown },
|
|
302
303
|
auth: NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>,
|
|
303
304
|
organizationId: string | null,
|
|
305
|
+
precomputedUserFeatures?: { userId: string; features: string[] | undefined },
|
|
304
306
|
): Promise<EnricherContext> {
|
|
305
307
|
const userId =
|
|
306
308
|
(typeof auth.sub === 'string' && auth.sub.trim().length > 0
|
|
@@ -311,13 +313,20 @@ async function buildEnricherContext(
|
|
|
311
313
|
? auth.keyId
|
|
312
314
|
: 'system')
|
|
313
315
|
|
|
316
|
+
// Reuse features already resolved for this same user (the GET handler resolves
|
|
317
|
+
// them once for the visibility filter) to avoid a second RBAC lookup per request.
|
|
318
|
+
const userFeatures =
|
|
319
|
+
precomputedUserFeatures && precomputedUserFeatures.userId === userId
|
|
320
|
+
? precomputedUserFeatures.features
|
|
321
|
+
: await resolveUserFeatures(container, userId, auth.tenantId ?? null, organizationId)
|
|
322
|
+
|
|
314
323
|
return {
|
|
315
324
|
organizationId: organizationId ?? '',
|
|
316
325
|
tenantId: auth.tenantId ?? '',
|
|
317
326
|
userId,
|
|
318
327
|
em: container.resolve('em'),
|
|
319
328
|
container,
|
|
320
|
-
userFeatures
|
|
329
|
+
userFeatures,
|
|
321
330
|
}
|
|
322
331
|
}
|
|
323
332
|
|
|
@@ -415,6 +424,12 @@ export async function GET(req: Request) {
|
|
|
415
424
|
}
|
|
416
425
|
if (query.excludeInteractionType) rowsQuery = rowsQuery.where('interaction_type', '!=', query.excludeInteractionType)
|
|
417
426
|
if (query.search) {
|
|
427
|
+
// NOTE: for tenants with data encryption enabled, `title`/`body` are
|
|
428
|
+
// ciphertext at rest (see encryption.ts), so this ILIKE matches encrypted
|
|
429
|
+
// bytes and returns no rows — substring search over encrypted free-text
|
|
430
|
+
// columns is unsupported, the same documented limitation as
|
|
431
|
+
// customer_activity / customer_comment. The returned page's title/body are
|
|
432
|
+
// still decrypted for display further below.
|
|
418
433
|
const searchTerm = `%${query.search}%`
|
|
419
434
|
rowsQuery = rowsQuery.where(sql<boolean>`coalesce(title, '') ilike ${searchTerm} or coalesce(body, '') ilike ${searchTerm}`)
|
|
420
435
|
}
|
|
@@ -438,6 +453,21 @@ export async function GET(req: Request) {
|
|
|
438
453
|
]))
|
|
439
454
|
}
|
|
440
455
|
|
|
456
|
+
// ── Email visibility filter (2026-05-27) ──────────────────────────────
|
|
457
|
+
// Non-email interactions pass through; email rows with visibility='private'
|
|
458
|
+
// are filtered out unless the caller is the author or has admin bypass.
|
|
459
|
+
// API-key callers have no user identity (`auth.sub` undefined): resolve the
|
|
460
|
+
// viewer to null so they never gain the author bypass and only see shared
|
|
461
|
+
// emails (fail-closed). Mirrors counts/people/activities routes.
|
|
462
|
+
const viewerUserId = auth.isApiKey ? null : (auth.sub ?? null)
|
|
463
|
+
const callerUserFeatures = viewerUserId
|
|
464
|
+
? await resolveUserFeatures(container, viewerUserId, auth.tenantId ?? null, selectedOrganizationId)
|
|
465
|
+
: undefined
|
|
466
|
+
rowsQuery = applyEmailVisibilityFilter(rowsQuery as any, {
|
|
467
|
+
currentUserId: viewerUserId,
|
|
468
|
+
userFeatures: callerUserFeatures,
|
|
469
|
+
})
|
|
470
|
+
|
|
441
471
|
rowsQuery = rowsQuery.orderBy(sql`${sql.raw(sortSql)} ${sql.raw(sortDir)}`).orderBy('id', sortDir)
|
|
442
472
|
|
|
443
473
|
const rows = await rowsQuery.execute() as InteractionListRow[]
|
|
@@ -460,7 +490,7 @@ export async function GET(req: Request) {
|
|
|
460
490
|
)
|
|
461
491
|
const interactionIds = pageRows.map((row) => row.id)
|
|
462
492
|
|
|
463
|
-
const [users, deals, customFieldValues] = await Promise.all([
|
|
493
|
+
const [users, deals, customFieldValues, interactionRecords] = await Promise.all([
|
|
464
494
|
authorIds.length > 0 ? findWithDecryption(em, User, { id: { $in: authorIds } }, undefined, { tenantId: auth.tenantId, organizationId: selectedOrganizationId }) : Promise.resolve([]),
|
|
465
495
|
dealIds.length > 0 ? findWithDecryption(em, CustomerDeal, { id: { $in: dealIds } }, undefined, { tenantId: auth.tenantId, organizationId: selectedOrganizationId }) : Promise.resolve([]),
|
|
466
496
|
interactionIds.length > 0
|
|
@@ -473,6 +503,9 @@ export async function GET(req: Request) {
|
|
|
473
503
|
tenantFallbacks: [auth.tenantId].filter((value): value is string => !!value),
|
|
474
504
|
})
|
|
475
505
|
: Promise.resolve<Record<string, Record<string, unknown>>>({}),
|
|
506
|
+
interactionIds.length > 0
|
|
507
|
+
? findWithDecryption(em, CustomerInteraction, { id: { $in: interactionIds } } as never, undefined, { tenantId: auth.tenantId, organizationId: selectedOrganizationId })
|
|
508
|
+
: Promise.resolve([]),
|
|
476
509
|
])
|
|
477
510
|
|
|
478
511
|
const userMap = new Map(
|
|
@@ -487,14 +520,22 @@ export async function GET(req: Request) {
|
|
|
487
520
|
const dealMap = new Map(
|
|
488
521
|
deals.map((deal) => [deal.id, deal.title]),
|
|
489
522
|
)
|
|
523
|
+
// title/body are encrypted at rest (see encryption.ts). The kysely rows above
|
|
524
|
+
// carry ciphertext when tenant encryption is enabled, so override them with the
|
|
525
|
+
// decrypted values from findWithDecryption for the returned page.
|
|
526
|
+
const interactionContentMap = new Map(
|
|
527
|
+
(interactionRecords as Array<{ id: string; title?: string | null; body?: string | null }>).map(
|
|
528
|
+
(record) => [record.id, { title: record.title ?? null, body: record.body ?? null }],
|
|
529
|
+
),
|
|
530
|
+
)
|
|
490
531
|
|
|
491
532
|
const baseItems = pageRows.map((row) => ({
|
|
492
533
|
id: row.id,
|
|
493
534
|
entityId: row.entity_id,
|
|
494
535
|
dealId: row.deal_id ?? null,
|
|
495
536
|
interactionType: row.interaction_type,
|
|
496
|
-
title: row.title ?? null,
|
|
497
|
-
body: row.body ?? null,
|
|
537
|
+
title: (interactionContentMap.has(row.id) ? interactionContentMap.get(row.id)!.title : row.title) ?? null,
|
|
538
|
+
body: (interactionContentMap.has(row.id) ? interactionContentMap.get(row.id)!.body : row.body) ?? null,
|
|
498
539
|
status: row.status,
|
|
499
540
|
scheduledAt: toIsoString(row.scheduled_at),
|
|
500
541
|
occurredAt: toIsoString(row.occurred_at),
|
|
@@ -526,7 +567,12 @@ export async function GET(req: Request) {
|
|
|
526
567
|
customValues: normalizeCustomFieldResponse(customFieldValues[row.id]) ?? null,
|
|
527
568
|
}))
|
|
528
569
|
|
|
529
|
-
const enricherContext = await buildEnricherContext(
|
|
570
|
+
const enricherContext = await buildEnricherContext(
|
|
571
|
+
container,
|
|
572
|
+
auth,
|
|
573
|
+
selectedOrganizationId,
|
|
574
|
+
viewerUserId ? { userId: viewerUserId, features: callerUserFeatures } : undefined,
|
|
575
|
+
)
|
|
530
576
|
const enriched = await applyResponseEnrichers(baseItems, 'customers.interaction', enricherContext)
|
|
531
577
|
|
|
532
578
|
let nextCursor: string | undefined
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
4
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
7
|
+
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
8
|
+
import { CustomerEntity } from '../../../../data/entities'
|
|
9
|
+
import { buildPersonEmailThreads } from '../../../../lib/personEmailThreads'
|
|
10
|
+
|
|
11
|
+
export const metadata = {
|
|
12
|
+
path: '/customers/people/[id]/email-threads',
|
|
13
|
+
GET: {
|
|
14
|
+
requireAuth: true,
|
|
15
|
+
requireFeatures: ['customers.people.view'],
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type RouteContext = {
|
|
20
|
+
params: Promise<{ id: string }> | { id: string }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function GET(req: Request, context: RouteContext): Promise<Response> {
|
|
24
|
+
const { id: personId } = await context.params
|
|
25
|
+
if (!z.string().uuid().safeParse(personId).success) {
|
|
26
|
+
return NextResponse.json({ error: 'Invalid person id' }, { status: 400 })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const auth = await getAuthFromRequest(req)
|
|
30
|
+
if (!auth?.sub || !auth?.tenantId) {
|
|
31
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const container = await createRequestContainer()
|
|
35
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
36
|
+
const organizationId = scope?.selectedId ?? (auth as { orgId?: string | null }).orgId ?? null
|
|
37
|
+
const em = (container.resolve('em') as EntityManager).fork()
|
|
38
|
+
const dscope = { tenantId: auth.tenantId as string, organizationId }
|
|
39
|
+
|
|
40
|
+
// Verify the Person exists in the caller's tenant/org (ownership check,
|
|
41
|
+
// same pattern as the [id]/emails compose route).
|
|
42
|
+
const person = await findOneWithDecryption(
|
|
43
|
+
em,
|
|
44
|
+
CustomerEntity,
|
|
45
|
+
{
|
|
46
|
+
id: personId,
|
|
47
|
+
kind: 'person',
|
|
48
|
+
tenantId: auth.tenantId,
|
|
49
|
+
organizationId,
|
|
50
|
+
deletedAt: null,
|
|
51
|
+
} as never,
|
|
52
|
+
undefined,
|
|
53
|
+
dscope,
|
|
54
|
+
)
|
|
55
|
+
if (!person) {
|
|
56
|
+
return NextResponse.json({ error: 'Person not found' }, { status: 404 })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const viewerUserId = auth.isApiKey ? null : auth.sub ?? null
|
|
60
|
+
|
|
61
|
+
const threads = await buildPersonEmailThreads(em, {
|
|
62
|
+
personId,
|
|
63
|
+
tenantId: auth.tenantId as string,
|
|
64
|
+
organizationId,
|
|
65
|
+
viewerUserId,
|
|
66
|
+
// The v1 visibility filter is owner-only and ignores `userFeatures`; match
|
|
67
|
+
// the sibling read routes (people/[id], interactions) by passing `undefined`
|
|
68
|
+
// rather than paying an RBAC round-trip the filter discards.
|
|
69
|
+
userFeatures: undefined,
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
return NextResponse.json({ threads })
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const openApi = {
|
|
76
|
+
tags: ['Customers', 'Email'],
|
|
77
|
+
methods: {
|
|
78
|
+
GET: {
|
|
79
|
+
summary: 'List a Person\'s email threads (Gmail-style conversation grouping)',
|
|
80
|
+
tags: ['Customers', 'Email'],
|
|
81
|
+
responses: [
|
|
82
|
+
{ status: 200, description: 'Email threads for the person, grouped by conversation' },
|
|
83
|
+
{ status: 400, description: 'Invalid person id' },
|
|
84
|
+
{ status: 401, description: 'Unauthorized' },
|
|
85
|
+
{ status: 403, description: 'Missing customers.people.view feature' },
|
|
86
|
+
{ status: 404, description: 'Person not found' },
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export default GET
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
4
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
7
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
8
|
+
import {
|
|
9
|
+
validateCrudMutationGuard,
|
|
10
|
+
runCrudMutationGuardAfterSuccess,
|
|
11
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
12
|
+
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
13
|
+
import { resolveAuthActorId } from '../../../../lib/interactionRequestContext'
|
|
14
|
+
import { CustomerEntity } from '../../../../data/entities'
|
|
15
|
+
import type { SendAsUserService } from '@open-mercato/core/modules/communication_channels/lib/send-as-user'
|
|
16
|
+
|
|
17
|
+
export const metadata = {
|
|
18
|
+
path: '/customers/people/[id]/emails',
|
|
19
|
+
POST: {
|
|
20
|
+
requireAuth: true,
|
|
21
|
+
requireFeatures: ['customers.email.compose'],
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const composeSchema = z
|
|
26
|
+
.object({
|
|
27
|
+
userChannelId: z.string().uuid(),
|
|
28
|
+
to: z.array(z.string().email()).min(1).max(50),
|
|
29
|
+
cc: z.array(z.string().email()).max(50).optional(),
|
|
30
|
+
bcc: z.array(z.string().email()).max(50).optional(),
|
|
31
|
+
subject: z.string().min(1).max(500),
|
|
32
|
+
body: z.string().min(1).max(200_000),
|
|
33
|
+
bodyFormat: z.enum(['text', 'html']).default('html'),
|
|
34
|
+
visibility: z.enum(['private', 'shared']).default('private'),
|
|
35
|
+
inReplyTo: z.string().max(500).optional(),
|
|
36
|
+
references: z.array(z.string().max(500)).max(50).optional(),
|
|
37
|
+
parentMessageId: z.string().uuid().optional(),
|
|
38
|
+
})
|
|
39
|
+
.strict()
|
|
40
|
+
|
|
41
|
+
type RouteContext = {
|
|
42
|
+
params: Promise<{ id: string }> | { id: string }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function POST(req: Request, context: RouteContext): Promise<Response> {
|
|
46
|
+
const { id: personId } = await context.params
|
|
47
|
+
if (!z.string().uuid().safeParse(personId).success) {
|
|
48
|
+
return NextResponse.json({ error: 'Invalid person id' }, { status: 400 })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const auth = await getAuthFromRequest(req)
|
|
52
|
+
if (!auth?.sub || !auth?.tenantId) {
|
|
53
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let body: z.infer<typeof composeSchema>
|
|
57
|
+
try {
|
|
58
|
+
body = composeSchema.parse(await readJsonSafe(req, null))
|
|
59
|
+
} catch (err) {
|
|
60
|
+
return NextResponse.json(
|
|
61
|
+
{ error: err instanceof Error ? err.message : 'Invalid request body' },
|
|
62
|
+
{ status: 422 },
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const container = await createRequestContainer()
|
|
67
|
+
const userId = resolveAuthActorId(auth)
|
|
68
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
69
|
+
const organizationId = scope?.selectedId ?? (auth as { orgId?: string | null }).orgId ?? null
|
|
70
|
+
|
|
71
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
72
|
+
tenantId: auth.tenantId,
|
|
73
|
+
organizationId,
|
|
74
|
+
userId,
|
|
75
|
+
resourceKind: 'customers.person',
|
|
76
|
+
resourceId: personId,
|
|
77
|
+
operation: 'custom',
|
|
78
|
+
requestMethod: req.method,
|
|
79
|
+
requestHeaders: req.headers,
|
|
80
|
+
})
|
|
81
|
+
if (guardResult && !guardResult.ok) {
|
|
82
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const em = (container.resolve('em') as EntityManager).fork()
|
|
86
|
+
const dscope = { tenantId: auth.tenantId as string, organizationId }
|
|
87
|
+
|
|
88
|
+
// 1. Verify the Person exists in the caller's tenant.
|
|
89
|
+
// Uses CustomerEntity (kind='person') as the canonical ownership check —
|
|
90
|
+
// the same pattern as the existing [id]/route.ts GET handler.
|
|
91
|
+
const person = await findOneWithDecryption(
|
|
92
|
+
em,
|
|
93
|
+
CustomerEntity,
|
|
94
|
+
{
|
|
95
|
+
id: personId,
|
|
96
|
+
kind: 'person',
|
|
97
|
+
tenantId: auth.tenantId,
|
|
98
|
+
organizationId,
|
|
99
|
+
deletedAt: null,
|
|
100
|
+
} as never,
|
|
101
|
+
undefined,
|
|
102
|
+
dscope,
|
|
103
|
+
)
|
|
104
|
+
if (!person) {
|
|
105
|
+
return NextResponse.json({ error: 'Person not found' }, { status: 404 })
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 2. Call the hub's send-as-user facade in-process (resolved via DI) so the
|
|
109
|
+
// customers module makes no HTTP self-call. `crmVisibility` and `crmPersonId`
|
|
110
|
+
// are injected as channelMetadata so the link-channel-message subscriber can
|
|
111
|
+
// anchor the sent message back to this Person on the
|
|
112
|
+
// `communication_channels.message.sent` event.
|
|
113
|
+
const sendAsUserService = container.resolve(
|
|
114
|
+
'communicationChannelsSendAsUser',
|
|
115
|
+
) as SendAsUserService
|
|
116
|
+
|
|
117
|
+
const sendResult = await sendAsUserService(
|
|
118
|
+
container,
|
|
119
|
+
{ userId: auth.sub as string, tenantId: auth.tenantId as string, organizationId, auth },
|
|
120
|
+
{
|
|
121
|
+
userChannelId: body.userChannelId,
|
|
122
|
+
to: body.to,
|
|
123
|
+
cc: body.cc,
|
|
124
|
+
bcc: body.bcc,
|
|
125
|
+
subject: body.subject,
|
|
126
|
+
body: body.bodyFormat === 'html' ? { html: body.body } : { plain: body.body },
|
|
127
|
+
inReplyTo: body.inReplyTo,
|
|
128
|
+
references: body.references,
|
|
129
|
+
parentMessageId: body.parentMessageId,
|
|
130
|
+
channelMetadata: {
|
|
131
|
+
crmVisibility: body.visibility,
|
|
132
|
+
crmPersonId: personId,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if (!sendResult.ok) {
|
|
138
|
+
return NextResponse.json({ error: sendResult.error }, { status: sendResult.status })
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
142
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
143
|
+
tenantId: auth.tenantId,
|
|
144
|
+
organizationId,
|
|
145
|
+
userId,
|
|
146
|
+
resourceKind: 'customers.person',
|
|
147
|
+
resourceId: personId,
|
|
148
|
+
operation: 'custom',
|
|
149
|
+
requestMethod: req.method,
|
|
150
|
+
requestHeaders: req.headers,
|
|
151
|
+
metadata: guardResult.metadata ?? null,
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Delivery is async (handled by the outbound queue worker), so this is the
|
|
156
|
+
// enqueue time — not the provider send time — and the provider's external
|
|
157
|
+
// message id is not known yet (the worker assigns it on successful delivery).
|
|
158
|
+
return NextResponse.json({
|
|
159
|
+
messageId: sendResult.messageId,
|
|
160
|
+
threadId: sendResult.threadId,
|
|
161
|
+
queuedAt: new Date().toISOString(),
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export const openApi = {
|
|
166
|
+
tags: ['Customers', 'Email'],
|
|
167
|
+
methods: {
|
|
168
|
+
POST: {
|
|
169
|
+
summary: 'Compose + send an email anchored to a Person',
|
|
170
|
+
tags: ['Customers', 'Email'],
|
|
171
|
+
responses: [
|
|
172
|
+
{ status: 200, description: 'Email queued for send' },
|
|
173
|
+
{ status: 400, description: 'Invalid person id' },
|
|
174
|
+
{ status: 401, description: 'Unauthorized' },
|
|
175
|
+
{ status: 403, description: 'Missing customers.email.compose feature or mutation guard rejection' },
|
|
176
|
+
{ status: 404, description: 'Person or channel not found' },
|
|
177
|
+
{ status: 409, description: 'Channel not connected' },
|
|
178
|
+
{ status: 422, description: 'Invalid request body' },
|
|
179
|
+
{ status: 500, description: 'Send failed' },
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
}
|
|
184
|
+
export default POST
|
|
@@ -40,6 +40,7 @@ import { parseBooleanFromUnknown, parseBooleanToken } from '@open-mercato/shared
|
|
|
40
40
|
import { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'
|
|
41
41
|
import { loadPersonCompanyLinks, summarizePersonCompanies } from '../../../lib/personCompanies'
|
|
42
42
|
import { normalizeCustomerDetailCustomFields } from '../../detailCustomFields'
|
|
43
|
+
import { buildEmailVisibilityMikroFilter } from '../../../lib/visibilityFilter'
|
|
43
44
|
|
|
44
45
|
export const metadata = {
|
|
45
46
|
GET: { requireAuth: true, requireFeatures: ['customers.people.view'] },
|
|
@@ -513,14 +514,25 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
513
514
|
profiler.mark('comments_loaded', { count: comments.length })
|
|
514
515
|
}
|
|
515
516
|
|
|
517
|
+
// Per-user email privacy (CRM email integration spec, "Layer 1 — DB filter"):
|
|
518
|
+
// exclude private email interactions owned by other users from every read
|
|
519
|
+
// path that returns customer_interactions. Non-email rows, shared emails, and
|
|
520
|
+
// legacy null-visibility rows pass through. v1 is strict owner-only: there is
|
|
521
|
+
// NO admin bypass — the filter ignores caller features, and
|
|
522
|
+
// `customers.email.view_private` is reserved (inert) for v2 oversight.
|
|
523
|
+
const emailVisibilityFilter = buildEmailVisibilityMikroFilter({
|
|
524
|
+
currentUserId: viewerUserId,
|
|
525
|
+
userFeatures: undefined,
|
|
526
|
+
})
|
|
527
|
+
|
|
516
528
|
const shouldLoadCanonicalInteractions = includeInteractions || includeActivities || includeTodos
|
|
517
529
|
const canonicalInteractionRows = shouldLoadCanonicalInteractions
|
|
518
530
|
? await findWithDecryption(
|
|
519
531
|
em,
|
|
520
532
|
CustomerInteraction,
|
|
521
533
|
interactionFlags.unified
|
|
522
|
-
? { entity: person.id, deletedAt: null }
|
|
523
|
-
: { entity: person.id },
|
|
534
|
+
? { entity: person.id, deletedAt: null, ...emailVisibilityFilter }
|
|
535
|
+
: { entity: person.id, ...emailVisibilityFilter },
|
|
524
536
|
{ orderBy: { scheduledAt: 'asc', createdAt: 'desc' }, limit: 100 },
|
|
525
537
|
{ tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null },
|
|
526
538
|
)
|
|
@@ -566,6 +578,7 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
566
578
|
deletedAt: null,
|
|
567
579
|
status: 'planned',
|
|
568
580
|
interactionType: { $ne: 'task' },
|
|
581
|
+
...emailVisibilityFilter,
|
|
569
582
|
},
|
|
570
583
|
{ orderBy: { scheduledAt: 'ASC', createdAt: 'ASC' }, limit: plannedPreviewLimit },
|
|
571
584
|
{ tenantId: person.tenantId ?? auth.tenantId ?? null, organizationId: person.organizationId ?? auth.orgId ?? null },
|
|
@@ -715,12 +728,14 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
715
728
|
tenantId: person.tenantId,
|
|
716
729
|
deletedAt: null,
|
|
717
730
|
interactionType: { $ne: 'task' },
|
|
731
|
+
...emailVisibilityFilter,
|
|
718
732
|
}),
|
|
719
733
|
interactions: await em.count(CustomerInteraction, {
|
|
720
734
|
entity: person.id,
|
|
721
735
|
organizationId: person.organizationId,
|
|
722
736
|
tenantId: person.tenantId,
|
|
723
737
|
deletedAt: null,
|
|
738
|
+
...emailVisibilityFilter,
|
|
724
739
|
}),
|
|
725
740
|
todos: interactionFlags.unified
|
|
726
741
|
? await em.count(CustomerInteraction, {
|