@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,267 @@
|
|
|
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 type { CommandBus } from '@open-mercato/shared/lib/commands'
|
|
7
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
8
|
+
import { ExternalConversation, MessageChannelLink } from '../../../data/entities'
|
|
9
|
+
import {
|
|
10
|
+
COMMUNICATION_CHANNELS_CONNECT_CREDENTIAL_CHANNEL_COMMAND_ID,
|
|
11
|
+
type ConnectCredentialChannelInput,
|
|
12
|
+
type ConnectCredentialChannelResult,
|
|
13
|
+
} from '../../../commands/connect-credential-channel'
|
|
14
|
+
import { emitCommunicationChannelsEvent } from '../../../events'
|
|
15
|
+
import {
|
|
16
|
+
TEST_SEED_PROVIDER_KEY,
|
|
17
|
+
ensureTestSeedAdapterRegistered,
|
|
18
|
+
isTestChannelSeedingEnabled,
|
|
19
|
+
} from '../../../lib/test-seed'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* TEST-ONLY channel seeding endpoint.
|
|
23
|
+
*
|
|
24
|
+
* Gated by `OM_ENABLE_TEST_CHANNEL_SEEDING` — when the flag is unset (the
|
|
25
|
+
* production default) every request returns 404, so this route is invisible and
|
|
26
|
+
* inert in production. See `lib/test-seed.ts` for the full rationale.
|
|
27
|
+
*
|
|
28
|
+
* Two actions, both scoped to the caller's tenant/org:
|
|
29
|
+
* - `connect-channel`: connect a network-free `__test_seed__` channel owned by
|
|
30
|
+
* the caller (delegates to the real connect-credential command so the channel
|
|
31
|
+
* persists credentials + lands in `status='connected'`). Enables the outbound
|
|
32
|
+
* compose → deliver → `.sent` chain to complete in CI.
|
|
33
|
+
* - `emit-inbound`: insert an inbound `MessageChannelLink` (+ a `messages.message`
|
|
34
|
+
* row for threading) and emit `communication_channels.message.received` so the
|
|
35
|
+
* customers link-channel-message subscriber runs against real Postgres. Enables
|
|
36
|
+
* the inbound auto-link tests (TC-CRM-EMAIL-002..005).
|
|
37
|
+
*/
|
|
38
|
+
export const metadata = {
|
|
39
|
+
path: '/communication_channels/test-seed',
|
|
40
|
+
POST: {
|
|
41
|
+
requireAuth: true,
|
|
42
|
+
requireFeatures: ['communication_channels.connect_user_channel'],
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const addressObjectSchema = z.object({ address: z.string(), name: z.string().optional() })
|
|
47
|
+
const addressFieldSchema = z.union([
|
|
48
|
+
z.string(),
|
|
49
|
+
addressObjectSchema,
|
|
50
|
+
z.array(z.union([z.string(), addressObjectSchema])),
|
|
51
|
+
])
|
|
52
|
+
|
|
53
|
+
const connectChannelSchema = z.object({
|
|
54
|
+
action: z.literal('connect-channel'),
|
|
55
|
+
displayName: z.string().min(1).max(255).optional(),
|
|
56
|
+
externalIdentifier: z.string().min(1).max(255).optional(),
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const emitInboundSchema = z.object({
|
|
60
|
+
action: z.literal('emit-inbound'),
|
|
61
|
+
/** Channel that owns the inbound message; controls authorUserId + default visibility. */
|
|
62
|
+
channelId: z.string().uuid(),
|
|
63
|
+
/** Provider key persisted on the link (defaults to the stub provider). */
|
|
64
|
+
providerKey: z.string().min(1).max(64).optional(),
|
|
65
|
+
/** Normalized inbound addresses (stored under channelPayload). */
|
|
66
|
+
from: addressFieldSchema.optional(),
|
|
67
|
+
to: addressFieldSchema.optional(),
|
|
68
|
+
cc: addressFieldSchema.optional(),
|
|
69
|
+
subject: z.string().max(500).optional(),
|
|
70
|
+
bodyText: z.string().max(200_000).optional(),
|
|
71
|
+
/** RFC2822 Message-ID of this inbound message (for In-Reply-To matching). */
|
|
72
|
+
messageId: z.string().max(500).optional(),
|
|
73
|
+
/** RFC2822 In-Reply-To header (threading-inheritance fallback). */
|
|
74
|
+
inReplyTo: z.string().max(500).optional(),
|
|
75
|
+
references: z.array(z.string().max(500)).max(50).optional(),
|
|
76
|
+
/**
|
|
77
|
+
* Open Mercato `messages.message` thread id this inbound message belongs to.
|
|
78
|
+
* When set, a `messages.message` row is created with this `threadId` so the
|
|
79
|
+
* hub-thread inheritance join can resolve a Person from a sibling message.
|
|
80
|
+
*/
|
|
81
|
+
messageThreadId: z.string().uuid().optional(),
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const bodySchema = z.discriminatedUnion('action', [connectChannelSchema, emitInboundSchema])
|
|
85
|
+
|
|
86
|
+
export async function POST(req: Request): Promise<Response> {
|
|
87
|
+
// Fail-closed: invisible in production. Mirrors an unknown route (404) rather
|
|
88
|
+
// than 403 so the surface leaks nothing when the flag is off.
|
|
89
|
+
if (!isTestChannelSeedingEnabled()) {
|
|
90
|
+
return NextResponse.json({ error: 'Not found' }, { status: 404 })
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const auth = await getAuthFromRequest(req)
|
|
94
|
+
if (!auth?.sub || !auth?.tenantId) {
|
|
95
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let body: z.infer<typeof bodySchema>
|
|
99
|
+
try {
|
|
100
|
+
body = bodySchema.parse(await readJsonSafe(req, null))
|
|
101
|
+
} catch (err) {
|
|
102
|
+
return NextResponse.json(
|
|
103
|
+
{ error: err instanceof Error ? err.message : 'Invalid request body' },
|
|
104
|
+
{ status: 422 },
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const container = await createRequestContainer()
|
|
109
|
+
// Defensive: make sure the stub adapter is registered for this process even if
|
|
110
|
+
// a worker-only node skipped module di registration.
|
|
111
|
+
ensureTestSeedAdapterRegistered()
|
|
112
|
+
|
|
113
|
+
const tenantId = auth.tenantId as string
|
|
114
|
+
const organizationId = (auth as { orgId?: string | null }).orgId ?? null
|
|
115
|
+
const userId = auth.sub as string
|
|
116
|
+
|
|
117
|
+
if (body.action === 'connect-channel') {
|
|
118
|
+
const stamp = Date.now()
|
|
119
|
+
const commandBus = container.resolve('commandBus') as CommandBus
|
|
120
|
+
const input: ConnectCredentialChannelInput = {
|
|
121
|
+
providerKey: TEST_SEED_PROVIDER_KEY,
|
|
122
|
+
displayName: body.displayName ?? `Test Seed Channel ${stamp}`,
|
|
123
|
+
credentials: {
|
|
124
|
+
username: body.externalIdentifier ?? `test-seed-${stamp}@test-seed.local`,
|
|
125
|
+
fromAddress: body.externalIdentifier ?? `test-seed-${stamp}@test-seed.local`,
|
|
126
|
+
},
|
|
127
|
+
userId,
|
|
128
|
+
scope: { tenantId, organizationId },
|
|
129
|
+
}
|
|
130
|
+
const { result } = await commandBus.execute<
|
|
131
|
+
ConnectCredentialChannelInput,
|
|
132
|
+
ConnectCredentialChannelResult
|
|
133
|
+
>(COMMUNICATION_CHANNELS_CONNECT_CREDENTIAL_CHANNEL_COMMAND_ID, {
|
|
134
|
+
input,
|
|
135
|
+
ctx: {
|
|
136
|
+
container,
|
|
137
|
+
auth: auth as never,
|
|
138
|
+
organizationScope: null,
|
|
139
|
+
selectedOrganizationId: organizationId,
|
|
140
|
+
organizationIds: organizationId ? [organizationId] : null,
|
|
141
|
+
},
|
|
142
|
+
})
|
|
143
|
+
if (result.status !== 'connected') {
|
|
144
|
+
return NextResponse.json(
|
|
145
|
+
{ error: '[internal] test-seed connect failed', detail: result },
|
|
146
|
+
{ status: 500 },
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
return NextResponse.json(
|
|
150
|
+
{ channelId: result.channelId, externalIdentifier: result.externalIdentifier },
|
|
151
|
+
{ status: 201 },
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// action === 'emit-inbound'
|
|
156
|
+
const em = (container.resolve('em') as EntityManager).fork()
|
|
157
|
+
const providerKey = body.providerKey ?? TEST_SEED_PROVIDER_KEY
|
|
158
|
+
|
|
159
|
+
// A MessageChannelLink requires a non-null external_conversation_id (FK) and
|
|
160
|
+
// message_id. Create a synthetic conversation + (optionally threaded) message
|
|
161
|
+
// so the link is shaped like a real inbound row the subscriber can consume.
|
|
162
|
+
const conversation = em.create(ExternalConversation, {
|
|
163
|
+
channelId: body.channelId,
|
|
164
|
+
externalConversationId: `inbound-seed:${Date.now()}:${Math.random().toString(16).slice(2, 8)}`,
|
|
165
|
+
subject: body.subject ?? null,
|
|
166
|
+
tenantId,
|
|
167
|
+
organizationId,
|
|
168
|
+
lastMessageAt: new Date(),
|
|
169
|
+
})
|
|
170
|
+
em.persist(conversation)
|
|
171
|
+
await em.flush()
|
|
172
|
+
|
|
173
|
+
// Insert the platform `messages.message` row via raw SQL rather than importing
|
|
174
|
+
// the messages module's entity class (cross-module ORM coupling rule). Only
|
|
175
|
+
// `thread_id` matters for the hub-thread inheritance join (TC-CRM-EMAIL-005);
|
|
176
|
+
// the rest satisfy NOT NULL constraints.
|
|
177
|
+
const messageRows = (await em.getConnection().execute(
|
|
178
|
+
`INSERT INTO messages
|
|
179
|
+
(type, thread_id, sender_user_id, subject, body, body_format, priority, status,
|
|
180
|
+
is_draft, sent_at, visibility, source_entity_type, source_entity_id,
|
|
181
|
+
tenant_id, organization_id, created_at, updated_at)
|
|
182
|
+
VALUES
|
|
183
|
+
(?, ?, ?, ?, ?, 'text', 'normal', 'sent',
|
|
184
|
+
false, now(), 'public', 'communication_channels.test_seed_inbound', ?,
|
|
185
|
+
?, ?, now(), now())
|
|
186
|
+
RETURNING id`,
|
|
187
|
+
[
|
|
188
|
+
`channel.${providerKey}`,
|
|
189
|
+
body.messageThreadId ?? null,
|
|
190
|
+
userId,
|
|
191
|
+
body.subject ?? '(no subject)',
|
|
192
|
+
body.bodyText ?? '',
|
|
193
|
+
body.channelId,
|
|
194
|
+
tenantId,
|
|
195
|
+
organizationId,
|
|
196
|
+
],
|
|
197
|
+
)) as Array<{ id: string }>
|
|
198
|
+
const messageId = messageRows[0]?.id
|
|
199
|
+
if (!messageId) {
|
|
200
|
+
return NextResponse.json({ error: '[internal] failed to seed message row' }, { status: 500 })
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const link = em.create(MessageChannelLink, {
|
|
204
|
+
messageId,
|
|
205
|
+
externalConversationId: conversation.id,
|
|
206
|
+
providerKey,
|
|
207
|
+
channelType: 'email',
|
|
208
|
+
direction: 'inbound',
|
|
209
|
+
deliveryStatus: 'delivered',
|
|
210
|
+
channelPayload: {
|
|
211
|
+
...(body.from !== undefined ? { from: body.from } : {}),
|
|
212
|
+
...(body.to !== undefined ? { to: body.to } : {}),
|
|
213
|
+
...(body.cc !== undefined ? { cc: body.cc } : {}),
|
|
214
|
+
...(body.subject !== undefined ? { subject: body.subject } : {}),
|
|
215
|
+
...(body.bodyText !== undefined ? { text: body.bodyText } : {}),
|
|
216
|
+
...(body.inReplyTo !== undefined ? { inReplyTo: body.inReplyTo } : {}),
|
|
217
|
+
...(body.references !== undefined ? { references: body.references } : {}),
|
|
218
|
+
},
|
|
219
|
+
channelContentType: 'text/plain',
|
|
220
|
+
channelMetadata: {
|
|
221
|
+
...(body.messageId !== undefined ? { messageId: body.messageId } : {}),
|
|
222
|
+
},
|
|
223
|
+
tenantId,
|
|
224
|
+
organizationId,
|
|
225
|
+
})
|
|
226
|
+
em.persist(link)
|
|
227
|
+
await em.flush()
|
|
228
|
+
|
|
229
|
+
// Emit the hub event through the real event bus so the persistent customers
|
|
230
|
+
// link-channel-message-received subscriber is enqueued to the `events` queue.
|
|
231
|
+
await emitCommunicationChannelsEvent(
|
|
232
|
+
'communication_channels.message.received',
|
|
233
|
+
{
|
|
234
|
+
channelLinkId: link.id,
|
|
235
|
+
channelId: body.channelId,
|
|
236
|
+
providerKey,
|
|
237
|
+
direction: 'inbound',
|
|
238
|
+
tenantId,
|
|
239
|
+
organizationId,
|
|
240
|
+
},
|
|
241
|
+
{ persistent: true },
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
return NextResponse.json(
|
|
245
|
+
{ channelLinkId: link.id, messageId, conversationId: conversation.id },
|
|
246
|
+
{ status: 201 },
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export const openApi = {
|
|
251
|
+
tags: ['CommunicationChannels'],
|
|
252
|
+
methods: {
|
|
253
|
+
POST: {
|
|
254
|
+
summary: 'Test-only: seed a connected channel or emit an inbound message (env-gated)',
|
|
255
|
+
tags: ['CommunicationChannels'],
|
|
256
|
+
responses: [
|
|
257
|
+
{ status: 201, description: 'Channel seeded / inbound message emitted' },
|
|
258
|
+
{ status: 401, description: 'Unauthorized' },
|
|
259
|
+
{ status: 404, description: 'Test channel seeding disabled (production default)' },
|
|
260
|
+
{ status: 422, description: 'Invalid request body' },
|
|
261
|
+
{ status: 500, description: 'Seed failed' },
|
|
262
|
+
],
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export default POST
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
3
|
+
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
4
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
5
|
+
import { CommunicationChannel } from '../../../../data/entities'
|
|
6
|
+
import { getChannelAdapterRegistry } from '../../../../lib/adapter-registry-singleton'
|
|
7
|
+
import {
|
|
8
|
+
COMMUNICATION_CHANNELS_QUEUES,
|
|
9
|
+
getCommunicationChannelsQueue,
|
|
10
|
+
} from '../../../../lib/queue'
|
|
11
|
+
import type { InboundMessage } from '../../../../lib/adapter'
|
|
12
|
+
import type { InboundProcessorPayload } from '../../../../workers/inbound-processor'
|
|
13
|
+
import type { ReactionInboundJob } from '../../../../lib/reaction-processor-types'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Inbound webhook endpoint for the communication_channels hub.
|
|
17
|
+
*
|
|
18
|
+
* One endpoint per provider — provider key in the path. The hub iterates all
|
|
19
|
+
* candidate `CommunicationChannel` rows for `(provider_key, is_active=true,
|
|
20
|
+
* deleted_at IS NULL)` across tenants and asks each adapter to verify the
|
|
21
|
+
* signature with that channel's credentials. The first successful verification
|
|
22
|
+
* pins the request to that channel's tenant scope; we then enqueue an inbound
|
|
23
|
+
* processor job and return 202.
|
|
24
|
+
*
|
|
25
|
+
* Fail-closed: if no candidate verifies, we return 401 — never 200. This mirrors
|
|
26
|
+
* the per-tenant authentication model used by `shipping_carriers/api/webhook/[provider]`
|
|
27
|
+
* and the security note in SPEC-045d §13.
|
|
28
|
+
*
|
|
29
|
+
* No auth required at the route level — signature verification IS the auth.
|
|
30
|
+
*/
|
|
31
|
+
export const metadata = {
|
|
32
|
+
path: '/communication_channels/webhook/[provider]',
|
|
33
|
+
POST: {
|
|
34
|
+
requireAuth: false,
|
|
35
|
+
// Unauthenticated by design (signature verification IS the auth), but the
|
|
36
|
+
// handler fans out an O(N) cross-tenant candidate scan, so bound per-IP
|
|
37
|
+
// request volume to limit abuse. Generous enough for real provider traffic;
|
|
38
|
+
// the dedicated gmail route carries its own matching limits.
|
|
39
|
+
rateLimit: { points: 120, duration: 60, keyPrefix: 'cc_webhook_inbound' },
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type RouteContext = {
|
|
44
|
+
params: Promise<{ provider: string }> | { provider: string }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function POST(req: Request, { params }: RouteContext): Promise<Response> {
|
|
48
|
+
const resolvedParams = await params
|
|
49
|
+
const providerKey = resolvedParams.provider
|
|
50
|
+
|
|
51
|
+
const registry = getChannelAdapterRegistry()
|
|
52
|
+
const adapter = registry?.get(providerKey)
|
|
53
|
+
if (!adapter) {
|
|
54
|
+
return NextResponse.json(
|
|
55
|
+
{ error: `No ChannelAdapter for provider: ${providerKey}` },
|
|
56
|
+
{ status: 404 },
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const rawBody = await req.text()
|
|
61
|
+
const headers: Record<string, string> = {}
|
|
62
|
+
req.headers.forEach((value, key) => {
|
|
63
|
+
headers[key] = value
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const container = await createRequestContainer()
|
|
67
|
+
const em = (container.resolve('em') as EntityManager).fork()
|
|
68
|
+
|
|
69
|
+
type CredentialsServiceLike = {
|
|
70
|
+
resolve: (
|
|
71
|
+
providerOrIntegrationId: string,
|
|
72
|
+
scope: { organizationId: string; tenantId: string; userId?: string | null },
|
|
73
|
+
) => Promise<Record<string, unknown> | null>
|
|
74
|
+
}
|
|
75
|
+
let credentialsService: CredentialsServiceLike | null = null
|
|
76
|
+
try {
|
|
77
|
+
credentialsService = container.resolve<CredentialsServiceLike>('integrationCredentialsService')
|
|
78
|
+
} catch {
|
|
79
|
+
credentialsService = null
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
// The webhook is unauthenticated. The TENANT MUST be derived from a channel whose
|
|
84
|
+
// per-tenant credentials successfully verify the inbound signature — NEVER from
|
|
85
|
+
// attacker-controlled payload headers or unsigned retries.
|
|
86
|
+
const candidates = await findWithDecryption(
|
|
87
|
+
em,
|
|
88
|
+
CommunicationChannel,
|
|
89
|
+
{
|
|
90
|
+
providerKey,
|
|
91
|
+
isActive: true,
|
|
92
|
+
deletedAt: null,
|
|
93
|
+
},
|
|
94
|
+
{ orderBy: { createdAt: 'desc' } },
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
let matchedChannel: CommunicationChannel | null = null
|
|
98
|
+
let matchedScope: { tenantId: string; organizationId: string } | null = null
|
|
99
|
+
let event: InboundMessage | null = null
|
|
100
|
+
let lastVerificationError: unknown = null
|
|
101
|
+
|
|
102
|
+
for (const candidate of candidates as CommunicationChannel[]) {
|
|
103
|
+
const candidateScope = {
|
|
104
|
+
tenantId: candidate.tenantId,
|
|
105
|
+
organizationId: candidate.organizationId ?? candidate.tenantId,
|
|
106
|
+
}
|
|
107
|
+
// Per-user credential lookup: each candidate channel has its own user
|
|
108
|
+
// and therefore its own credentials row. See review R2-C1 / N1.
|
|
109
|
+
const credentialsLookupScope = {
|
|
110
|
+
...candidateScope,
|
|
111
|
+
userId: candidate.userId ?? null,
|
|
112
|
+
}
|
|
113
|
+
let credentials: Record<string, unknown> = {}
|
|
114
|
+
if (candidate.credentialsRef && credentialsService) {
|
|
115
|
+
try {
|
|
116
|
+
credentials =
|
|
117
|
+
(await credentialsService.resolve(`channel_${providerKey}`, credentialsLookupScope)) ?? {}
|
|
118
|
+
} catch {
|
|
119
|
+
credentials = {}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
event = await adapter.verifyWebhook({
|
|
124
|
+
rawBody,
|
|
125
|
+
headers,
|
|
126
|
+
credentials,
|
|
127
|
+
scope: candidateScope,
|
|
128
|
+
})
|
|
129
|
+
matchedChannel = candidate
|
|
130
|
+
matchedScope = candidateScope
|
|
131
|
+
break
|
|
132
|
+
} catch (error: unknown) {
|
|
133
|
+
lastVerificationError = error
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!event || !matchedChannel || !matchedScope) {
|
|
138
|
+
throw (
|
|
139
|
+
lastVerificationError ?? new Error('Webhook verification failed: no matching channel')
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Dispatch by event type:
|
|
144
|
+
// - 'message' (default) → inbound-processor (slice 2b)
|
|
145
|
+
// - 'reaction' → reaction-processor (slice 2d)
|
|
146
|
+
// - 'status_update' / 'other' → 202 not handled (future slice)
|
|
147
|
+
if (event.eventType === 'reaction') {
|
|
148
|
+
if (typeof adapter.normalizeInboundReaction !== 'function') {
|
|
149
|
+
return NextResponse.json(
|
|
150
|
+
{
|
|
151
|
+
received: true,
|
|
152
|
+
queued: false,
|
|
153
|
+
reason: `adapter '${providerKey}' does not implement normalizeInboundReaction`,
|
|
154
|
+
},
|
|
155
|
+
{ status: 202 },
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
const reactionEvent = await adapter.normalizeInboundReaction(event)
|
|
159
|
+
const reactionJob: ReactionInboundJob = {
|
|
160
|
+
kind: 'inbound',
|
|
161
|
+
providerKey,
|
|
162
|
+
channelId: matchedChannel.id,
|
|
163
|
+
channelType: matchedChannel.channelType,
|
|
164
|
+
event: reactionEvent,
|
|
165
|
+
// Use the channel's REAL org (null when null), matching the poll and
|
|
166
|
+
// dedicated gmail webhook path — `candidateScope` falls org
|
|
167
|
+
// back to tenantId for credential/verify lookups, which must not leak
|
|
168
|
+
// into the ingest scope (it would diverge dedup for null-org channels).
|
|
169
|
+
scope: { tenantId: matchedScope.tenantId, organizationId: matchedChannel.organizationId ?? null },
|
|
170
|
+
attempt: 1,
|
|
171
|
+
}
|
|
172
|
+
const reactionsQueue = getCommunicationChannelsQueue(COMMUNICATION_CHANNELS_QUEUES.reactions)
|
|
173
|
+
await reactionsQueue.enqueue(reactionJob as unknown as Record<string, unknown>)
|
|
174
|
+
return NextResponse.json({ received: true, queued: true, kind: 'reaction' }, { status: 202 })
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (event.eventType && event.eventType !== 'message') {
|
|
178
|
+
return NextResponse.json(
|
|
179
|
+
{
|
|
180
|
+
received: true,
|
|
181
|
+
queued: false,
|
|
182
|
+
reason: `event ${event.eventType} not yet handled`,
|
|
183
|
+
},
|
|
184
|
+
{ status: 202 },
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const queue = getCommunicationChannelsQueue(COMMUNICATION_CHANNELS_QUEUES.inbound)
|
|
189
|
+
const jobPayload: InboundProcessorPayload = {
|
|
190
|
+
providerKey,
|
|
191
|
+
channelId: matchedChannel.id,
|
|
192
|
+
channelType: matchedChannel.channelType,
|
|
193
|
+
raw: event,
|
|
194
|
+
// Ingest scope uses the channel's REAL org (null when null) so dedup matches
|
|
195
|
+
// the poll + dedicated webhook paths; see the reaction branch above.
|
|
196
|
+
scope: { tenantId: matchedScope.tenantId, organizationId: matchedChannel.organizationId ?? null },
|
|
197
|
+
}
|
|
198
|
+
await queue.enqueue(jobPayload as unknown as Record<string, unknown>)
|
|
199
|
+
|
|
200
|
+
return NextResponse.json({ received: true, queued: true, kind: 'message' }, { status: 202 })
|
|
201
|
+
} catch (error: unknown) {
|
|
202
|
+
// Do not echo adapter/verification internals to an unauthenticated caller;
|
|
203
|
+
// log the detail server-side and return a fixed, minimal message.
|
|
204
|
+
console.warn(
|
|
205
|
+
'[communication_channels] inbound webhook verification failed:',
|
|
206
|
+
error instanceof Error ? error.message : error,
|
|
207
|
+
)
|
|
208
|
+
return NextResponse.json({ error: 'verification_failed' }, { status: 401 })
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export const openApi = {
|
|
213
|
+
tags: ['CommunicationChannels'],
|
|
214
|
+
summary: 'Receive a communication channel webhook',
|
|
215
|
+
methods: {
|
|
216
|
+
POST: {
|
|
217
|
+
summary: 'Process an inbound channel webhook (Slack, WhatsApp, Email, ...)',
|
|
218
|
+
tags: ['CommunicationChannels'],
|
|
219
|
+
responses: [
|
|
220
|
+
{ status: 202, description: 'Webhook accepted for async processing' },
|
|
221
|
+
{ status: 401, description: 'Signature verification failed against every candidate channel' },
|
|
222
|
+
{ status: 404, description: 'Unknown provider' },
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
}
|
|
227
|
+
export default POST
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
3
|
+
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
4
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
5
|
+
import { CommunicationChannel } from '../../../../data/entities'
|
|
6
|
+
import {
|
|
7
|
+
COMMUNICATION_CHANNELS_QUEUES,
|
|
8
|
+
getCommunicationChannelsQueue,
|
|
9
|
+
} from '../../../../lib/queue'
|
|
10
|
+
import {
|
|
11
|
+
decodeGmailPubSubBody,
|
|
12
|
+
getGmailPubSubVerifier,
|
|
13
|
+
GmailPubSubJwtError,
|
|
14
|
+
} from '../../../../lib/gmail-pubsub-jwt'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Spec C § Phase C2 — Gmail Pub/Sub push webhook.
|
|
18
|
+
*
|
|
19
|
+
* Auth model: NOT authenticated via the platform's session cookie. Pub/Sub
|
|
20
|
+
* authenticates with a Google-signed JWT in the `Authorization: Bearer …`
|
|
21
|
+
* header, which we verify against Google's public certs.
|
|
22
|
+
*
|
|
23
|
+
* Validation pipeline:
|
|
24
|
+
* 1. Verify JWT signature, audience (`OM_GMAIL_PUBSUB_AUDIENCE`), and
|
|
25
|
+
* email claim (`OM_GMAIL_PUBSUB_SERVICE_ACCOUNT_EMAIL`).
|
|
26
|
+
* 2. Decode the Pub/Sub envelope → `{ emailAddress, historyId }`.
|
|
27
|
+
* 3. Look up every active Gmail channel matching `emailAddress`. Multiple
|
|
28
|
+
* tenants may have the same mailbox connected — we enqueue one sync
|
|
29
|
+
* job per matching channel (each tenant sees their own data).
|
|
30
|
+
* 4. Return `204 No Content` even when no channels match — returning 4xx
|
|
31
|
+
* would cause Pub/Sub to retry forever on a permanently-orphaned
|
|
32
|
+
* registration.
|
|
33
|
+
*/
|
|
34
|
+
export const metadata = {
|
|
35
|
+
path: '/communication_channels/webhooks/gmail',
|
|
36
|
+
// Unauthenticated at the platform layer (a Google-signed JWT is the auth).
|
|
37
|
+
// Rate-limited so a caller can't drive unbounded JWT-verification +
|
|
38
|
+
// cert-fetch work before the signature gate rejects them.
|
|
39
|
+
POST: { requireAuth: false, rateLimit: { points: 120, duration: 60, keyPrefix: 'cc_webhook_gmail' } },
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type GmailHistorySyncJobPayload = {
|
|
43
|
+
channelId: string
|
|
44
|
+
scope: { tenantId: string; organizationId: string | null }
|
|
45
|
+
notification: { emailAddress: string; historyId: string }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function POST(req: Request): Promise<Response> {
|
|
49
|
+
const expectedAudience = process.env.OM_GMAIL_PUBSUB_AUDIENCE
|
|
50
|
+
const expectedEmail = process.env.OM_GMAIL_PUBSUB_SERVICE_ACCOUNT_EMAIL
|
|
51
|
+
if (!expectedAudience || !expectedEmail) {
|
|
52
|
+
// Misconfiguration is operator-facing; log and 503 so Pub/Sub retries
|
|
53
|
+
// briefly and the operator notices.
|
|
54
|
+
console.error(
|
|
55
|
+
'[gmail-webhook] OM_GMAIL_PUBSUB_AUDIENCE / OM_GMAIL_PUBSUB_SERVICE_ACCOUNT_EMAIL not set',
|
|
56
|
+
)
|
|
57
|
+
return NextResponse.json({ error: 'webhook not configured' }, { status: 503 })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const verifier = getGmailPubSubVerifier()
|
|
61
|
+
try {
|
|
62
|
+
await verifier.verify({
|
|
63
|
+
authorizationHeader: req.headers.get('authorization'),
|
|
64
|
+
expectedAudience,
|
|
65
|
+
expectedEmail,
|
|
66
|
+
})
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (err instanceof GmailPubSubJwtError) {
|
|
69
|
+
const status =
|
|
70
|
+
err.code === 'wrong_audience' ? 403 :
|
|
71
|
+
err.code === 'fetch_certs_failed' ? 503 :
|
|
72
|
+
401
|
|
73
|
+
return NextResponse.json({ error: err.code, message: err.message }, { status })
|
|
74
|
+
}
|
|
75
|
+
throw err
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let rawBody: string
|
|
79
|
+
try {
|
|
80
|
+
rawBody = await req.text()
|
|
81
|
+
} catch {
|
|
82
|
+
return NextResponse.json({ error: 'unreadable_body' }, { status: 400 })
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let payload: { emailAddress: string; historyId: string | number }
|
|
86
|
+
try {
|
|
87
|
+
payload = decodeGmailPubSubBody(rawBody)
|
|
88
|
+
} catch (err) {
|
|
89
|
+
return NextResponse.json(
|
|
90
|
+
{ error: err instanceof Error ? err.message : 'invalid_payload' },
|
|
91
|
+
{ status: 400 },
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Channel lookup is intentionally NOT tenant-scoped here — we don't have a
|
|
96
|
+
// tenant signal beyond the email address. We enumerate matching channels
|
|
97
|
+
// across all tenants and enqueue one sync job per match. Each job carries
|
|
98
|
+
// its own tenant scope, so downstream ingest stays tenant-isolated.
|
|
99
|
+
const container = await createRequestContainer()
|
|
100
|
+
const em = (container.resolve('em') as EntityManager).fork()
|
|
101
|
+
const channels = await findWithDecryption(
|
|
102
|
+
em,
|
|
103
|
+
CommunicationChannel,
|
|
104
|
+
{
|
|
105
|
+
providerKey: 'gmail',
|
|
106
|
+
externalIdentifier: payload.emailAddress,
|
|
107
|
+
isActive: true,
|
|
108
|
+
deletedAt: null,
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
if (channels.length === 0) {
|
|
112
|
+
// Return 204 anyway — see route header comment.
|
|
113
|
+
return new NextResponse(null, { status: 204 })
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const queue = getCommunicationChannelsQueue(
|
|
117
|
+
COMMUNICATION_CHANNELS_QUEUES.gmailHistorySync,
|
|
118
|
+
)
|
|
119
|
+
for (const channel of channels) {
|
|
120
|
+
const job: GmailHistorySyncJobPayload = {
|
|
121
|
+
channelId: channel.id,
|
|
122
|
+
scope: {
|
|
123
|
+
tenantId: channel.tenantId,
|
|
124
|
+
organizationId: channel.organizationId ?? null,
|
|
125
|
+
},
|
|
126
|
+
notification: {
|
|
127
|
+
emailAddress: payload.emailAddress,
|
|
128
|
+
historyId: String(payload.historyId),
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
await queue.enqueue(job as unknown as Record<string, unknown>)
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.error(
|
|
135
|
+
`[gmail-webhook] failed to enqueue history-sync for channel ${channel.id}:`,
|
|
136
|
+
err,
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return new NextResponse(null, { status: 204 })
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export const openApi = {
|
|
145
|
+
tags: ['CommunicationChannels'],
|
|
146
|
+
methods: {
|
|
147
|
+
POST: {
|
|
148
|
+
summary: 'Gmail Pub/Sub push notification webhook (Spec C § Phase C2)',
|
|
149
|
+
tags: ['CommunicationChannels'],
|
|
150
|
+
responses: [
|
|
151
|
+
{ status: 204, description: 'Notification verified + history-sync job enqueued' },
|
|
152
|
+
{ status: 400, description: 'Body not a valid Pub/Sub envelope' },
|
|
153
|
+
{ status: 401, description: 'Invalid JWT or email claim' },
|
|
154
|
+
{ status: 403, description: 'Wrong audience' },
|
|
155
|
+
{ status: 503, description: 'Webhook not configured / Google certs unreachable' },
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export default POST
|