@open-mercato/core 0.6.8-develop.6984.1.f0cf23f0f6 → 0.6.8-develop.6985.1.fb93574faa
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/notification/index.js +6 -0
- package/dist/generated/entities/notification/index.js.map +2 -2
- package/dist/generated/entities/notification_preference/index.js +19 -0
- package/dist/generated/entities/notification_preference/index.js.map +7 -0
- package/dist/generated/entities/notification_type/index.js +21 -0
- package/dist/generated/entities/notification_type/index.js.map +7 -0
- package/dist/generated/entities/notification_type_override/index.js +17 -0
- package/dist/generated/entities/notification_type_override/index.js.map +7 -0
- package/dist/generated/entities/push_notification_delivery/index.js +41 -0
- package/dist/generated/entities/push_notification_delivery/index.js.map +7 -0
- package/dist/generated/entities/user_device/index.js +35 -0
- package/dist/generated/entities/user_device/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +12 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +72 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/api.js +2 -1
- package/dist/helpers/integration/api.js.map +2 -2
- package/dist/helpers/integration/appRoot.js +9 -0
- package/dist/helpers/integration/appRoot.js.map +7 -0
- package/dist/helpers/integration/dbFixtures.js +1 -4
- package/dist/helpers/integration/dbFixtures.js.map +2 -2
- package/dist/helpers/integration/pushFake.js +126 -0
- package/dist/helpers/integration/pushFake.js.map +7 -0
- package/dist/modules/auth/notifications.js +16 -0
- package/dist/modules/auth/notifications.js.map +2 -2
- package/dist/modules/business_rules/notifications.js +2 -0
- package/dist/modules/business_rules/notifications.js.map +2 -2
- package/dist/modules/catalog/notifications.js +2 -0
- package/dist/modules/catalog/notifications.js.map +2 -2
- package/dist/modules/communication_channels/acl.js +9 -0
- package/dist/modules/communication_channels/acl.js.map +2 -2
- package/dist/modules/communication_channels/api/delete/admin/channels/[id]/route.js +133 -0
- package/dist/modules/communication_channels/api/delete/admin/channels/[id]/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js +6 -2
- package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js.map +2 -2
- package/dist/modules/communication_channels/api/get/channels/[id]/route.js +6 -2
- package/dist/modules/communication_channels/api/get/channels/[id]/route.js.map +2 -2
- package/dist/modules/communication_channels/api/get/channels/route.js +2 -1
- package/dist/modules/communication_channels/api/get/channels/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +10 -5
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +29 -1
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js +157 -0
- package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js.map +7 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +8 -1
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +2 -2
- package/dist/modules/communication_channels/commands/admin-delete-channel.js +141 -0
- package/dist/modules/communication_channels/commands/admin-delete-channel.js.map +7 -0
- package/dist/modules/communication_channels/commands/connect-credential-channel.js +25 -8
- package/dist/modules/communication_channels/commands/connect-credential-channel.js.map +2 -2
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js +2 -2
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
- package/dist/modules/communication_channels/commands/push-register.js +1 -1
- package/dist/modules/communication_channels/commands/push-register.js.map +2 -2
- package/dist/modules/communication_channels/commands/push-unregister.js +4 -3
- package/dist/modules/communication_channels/commands/push-unregister.js.map +2 -2
- package/dist/modules/communication_channels/data/entities.js +4 -0
- package/dist/modules/communication_channels/data/entities.js.map +2 -2
- package/dist/modules/communication_channels/data/validators.js +2 -1
- package/dist/modules/communication_channels/data/validators.js.map +2 -2
- package/dist/modules/communication_channels/lib/access-control.js +11 -1
- package/dist/modules/communication_channels/lib/access-control.js.map +2 -2
- package/dist/modules/communication_channels/lib/connect-channel.js +20 -12
- package/dist/modules/communication_channels/lib/connect-channel.js.map +2 -2
- package/dist/modules/communication_channels/lib/push-adapter.js +68 -0
- package/dist/modules/communication_channels/lib/push-adapter.js.map +7 -0
- package/dist/modules/communication_channels/lib/push-capabilities.js +31 -0
- package/dist/modules/communication_channels/lib/push-capabilities.js.map +7 -0
- package/dist/modules/communication_channels/lib/push-connect-error.js +20 -0
- package/dist/modules/communication_channels/lib/push-connect-error.js.map +7 -0
- package/dist/modules/communication_channels/lib/push-credential-errors.js +41 -0
- package/dist/modules/communication_channels/lib/push-credential-errors.js.map +7 -0
- package/dist/modules/communication_channels/lib/push-envelope.js +46 -0
- package/dist/modules/communication_channels/lib/push-envelope.js.map +7 -0
- package/dist/modules/communication_channels/lib/refcounted-client-cache.js +64 -0
- package/dist/modules/communication_channels/lib/refcounted-client-cache.js.map +7 -0
- package/dist/modules/communication_channels/migrations/Migration20260703123630_communication_channels.js +13 -0
- package/dist/modules/communication_channels/migrations/Migration20260703123630_communication_channels.js.map +7 -0
- package/dist/modules/communication_channels/notifications.js +4 -0
- package/dist/modules/communication_channels/notifications.js.map +2 -2
- package/dist/modules/communication_channels/setup.js +2 -0
- package/dist/modules/communication_channels/setup.js.map +2 -2
- package/dist/modules/configs/lib/upgrade-actions.js +21 -0
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/customer_accounts/notifications.js +12 -0
- package/dist/modules/customer_accounts/notifications.js.map +2 -2
- package/dist/modules/customers/notifications.js +4 -0
- package/dist/modules/customers/notifications.js.map +2 -2
- package/dist/modules/devices/acl.js +11 -0
- package/dist/modules/devices/acl.js.map +7 -0
- package/dist/modules/devices/api/[id]/route.js +167 -0
- package/dist/modules/devices/api/[id]/route.js.map +7 -0
- package/dist/modules/devices/api/admin/devices/[id]/route.js +219 -0
- package/dist/modules/devices/api/admin/devices/[id]/route.js.map +7 -0
- package/dist/modules/devices/api/admin/devices/route.js +140 -0
- package/dist/modules/devices/api/admin/devices/route.js.map +7 -0
- package/dist/modules/devices/api/auth.js +9 -0
- package/dist/modules/devices/api/auth.js.map +7 -0
- package/dist/modules/devices/api/deviceList.js +53 -0
- package/dist/modules/devices/api/deviceList.js.map +7 -0
- package/dist/modules/devices/api/deviceOps.js +118 -0
- package/dist/modules/devices/api/deviceOps.js.map +7 -0
- package/dist/modules/devices/api/openapi.js +19 -0
- package/dist/modules/devices/api/openapi.js.map +7 -0
- package/dist/modules/devices/api/route.js +143 -0
- package/dist/modules/devices/api/route.js.map +7 -0
- package/dist/modules/devices/backend/devices/[id]/page.js +135 -0
- package/dist/modules/devices/backend/devices/[id]/page.js.map +7 -0
- package/dist/modules/devices/backend/devices/[id]/page.meta.js +16 -0
- package/dist/modules/devices/backend/devices/[id]/page.meta.js.map +7 -0
- package/dist/modules/devices/backend/devices/create/page.js +68 -0
- package/dist/modules/devices/backend/devices/create/page.js.map +7 -0
- package/dist/modules/devices/backend/devices/create/page.meta.js +16 -0
- package/dist/modules/devices/backend/devices/create/page.meta.js.map +7 -0
- package/dist/modules/devices/backend/devices/page.js +235 -0
- package/dist/modules/devices/backend/devices/page.js.map +7 -0
- package/dist/modules/devices/backend/devices/page.meta.js +23 -0
- package/dist/modules/devices/backend/devices/page.meta.js.map +7 -0
- package/dist/modules/devices/commands/deactivate.js +107 -0
- package/dist/modules/devices/commands/deactivate.js.map +7 -0
- package/dist/modules/devices/commands/index.js +4 -0
- package/dist/modules/devices/commands/index.js.map +7 -0
- package/dist/modules/devices/commands/register.js +183 -0
- package/dist/modules/devices/commands/register.js.map +7 -0
- package/dist/modules/devices/commands/shared.js +74 -0
- package/dist/modules/devices/commands/shared.js.map +7 -0
- package/dist/modules/devices/commands/update.js +126 -0
- package/dist/modules/devices/commands/update.js.map +7 -0
- package/dist/modules/devices/data/entities.js +83 -0
- package/dist/modules/devices/data/entities.js.map +7 -0
- package/dist/modules/devices/data/validators.js +49 -0
- package/dist/modules/devices/data/validators.js.map +7 -0
- package/dist/modules/devices/di.js +11 -0
- package/dist/modules/devices/di.js.map +7 -0
- package/dist/modules/devices/encryption.js +12 -0
- package/dist/modules/devices/encryption.js.map +7 -0
- package/dist/modules/devices/events.js +19 -0
- package/dist/modules/devices/events.js.map +7 -0
- package/dist/modules/devices/index.js +17 -0
- package/dist/modules/devices/index.js.map +7 -0
- package/dist/modules/devices/lib/operationMetadata.js +20 -0
- package/dist/modules/devices/lib/operationMetadata.js.map +7 -0
- package/dist/modules/devices/migrations/Migration20260602120000.js +15 -0
- package/dist/modules/devices/migrations/Migration20260602120000.js.map +7 -0
- package/dist/modules/devices/migrations/Migration20260630120000.js +15 -0
- package/dist/modules/devices/migrations/Migration20260630120000.js.map +7 -0
- package/dist/modules/devices/migrations/Migration20260701120000.js +13 -0
- package/dist/modules/devices/migrations/Migration20260701120000.js.map +7 -0
- package/dist/modules/devices/migrations/Migration20260721120000.js +15 -0
- package/dist/modules/devices/migrations/Migration20260721120000.js.map +7 -0
- package/dist/modules/devices/setup.js +13 -0
- package/dist/modules/devices/setup.js.map +7 -0
- package/dist/modules/entities/cli.js +8 -3
- package/dist/modules/entities/cli.js.map +2 -2
- package/dist/modules/entities/migrations/Migration20260722120000.js +28 -0
- package/dist/modules/entities/migrations/Migration20260722120000.js.map +7 -0
- package/dist/modules/inbox_ops/notifications.js +2 -0
- package/dist/modules/inbox_ops/notifications.js.map +2 -2
- package/dist/modules/messages/notifications.js +2 -0
- package/dist/modules/messages/notifications.js.map +2 -2
- package/dist/modules/notifications/acl.js +3 -1
- package/dist/modules/notifications/acl.js.map +2 -2
- package/dist/modules/notifications/api/admin/preferences/route.js +167 -0
- package/dist/modules/notifications/api/admin/preferences/route.js.map +7 -0
- package/dist/modules/notifications/api/channels/route.js +54 -0
- package/dist/modules/notifications/api/channels/route.js.map +7 -0
- package/dist/modules/notifications/api/preferences/route.js +126 -0
- package/dist/modules/notifications/api/preferences/route.js.map +7 -0
- package/dist/modules/notifications/api/route.js +8 -1
- package/dist/modules/notifications/api/route.js.map +2 -2
- package/dist/modules/notifications/api/types/[id]/channels/[channel]/route.js +187 -0
- package/dist/modules/notifications/api/types/[id]/channels/[channel]/route.js.map +7 -0
- package/dist/modules/notifications/api/types/route.js +225 -0
- package/dist/modules/notifications/api/types/route.js.map +7 -0
- package/dist/modules/notifications/api/unread-count/route.js +5 -1
- package/dist/modules/notifications/api/unread-count/route.js.map +2 -2
- package/dist/modules/notifications/backend/notifications/user-preferences/page.js +10 -0
- package/dist/modules/notifications/backend/notifications/user-preferences/page.js.map +7 -0
- package/dist/modules/notifications/backend/notifications/user-preferences/page.meta.js +28 -0
- package/dist/modules/notifications/backend/notifications/user-preferences/page.meta.js.map +7 -0
- package/dist/modules/notifications/backend/profile/notification-preferences/page.js +10 -0
- package/dist/modules/notifications/backend/profile/notification-preferences/page.js.map +7 -0
- package/dist/modules/notifications/backend/profile/notification-preferences/page.meta.js +33 -0
- package/dist/modules/notifications/backend/profile/notification-preferences/page.meta.js.map +7 -0
- package/dist/modules/notifications/data/entities.js +128 -1
- package/dist/modules/notifications/data/entities.js.map +2 -2
- package/dist/modules/notifications/data/validators.js +95 -2
- package/dist/modules/notifications/data/validators.js.map +2 -2
- package/dist/modules/notifications/di.js +7 -0
- package/dist/modules/notifications/di.js.map +2 -2
- package/dist/modules/notifications/events.js +17 -0
- package/dist/modules/notifications/events.js.map +2 -2
- package/dist/modules/notifications/frontend/NotificationPreferenceMatrix.js +190 -0
- package/dist/modules/notifications/frontend/NotificationPreferenceMatrix.js.map +7 -0
- package/dist/modules/notifications/frontend/NotificationPreferencesPageClient.js +124 -0
- package/dist/modules/notifications/frontend/NotificationPreferencesPageClient.js.map +7 -0
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +166 -1
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +2 -2
- package/dist/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.js +172 -0
- package/dist/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.js.map +7 -0
- package/dist/modules/notifications/generators.js +63 -0
- package/dist/modules/notifications/generators.js.map +7 -0
- package/dist/modules/notifications/lib/delivery-strategies-registry.js +19 -0
- package/dist/modules/notifications/lib/delivery-strategies-registry.js.map +7 -0
- package/dist/modules/notifications/lib/deliveryStrategies.js.map +2 -2
- package/dist/modules/notifications/lib/derive-category.js +8 -0
- package/dist/modules/notifications/lib/derive-category.js.map +7 -0
- package/dist/modules/notifications/lib/notification-channel-registry.js +35 -0
- package/dist/modules/notifications/lib/notification-channel-registry.js.map +7 -0
- package/dist/modules/notifications/lib/notification-type-registry.js +87 -0
- package/dist/modules/notifications/lib/notification-type-registry.js.map +7 -0
- package/dist/modules/notifications/lib/notificationCopy.js +14 -0
- package/dist/modules/notifications/lib/notificationCopy.js.map +7 -0
- package/dist/modules/notifications/lib/notificationFactory.js +4 -1
- package/dist/modules/notifications/lib/notificationFactory.js.map +2 -2
- package/dist/modules/notifications/lib/notificationMapper.js +2 -0
- package/dist/modules/notifications/lib/notificationMapper.js.map +2 -2
- package/dist/modules/notifications/lib/notificationPreferenceService.js +83 -0
- package/dist/modules/notifications/lib/notificationPreferenceService.js.map +7 -0
- package/dist/modules/notifications/lib/notificationService.js +83 -28
- package/dist/modules/notifications/lib/notificationService.js.map +2 -2
- package/dist/modules/notifications/lib/notificationVisibility.js +24 -0
- package/dist/modules/notifications/lib/notificationVisibility.js.map +7 -0
- package/dist/modules/notifications/lib/routeHelpers.js +11 -1
- package/dist/modules/notifications/lib/routeHelpers.js.map +2 -2
- package/dist/modules/notifications/lib/shouldDeliver.js +46 -0
- package/dist/modules/notifications/lib/shouldDeliver.js.map +7 -0
- package/dist/modules/notifications/lib/strategies/email-delivery-strategy.js +54 -0
- package/dist/modules/notifications/lib/strategies/email-delivery-strategy.js.map +7 -0
- package/dist/modules/notifications/lib/strategies/in-app-delivery-strategy.js +12 -0
- package/dist/modules/notifications/lib/strategies/in-app-delivery-strategy.js.map +7 -0
- package/dist/modules/notifications/lib/typeCatalogue.js +71 -0
- package/dist/modules/notifications/lib/typeCatalogue.js.map +7 -0
- package/dist/modules/notifications/lib/typeChannelSettings.js +12 -0
- package/dist/modules/notifications/lib/typeChannelSettings.js.map +7 -0
- package/dist/modules/notifications/lib/typeOverrides.js +19 -0
- package/dist/modules/notifications/lib/typeOverrides.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260625122947_notifications.js +14 -0
- package/dist/modules/notifications/migrations/Migration20260625122947_notifications.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260626120500_notifications_push_payload.js +15 -0
- package/dist/modules/notifications/migrations/Migration20260626120500_notifications_push_payload.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260626130000_notifications_non_opt_out.js +13 -0
- package/dist/modules/notifications/migrations/Migration20260626130000_notifications_non_opt_out.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260626140000_notifications_type_category_silent.js +15 -0
- package/dist/modules/notifications/migrations/Migration20260626140000_notifications_type_category_silent.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260706120000_notifications_channels.js +13 -0
- package/dist/modules/notifications/migrations/Migration20260706120000_notifications_channels.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260716123121_notifications.js +14 -0
- package/dist/modules/notifications/migrations/Migration20260716123121_notifications.js.map +7 -0
- package/dist/modules/notifications/notification-channels.js +26 -0
- package/dist/modules/notifications/notification-channels.js.map +7 -0
- package/dist/modules/notifications/notifications.delivery-strategies.js +12 -0
- package/dist/modules/notifications/notifications.delivery-strategies.js.map +7 -0
- package/dist/modules/notifications/setup.js +16 -1
- package/dist/modules/notifications/setup.js.map +2 -2
- package/dist/modules/notifications/subscribers/deliver-notification.js +66 -57
- package/dist/modules/notifications/subscribers/deliver-notification.js.map +2 -2
- package/dist/modules/notifications/subscribers/sync-notification-types.js +16 -0
- package/dist/modules/notifications/subscribers/sync-notification-types.js.map +7 -0
- package/dist/modules/notifications/widgets/injection/profile-preferences-menu/widget.js +23 -0
- package/dist/modules/notifications/widgets/injection/profile-preferences-menu/widget.js.map +7 -0
- package/dist/modules/notifications/widgets/injection-table.js +14 -0
- package/dist/modules/notifications/widgets/injection-table.js.map +7 -0
- package/dist/modules/push_notifications/acl.js +13 -0
- package/dist/modules/push_notifications/acl.js.map +7 -0
- package/dist/modules/push_notifications/api/custom-send/route.js +141 -0
- package/dist/modules/push_notifications/api/custom-send/route.js.map +7 -0
- package/dist/modules/push_notifications/api/deliveries/[id]/route.js +110 -0
- package/dist/modules/push_notifications/api/deliveries/[id]/route.js.map +7 -0
- package/dist/modules/push_notifications/api/deliveries/route.js +56 -0
- package/dist/modules/push_notifications/api/deliveries/route.js.map +7 -0
- package/dist/modules/push_notifications/api/openapi.js +19 -0
- package/dist/modules/push_notifications/api/openapi.js.map +7 -0
- package/dist/modules/push_notifications/backend/push_notifications/[id]/page.js +103 -0
- package/dist/modules/push_notifications/backend/push_notifications/[id]/page.js.map +7 -0
- package/dist/modules/push_notifications/backend/push_notifications/[id]/page.meta.js +13 -0
- package/dist/modules/push_notifications/backend/push_notifications/[id]/page.meta.js.map +7 -0
- package/dist/modules/push_notifications/backend/push_notifications/page.js +213 -0
- package/dist/modules/push_notifications/backend/push_notifications/page.js.map +7 -0
- package/dist/modules/push_notifications/backend/push_notifications/page.meta.js +23 -0
- package/dist/modules/push_notifications/backend/push_notifications/page.meta.js.map +7 -0
- package/dist/modules/push_notifications/backend/push_notifications/send/page.js +245 -0
- package/dist/modules/push_notifications/backend/push_notifications/send/page.js.map +7 -0
- package/dist/modules/push_notifications/backend/push_notifications/send/page.meta.js +26 -0
- package/dist/modules/push_notifications/backend/push_notifications/send/page.meta.js.map +7 -0
- package/dist/modules/push_notifications/data/entities.js +92 -0
- package/dist/modules/push_notifications/data/entities.js.map +7 -0
- package/dist/modules/push_notifications/data/extensions.js +23 -0
- package/dist/modules/push_notifications/data/extensions.js.map +7 -0
- package/dist/modules/push_notifications/data/validators.js +89 -0
- package/dist/modules/push_notifications/data/validators.js.map +7 -0
- package/dist/modules/push_notifications/di.js +17 -0
- package/dist/modules/push_notifications/di.js.map +7 -0
- package/dist/modules/push_notifications/events.js +24 -0
- package/dist/modules/push_notifications/events.js.map +7 -0
- package/dist/modules/push_notifications/index.js +16 -0
- package/dist/modules/push_notifications/index.js.map +7 -0
- package/dist/modules/push_notifications/lib/fake-provider-recorder.js +80 -0
- package/dist/modules/push_notifications/lib/fake-provider-recorder.js.map +7 -0
- package/dist/modules/push_notifications/lib/push-delivery-strategy.js +54 -0
- package/dist/modules/push_notifications/lib/push-delivery-strategy.js.map +7 -0
- package/dist/modules/push_notifications/lib/push-delivery.js +297 -0
- package/dist/modules/push_notifications/lib/push-delivery.js.map +7 -0
- package/dist/modules/push_notifications/lib/push-fanout.js +138 -0
- package/dist/modules/push_notifications/lib/push-fanout.js.map +7 -0
- package/dist/modules/push_notifications/lib/push-health.js +32 -0
- package/dist/modules/push_notifications/lib/push-health.js.map +7 -0
- package/dist/modules/push_notifications/lib/push-reaper.js +87 -0
- package/dist/modules/push_notifications/lib/push-reaper.js.map +7 -0
- package/dist/modules/push_notifications/lib/push-receipt-reaper.js +136 -0
- package/dist/modules/push_notifications/lib/push-receipt-reaper.js.map +7 -0
- package/dist/modules/push_notifications/lib/push-stub-adapter.js +63 -0
- package/dist/modules/push_notifications/lib/push-stub-adapter.js.map +7 -0
- package/dist/modules/push_notifications/lib/queue.js +53 -0
- package/dist/modules/push_notifications/lib/queue.js.map +7 -0
- package/dist/modules/push_notifications/lib/reclaim-window.js +13 -0
- package/dist/modules/push_notifications/lib/reclaim-window.js.map +7 -0
- package/dist/modules/push_notifications/lib/send-custom-push.js +48 -0
- package/dist/modules/push_notifications/lib/send-custom-push.js.map +7 -0
- package/dist/modules/push_notifications/migrations/Migration20260625150049_push_notifications.js +16 -0
- package/dist/modules/push_notifications/migrations/Migration20260625150049_push_notifications.js.map +7 -0
- package/dist/modules/push_notifications/migrations/Migration20260626120000_push_notifications_silent.js +13 -0
- package/dist/modules/push_notifications/migrations/Migration20260626120000_push_notifications_silent.js.map +7 -0
- package/dist/modules/push_notifications/notifications.delivery-strategies.js +8 -0
- package/dist/modules/push_notifications/notifications.delivery-strategies.js.map +7 -0
- package/dist/modules/push_notifications/notifications.js +40 -0
- package/dist/modules/push_notifications/notifications.js.map +7 -0
- package/dist/modules/push_notifications/setup.js +52 -0
- package/dist/modules/push_notifications/setup.js.map +7 -0
- package/dist/modules/push_notifications/workers/reclaim-stuck.worker.js +57 -0
- package/dist/modules/push_notifications/workers/reclaim-stuck.worker.js.map +7 -0
- package/dist/modules/push_notifications/workers/send-push.worker.js +27 -0
- package/dist/modules/push_notifications/workers/send-push.worker.js.map +7 -0
- package/dist/modules/sales/notifications.js +8 -0
- package/dist/modules/sales/notifications.js.map +2 -2
- package/dist/modules/staff/notifications.js +6 -0
- package/dist/modules/staff/notifications.js.map +2 -2
- package/dist/modules/workflows/notifications.js +2 -0
- package/dist/modules/workflows/notifications.js.map +2 -2
- package/generated/entities/notification/index.ts +3 -0
- package/generated/entities/notification_preference/index.ts +8 -0
- package/generated/entities/notification_type/index.ts +9 -0
- package/generated/entities/notification_type_override/index.ts +7 -0
- package/generated/entities/push_notification_delivery/index.ts +19 -0
- package/generated/entities/user_device/index.ts +16 -0
- package/generated/entities.ids.generated.ts +12 -1
- package/generated/entity-fields-registry.ts +72 -0
- package/package.json +7 -7
- package/src/helpers/integration/api.ts +2 -1
- package/src/helpers/integration/appRoot.ts +13 -0
- package/src/helpers/integration/dbFixtures.ts +1 -5
- package/src/helpers/integration/pushFake.ts +219 -0
- package/src/modules/auth/i18n/de.json +1 -0
- package/src/modules/auth/i18n/en.json +1 -0
- package/src/modules/auth/i18n/es.json +1 -0
- package/src/modules/auth/i18n/ko.json +1 -0
- package/src/modules/auth/i18n/pl.json +1 -0
- package/src/modules/auth/notifications.ts +16 -0
- package/src/modules/business_rules/i18n/de.json +2 -1
- package/src/modules/business_rules/i18n/en.json +2 -1
- package/src/modules/business_rules/i18n/es.json +2 -1
- package/src/modules/business_rules/i18n/ko.json +2 -1
- package/src/modules/business_rules/i18n/pl.json +2 -1
- package/src/modules/business_rules/notifications.ts +2 -0
- package/src/modules/catalog/i18n/de.json +2 -1
- package/src/modules/catalog/i18n/en.json +2 -1
- package/src/modules/catalog/i18n/es.json +2 -1
- package/src/modules/catalog/i18n/ko.json +2 -1
- package/src/modules/catalog/i18n/pl.json +2 -1
- package/src/modules/catalog/notifications.ts +2 -0
- package/src/modules/communication_channels/acl.ts +9 -0
- package/src/modules/communication_channels/api/delete/admin/channels/[id]/route.ts +168 -0
- package/src/modules/communication_channels/api/get/channels/[id]/health/route.ts +6 -2
- package/src/modules/communication_channels/api/get/channels/[id]/route.ts +6 -2
- package/src/modules/communication_channels/api/get/channels/route.ts +2 -1
- package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +16 -5
- package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +37 -1
- package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts +175 -0
- package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +11 -1
- package/src/modules/communication_channels/commands/admin-delete-channel.ts +203 -0
- package/src/modules/communication_channels/commands/connect-credential-channel.ts +63 -15
- package/src/modules/communication_channels/commands/deliver-outbound-message.ts +10 -2
- package/src/modules/communication_channels/commands/push-register.ts +7 -1
- package/src/modules/communication_channels/commands/push-unregister.ts +14 -3
- package/src/modules/communication_channels/data/entities.ts +10 -0
- package/src/modules/communication_channels/data/validators.ts +1 -0
- package/src/modules/communication_channels/i18n/de.json +35 -1
- package/src/modules/communication_channels/i18n/en.json +35 -1
- package/src/modules/communication_channels/i18n/es.json +35 -1
- package/src/modules/communication_channels/i18n/ko.json +35 -1
- package/src/modules/communication_channels/i18n/pl.json +35 -1
- package/src/modules/communication_channels/lib/access-control.ts +42 -0
- package/src/modules/communication_channels/lib/adapter.ts +25 -0
- package/src/modules/communication_channels/lib/connect-channel.ts +61 -26
- package/src/modules/communication_channels/lib/push-adapter.ts +139 -0
- package/src/modules/communication_channels/lib/push-capabilities.ts +42 -0
- package/src/modules/communication_channels/lib/push-connect-error.ts +52 -0
- package/src/modules/communication_channels/lib/push-credential-errors.ts +58 -0
- package/src/modules/communication_channels/lib/push-envelope.ts +104 -0
- package/src/modules/communication_channels/lib/refcounted-client-cache.ts +123 -0
- package/src/modules/communication_channels/migrations/.snapshot-open-mercato.json +9 -0
- package/src/modules/communication_channels/migrations/Migration20260703123630_communication_channels.ts +20 -0
- package/src/modules/communication_channels/notifications.ts +4 -0
- package/src/modules/communication_channels/setup.ts +2 -0
- package/src/modules/configs/i18n/de.json +5 -1
- package/src/modules/configs/i18n/en.json +5 -1
- package/src/modules/configs/i18n/es.json +5 -1
- package/src/modules/configs/i18n/ko.json +5 -1
- package/src/modules/configs/i18n/pl.json +5 -1
- package/src/modules/configs/lib/upgrade-actions.ts +21 -0
- package/src/modules/customer_accounts/i18n/de.json +2 -1
- package/src/modules/customer_accounts/i18n/en.json +2 -1
- package/src/modules/customer_accounts/i18n/es.json +2 -1
- package/src/modules/customer_accounts/i18n/ko.json +2 -1
- package/src/modules/customer_accounts/i18n/pl.json +2 -1
- package/src/modules/customer_accounts/notifications.ts +12 -0
- package/src/modules/customers/i18n/de.json +2 -1
- package/src/modules/customers/i18n/en.json +2 -1
- package/src/modules/customers/i18n/es.json +2 -1
- package/src/modules/customers/i18n/ko.json +2 -1
- package/src/modules/customers/i18n/pl.json +2 -1
- package/src/modules/customers/notifications.ts +4 -0
- package/src/modules/devices/AGENTS.md +101 -0
- package/src/modules/devices/acl.ts +7 -0
- package/src/modules/devices/api/[id]/route.ts +180 -0
- package/src/modules/devices/api/admin/devices/[id]/route.ts +235 -0
- package/src/modules/devices/api/admin/devices/route.ts +160 -0
- package/src/modules/devices/api/auth.ts +13 -0
- package/src/modules/devices/api/deviceList.ts +54 -0
- package/src/modules/devices/api/deviceOps.ts +186 -0
- package/src/modules/devices/api/openapi.ts +23 -0
- package/src/modules/devices/api/route.ts +165 -0
- package/src/modules/devices/backend/devices/[id]/page.meta.ts +12 -0
- package/src/modules/devices/backend/devices/[id]/page.tsx +164 -0
- package/src/modules/devices/backend/devices/create/page.meta.ts +12 -0
- package/src/modules/devices/backend/devices/create/page.tsx +81 -0
- package/src/modules/devices/backend/devices/page.meta.ts +21 -0
- package/src/modules/devices/backend/devices/page.tsx +259 -0
- package/src/modules/devices/commands/deactivate.ts +126 -0
- package/src/modules/devices/commands/index.ts +3 -0
- package/src/modules/devices/commands/register.ts +199 -0
- package/src/modules/devices/commands/shared.ts +119 -0
- package/src/modules/devices/commands/update.ts +142 -0
- package/src/modules/devices/data/entities.ts +79 -0
- package/src/modules/devices/data/validators.ts +65 -0
- package/src/modules/devices/di.ts +9 -0
- package/src/modules/devices/encryption.ts +15 -0
- package/src/modules/devices/events.ts +16 -0
- package/src/modules/devices/i18n/de.json +45 -0
- package/src/modules/devices/i18n/en.json +45 -0
- package/src/modules/devices/i18n/es.json +45 -0
- package/src/modules/devices/i18n/ko.json +45 -0
- package/src/modules/devices/i18n/pl.json +45 -0
- package/src/modules/devices/index.ts +15 -0
- package/src/modules/devices/lib/operationMetadata.ts +35 -0
- package/src/modules/devices/migrations/.snapshot-open-mercato.json +200 -0
- package/src/modules/devices/migrations/Migration20260602120000.ts +15 -0
- package/src/modules/devices/migrations/Migration20260630120000.ts +20 -0
- package/src/modules/devices/migrations/Migration20260701120000.ts +13 -0
- package/src/modules/devices/migrations/Migration20260721120000.ts +18 -0
- package/src/modules/devices/setup.ts +11 -0
- package/src/modules/entities/cli.ts +16 -2
- package/src/modules/entities/migrations/Migration20260722120000.ts +38 -0
- package/src/modules/inbox_ops/i18n/de.json +2 -1
- package/src/modules/inbox_ops/i18n/en.json +2 -1
- package/src/modules/inbox_ops/i18n/es.json +2 -1
- package/src/modules/inbox_ops/i18n/ko.json +2 -1
- package/src/modules/inbox_ops/i18n/pl.json +2 -1
- package/src/modules/inbox_ops/notifications.ts +2 -0
- package/src/modules/messages/i18n/de.json +2 -1
- package/src/modules/messages/i18n/en.json +2 -1
- package/src/modules/messages/i18n/es.json +2 -1
- package/src/modules/messages/i18n/ko.json +2 -1
- package/src/modules/messages/i18n/pl.json +2 -1
- package/src/modules/messages/notifications.ts +2 -0
- package/src/modules/notifications/AGENTS.md +54 -0
- package/src/modules/notifications/acl.ts +2 -0
- package/src/modules/notifications/api/admin/preferences/route.ts +193 -0
- package/src/modules/notifications/api/channels/route.ts +54 -0
- package/src/modules/notifications/api/preferences/route.ts +131 -0
- package/src/modules/notifications/api/route.ts +8 -1
- package/src/modules/notifications/api/types/[id]/channels/[channel]/route.ts +226 -0
- package/src/modules/notifications/api/types/route.ts +239 -0
- package/src/modules/notifications/api/unread-count/route.ts +5 -1
- package/src/modules/notifications/backend/notifications/user-preferences/page.meta.ts +26 -0
- package/src/modules/notifications/backend/notifications/user-preferences/page.tsx +12 -0
- package/src/modules/notifications/backend/profile/notification-preferences/page.meta.ts +31 -0
- package/src/modules/notifications/backend/profile/notification-preferences/page.tsx +12 -0
- package/src/modules/notifications/data/entities.ts +144 -0
- package/src/modules/notifications/data/validators.ts +116 -0
- package/src/modules/notifications/di.ts +7 -0
- package/src/modules/notifications/events.ts +23 -0
- package/src/modules/notifications/frontend/NotificationPreferenceMatrix.tsx +287 -0
- package/src/modules/notifications/frontend/NotificationPreferencesPageClient.tsx +155 -0
- package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +273 -2
- package/src/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.tsx +214 -0
- package/src/modules/notifications/generators.ts +82 -0
- package/src/modules/notifications/i18n/de.json +43 -0
- package/src/modules/notifications/i18n/en.json +43 -0
- package/src/modules/notifications/i18n/es.json +43 -0
- package/src/modules/notifications/i18n/ko.json +43 -0
- package/src/modules/notifications/i18n/pl.json +44 -1
- package/src/modules/notifications/lib/delivery-strategies-registry.ts +30 -0
- package/src/modules/notifications/lib/deliveryStrategies.ts +40 -4
- package/src/modules/notifications/lib/derive-category.ts +15 -0
- package/src/modules/notifications/lib/notification-channel-registry.ts +59 -0
- package/src/modules/notifications/lib/notification-type-registry.ts +182 -0
- package/src/modules/notifications/lib/notificationCopy.ts +34 -0
- package/src/modules/notifications/lib/notificationFactory.ts +5 -1
- package/src/modules/notifications/lib/notificationMapper.ts +2 -0
- package/src/modules/notifications/lib/notificationPreferenceService.ts +138 -0
- package/src/modules/notifications/lib/notificationService.ts +129 -25
- package/src/modules/notifications/lib/notificationVisibility.ts +48 -0
- package/src/modules/notifications/lib/routeHelpers.ts +14 -1
- package/src/modules/notifications/lib/shouldDeliver.ts +112 -0
- package/src/modules/notifications/lib/strategies/email-delivery-strategy.ts +63 -0
- package/src/modules/notifications/lib/strategies/in-app-delivery-strategy.ts +21 -0
- package/src/modules/notifications/lib/typeCatalogue.ts +141 -0
- package/src/modules/notifications/lib/typeChannelSettings.ts +29 -0
- package/src/modules/notifications/lib/typeOverrides.ts +46 -0
- package/src/modules/notifications/migrations/.snapshot-open-mercato.json +874 -100
- package/src/modules/notifications/migrations/Migration20260625122947_notifications.ts +14 -0
- package/src/modules/notifications/migrations/Migration20260626120500_notifications_push_payload.ts +15 -0
- package/src/modules/notifications/migrations/Migration20260626130000_notifications_non_opt_out.ts +13 -0
- package/src/modules/notifications/migrations/Migration20260626140000_notifications_type_category_silent.ts +15 -0
- package/src/modules/notifications/migrations/Migration20260706120000_notifications_channels.ts +13 -0
- package/src/modules/notifications/migrations/Migration20260716123121_notifications.ts +14 -0
- package/src/modules/notifications/notification-channels.ts +30 -0
- package/src/modules/notifications/notifications.delivery-strategies.ts +15 -0
- package/src/modules/notifications/setup.ts +23 -1
- package/src/modules/notifications/subscribers/deliver-notification.ts +105 -71
- package/src/modules/notifications/subscribers/sync-notification-types.ts +18 -0
- package/src/modules/notifications/widgets/injection/profile-preferences-menu/widget.ts +35 -0
- package/src/modules/notifications/widgets/injection-table.ts +18 -0
- package/src/modules/push_notifications/AGENTS.md +92 -0
- package/src/modules/push_notifications/acl.ts +9 -0
- package/src/modules/push_notifications/api/custom-send/route.ts +175 -0
- package/src/modules/push_notifications/api/deliveries/[id]/route.ts +122 -0
- package/src/modules/push_notifications/api/deliveries/route.ts +58 -0
- package/src/modules/push_notifications/api/openapi.ts +22 -0
- package/src/modules/push_notifications/backend/push_notifications/[id]/page.meta.ts +9 -0
- package/src/modules/push_notifications/backend/push_notifications/[id]/page.tsx +149 -0
- package/src/modules/push_notifications/backend/push_notifications/page.meta.ts +21 -0
- package/src/modules/push_notifications/backend/push_notifications/page.tsx +239 -0
- package/src/modules/push_notifications/backend/push_notifications/send/page.meta.ts +24 -0
- package/src/modules/push_notifications/backend/push_notifications/send/page.tsx +327 -0
- package/src/modules/push_notifications/data/entities.ts +111 -0
- package/src/modules/push_notifications/data/extensions.ts +29 -0
- package/src/modules/push_notifications/data/validators.ts +102 -0
- package/src/modules/push_notifications/di.ts +20 -0
- package/src/modules/push_notifications/events.ts +21 -0
- package/src/modules/push_notifications/i18n/de.json +79 -0
- package/src/modules/push_notifications/i18n/en.json +79 -0
- package/src/modules/push_notifications/i18n/es.json +79 -0
- package/src/modules/push_notifications/i18n/ko.json +79 -0
- package/src/modules/push_notifications/i18n/pl.json +79 -0
- package/src/modules/push_notifications/index.ts +15 -0
- package/src/modules/push_notifications/lib/fake-provider-recorder.ts +154 -0
- package/src/modules/push_notifications/lib/push-delivery-strategy.ts +90 -0
- package/src/modules/push_notifications/lib/push-delivery.ts +455 -0
- package/src/modules/push_notifications/lib/push-fanout.ts +237 -0
- package/src/modules/push_notifications/lib/push-health.ts +47 -0
- package/src/modules/push_notifications/lib/push-reaper.ts +140 -0
- package/src/modules/push_notifications/lib/push-receipt-reaper.ts +210 -0
- package/src/modules/push_notifications/lib/push-stub-adapter.ts +80 -0
- package/src/modules/push_notifications/lib/queue.ts +74 -0
- package/src/modules/push_notifications/lib/reclaim-window.ts +16 -0
- package/src/modules/push_notifications/lib/send-custom-push.ts +100 -0
- package/src/modules/push_notifications/migrations/.snapshot-open-mercato.json +386 -0
- package/src/modules/push_notifications/migrations/Migration20260625150049_push_notifications.ts +16 -0
- package/src/modules/push_notifications/migrations/Migration20260626120000_push_notifications_silent.ts +13 -0
- package/src/modules/push_notifications/notifications.delivery-strategies.ts +8 -0
- package/src/modules/push_notifications/notifications.ts +50 -0
- package/src/modules/push_notifications/setup.ts +92 -0
- package/src/modules/push_notifications/workers/reclaim-stuck.worker.ts +78 -0
- package/src/modules/push_notifications/workers/send-push.worker.ts +34 -0
- package/src/modules/sales/i18n/de.json +1 -0
- package/src/modules/sales/i18n/en.json +1 -0
- package/src/modules/sales/i18n/es.json +1 -0
- package/src/modules/sales/i18n/ko.json +1 -0
- package/src/modules/sales/i18n/pl.json +1 -0
- package/src/modules/sales/notifications.ts +8 -0
- package/src/modules/staff/i18n/de.json +1 -0
- package/src/modules/staff/i18n/en.json +1 -0
- package/src/modules/staff/i18n/es.json +1 -0
- package/src/modules/staff/i18n/ko.json +1 -0
- package/src/modules/staff/i18n/pl.json +1 -0
- package/src/modules/staff/notifications.ts +6 -0
- package/src/modules/workflows/i18n/de.json +1 -0
- package/src/modules/workflows/i18n/en.json +1 -0
- package/src/modules/workflows/i18n/es.json +1 -0
- package/src/modules/workflows/i18n/ko.json +1 -0
- package/src/modules/workflows/i18n/pl.json +1 -0
- package/src/modules/workflows/notifications.ts +2 -0
|
@@ -719,5 +719,6 @@
|
|
|
719
719
|
"customers~catalog.errors.id_required": "Datensatzkennung ist erforderlich.",
|
|
720
720
|
"customers~catalog.errors.organization_required": "Organisationskontext ist erforderlich.",
|
|
721
721
|
"customers~catalog.errors.tenant_required": "Mandantenkontext ist erforderlich.",
|
|
722
|
-
"customers~catalog.nav.group": "Katalog"
|
|
722
|
+
"customers~catalog.nav.group": "Katalog",
|
|
723
|
+
"notifications.categories.catalog": "Katalog"
|
|
723
724
|
}
|
|
@@ -719,5 +719,6 @@
|
|
|
719
719
|
"customers~catalog.errors.id_required": "Record identifier is required.",
|
|
720
720
|
"customers~catalog.errors.organization_required": "Organization context is required.",
|
|
721
721
|
"customers~catalog.errors.tenant_required": "Tenant context is required.",
|
|
722
|
-
"customers~catalog.nav.group": "Catalog"
|
|
722
|
+
"customers~catalog.nav.group": "Catalog",
|
|
723
|
+
"notifications.categories.catalog": "Catalog"
|
|
723
724
|
}
|
|
@@ -719,5 +719,6 @@
|
|
|
719
719
|
"customers~catalog.errors.id_required": "El identificador del registro es obligatorio.",
|
|
720
720
|
"customers~catalog.errors.organization_required": "El contexto de organización es obligatorio.",
|
|
721
721
|
"customers~catalog.errors.tenant_required": "El contexto de tenant es obligatorio.",
|
|
722
|
-
"customers~catalog.nav.group": "Catálogo"
|
|
722
|
+
"customers~catalog.nav.group": "Catálogo",
|
|
723
|
+
"notifications.categories.catalog": "Catálogo"
|
|
723
724
|
}
|
|
@@ -719,5 +719,6 @@
|
|
|
719
719
|
"customers~catalog.errors.id_required": "레코드 식별자가 필요합니다.",
|
|
720
720
|
"customers~catalog.errors.organization_required": "조직 컨텍스트가 필요합니다.",
|
|
721
721
|
"customers~catalog.errors.tenant_required": "테넌트 컨텍스트가 필요합니다.",
|
|
722
|
-
"customers~catalog.nav.group": "카탈로그"
|
|
722
|
+
"customers~catalog.nav.group": "카탈로그",
|
|
723
|
+
"notifications.categories.catalog": "카탈로그"
|
|
723
724
|
}
|
|
@@ -719,5 +719,6 @@
|
|
|
719
719
|
"customers~catalog.errors.id_required": "Identyfikator rekordu jest wymagany.",
|
|
720
720
|
"customers~catalog.errors.organization_required": "Kontekst organizacji jest wymagany.",
|
|
721
721
|
"customers~catalog.errors.tenant_required": "Kontekst tenanta jest wymagany.",
|
|
722
|
-
"customers~catalog.nav.group": "Katalog"
|
|
722
|
+
"customers~catalog.nav.group": "Katalog",
|
|
723
|
+
"notifications.categories.catalog": "Katalog"
|
|
723
724
|
}
|
|
@@ -3,6 +3,8 @@ import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/no
|
|
|
3
3
|
export const notificationTypes: NotificationTypeDefinition[] = [
|
|
4
4
|
{
|
|
5
5
|
type: 'catalog.product.low_stock',
|
|
6
|
+
// Ships without push — operators re-enable it per type from the Notification Delivery settings.
|
|
7
|
+
channels: ['in_app', 'email'],
|
|
6
8
|
module: 'catalog',
|
|
7
9
|
titleKey: 'catalog.notifications.product.lowStock.title',
|
|
8
10
|
bodyKey: 'catalog.notifications.product.lowStock.body',
|
|
@@ -12,6 +12,15 @@ export const features = [
|
|
|
12
12
|
* Default-granted to all roles in `setup.ts`.
|
|
13
13
|
*/
|
|
14
14
|
{ id: 'communication_channels.connect_user_channel', title: 'Connect own communication channel', module: 'communication_channels' },
|
|
15
|
+
/**
|
|
16
|
+
* Connect a tenant-wide channel (`CommunicationChannel.user_id IS NULL`) that
|
|
17
|
+
* serves every user in the tenant — used for push providers (FCM/APNs/Expo)
|
|
18
|
+
* whose service account / signing key is shared tenant infrastructure. Gates
|
|
19
|
+
* `POST /channels/connect/tenant-credentials`. Admin/superadmin only: unlike
|
|
20
|
+
* `connect_user_channel` (everyone links their own mailbox), connecting shared
|
|
21
|
+
* push credentials is an administrative action.
|
|
22
|
+
*/
|
|
23
|
+
{ id: 'communication_channels.connect_tenant_channel', title: 'Connect tenant-wide communication channel', module: 'communication_channels' },
|
|
15
24
|
/**
|
|
16
25
|
* Reserved for a future v2 team-oversight capability. NOT consulted in v1:
|
|
17
26
|
* personal mailboxes (`CommunicationChannel.user_id` set) follow the strict
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
4
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
5
|
+
import type { CommandBus } from '@open-mercato/shared/lib/commands'
|
|
6
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
7
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
8
|
+
import { CommunicationChannel } from '../../../../../data/entities'
|
|
9
|
+
import {
|
|
10
|
+
ChannelAccessDeniedError,
|
|
11
|
+
assertCanManageChannel,
|
|
12
|
+
channelOrgScopeWhere,
|
|
13
|
+
} from '../../../../../lib/access-control'
|
|
14
|
+
import {
|
|
15
|
+
COMMUNICATION_CHANNELS_ADMIN_DELETE_CHANNEL_COMMAND_ID,
|
|
16
|
+
type AdminDeleteChannelInput,
|
|
17
|
+
type AdminDeleteChannelResult,
|
|
18
|
+
} from '../../../../../commands/admin-delete-channel'
|
|
19
|
+
import { validateRouteMutationGuard } from '../../../../../lib/route-mutation-guard'
|
|
20
|
+
|
|
21
|
+
type RbacServiceLike = {
|
|
22
|
+
loadAcl: (
|
|
23
|
+
userId: string,
|
|
24
|
+
scope: { tenantId: string | null; organizationId: string | null },
|
|
25
|
+
) => Promise<{ isSuperAdmin: boolean; features: string[]; organizations: string[] | null }>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Admin soft-delete for a TENANT-WIDE communication channel (`user_id IS NULL`).
|
|
30
|
+
*
|
|
31
|
+
* Companion to `/communication_channels/channels/[id]` (owner-only, per-user
|
|
32
|
+
* path). Tenant-wide channels — shared inboxes and the push providers
|
|
33
|
+
* FCM/APNs/Expo — have no owner, so the owner route rejects them; this route
|
|
34
|
+
* deletes them under `communication_channels.admin`. The channel is loaded
|
|
35
|
+
* org-agnostically so tenant-wide `organization_id IS NULL` rows resolve from
|
|
36
|
+
* any session org; per-user channels are masked as 404.
|
|
37
|
+
*/
|
|
38
|
+
export const metadata = {
|
|
39
|
+
path: '/communication_channels/admin/channels/[id]',
|
|
40
|
+
DELETE: {
|
|
41
|
+
requireAuth: true,
|
|
42
|
+
requireFeatures: ['communication_channels.admin'],
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type RouteContext = {
|
|
47
|
+
params: Promise<{ id: string }> | { id: string }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function DELETE(req: Request, context: RouteContext): Promise<Response> {
|
|
51
|
+
const { id } = await context.params
|
|
52
|
+
if (!z.string().uuid().safeParse(id).success) {
|
|
53
|
+
return NextResponse.json({ error: 'Invalid channel id' }, { status: 400 })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const auth = await getAuthFromRequest(req)
|
|
57
|
+
if (!auth?.sub || !auth?.tenantId) {
|
|
58
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const container = await createRequestContainer()
|
|
62
|
+
const em = (container.resolve('em') as EntityManager).fork()
|
|
63
|
+
const organizationId = (auth as { orgId?: string | null }).orgId ?? null
|
|
64
|
+
const dscope = { tenantId: auth.tenantId as string, organizationId }
|
|
65
|
+
|
|
66
|
+
const channel = await findOneWithDecryption(
|
|
67
|
+
em,
|
|
68
|
+
CommunicationChannel,
|
|
69
|
+
{
|
|
70
|
+
id,
|
|
71
|
+
tenantId: auth.tenantId as string,
|
|
72
|
+
...channelOrgScopeWhere(organizationId),
|
|
73
|
+
deletedAt: null,
|
|
74
|
+
},
|
|
75
|
+
undefined,
|
|
76
|
+
dscope,
|
|
77
|
+
)
|
|
78
|
+
// Mask a missing channel AND a per-user channel (which must go through the
|
|
79
|
+
// owner path) as 404 so admins can't probe personal-mailbox existence.
|
|
80
|
+
if (!channel || (channel as { userId?: string | null }).userId != null) {
|
|
81
|
+
return NextResponse.json({ error: 'Channel not found' }, { status: 404 })
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let userFeatures: string[] = []
|
|
85
|
+
try {
|
|
86
|
+
const rbac = container.resolve('rbacService') as RbacServiceLike
|
|
87
|
+
const acl = await rbac.loadAcl(auth.sub as string, {
|
|
88
|
+
tenantId: auth.tenantId as string,
|
|
89
|
+
organizationId,
|
|
90
|
+
})
|
|
91
|
+
userFeatures = acl?.isSuperAdmin ? ['*'] : Array.isArray(acl?.features) ? acl.features : []
|
|
92
|
+
} catch {
|
|
93
|
+
userFeatures = []
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
assertCanManageChannel(
|
|
97
|
+
{ userId: (channel as { userId?: string | null }).userId },
|
|
98
|
+
auth.sub as string,
|
|
99
|
+
userFeatures,
|
|
100
|
+
'communication_channels.manage',
|
|
101
|
+
)
|
|
102
|
+
} catch (err) {
|
|
103
|
+
if (err instanceof ChannelAccessDeniedError) {
|
|
104
|
+
return NextResponse.json({ error: 'Channel not found' }, { status: 404 })
|
|
105
|
+
}
|
|
106
|
+
throw err
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const guard = await validateRouteMutationGuard({
|
|
110
|
+
container,
|
|
111
|
+
req,
|
|
112
|
+
auth,
|
|
113
|
+
input: {
|
|
114
|
+
resourceKind: 'communication_channels.channel',
|
|
115
|
+
resourceId: id,
|
|
116
|
+
operation: 'delete',
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
if ('response' in guard) return guard.response
|
|
120
|
+
|
|
121
|
+
const commandBus = container.resolve('commandBus') as CommandBus
|
|
122
|
+
// Use the CHANNEL's own org (not the caller's session org) so push teardown
|
|
123
|
+
// and credential resolution key where the channel stored them.
|
|
124
|
+
const channelOrganizationId = (channel as { organizationId?: string | null }).organizationId ?? null
|
|
125
|
+
const input: AdminDeleteChannelInput = {
|
|
126
|
+
channelId: id,
|
|
127
|
+
scope: { tenantId: auth.tenantId as string, organizationId: channelOrganizationId },
|
|
128
|
+
}
|
|
129
|
+
const { result } = await commandBus.execute<AdminDeleteChannelInput, AdminDeleteChannelResult>(
|
|
130
|
+
COMMUNICATION_CHANNELS_ADMIN_DELETE_CHANNEL_COMMAND_ID,
|
|
131
|
+
{
|
|
132
|
+
input,
|
|
133
|
+
ctx: {
|
|
134
|
+
container,
|
|
135
|
+
auth: auth as never,
|
|
136
|
+
organizationScope: null,
|
|
137
|
+
selectedOrganizationId: organizationId,
|
|
138
|
+
organizationIds: organizationId ? [organizationId] : null,
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
// 'noop' (channel vanished between the load and the command) and
|
|
144
|
+
// 'not_tenant_wide' both map to 404 to avoid leaking existence.
|
|
145
|
+
if (result.status !== 'deleted') {
|
|
146
|
+
return NextResponse.json({ error: 'Channel not found' }, { status: 404 })
|
|
147
|
+
}
|
|
148
|
+
await guard.afterSuccess()
|
|
149
|
+
return new NextResponse(null, { status: 204 })
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export const openApi = {
|
|
153
|
+
tags: ['CommunicationChannels'],
|
|
154
|
+
methods: {
|
|
155
|
+
DELETE: {
|
|
156
|
+
summary: 'Admin delete (soft-delete) a tenant-wide communication channel',
|
|
157
|
+
tags: ['CommunicationChannels'],
|
|
158
|
+
responses: [
|
|
159
|
+
{ status: 204, description: 'Channel deleted' },
|
|
160
|
+
{ status: 400, description: 'Invalid channel id' },
|
|
161
|
+
{ status: 401, description: 'Unauthorized' },
|
|
162
|
+
{ status: 404, description: 'Channel not found or not a tenant-wide channel' },
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export default DELETE
|
|
@@ -8,7 +8,11 @@ import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/
|
|
|
8
8
|
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
9
9
|
import { resolveOrganizationScopeFilter } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'
|
|
10
10
|
import { CommunicationChannel, ExternalConversation, MessageChannelLink } from '../../../../../data/entities'
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
ChannelAccessDeniedError,
|
|
13
|
+
assertCanAccessChannel,
|
|
14
|
+
channelOrgScopeWhereFromFilter,
|
|
15
|
+
} from '../../../../../lib/access-control'
|
|
12
16
|
|
|
13
17
|
type RbacServiceLike = {
|
|
14
18
|
loadAcl: (
|
|
@@ -67,7 +71,7 @@ export async function GET(req: Request, context: RouteContext): Promise<Response
|
|
|
67
71
|
const channel = await findOneWithDecryption(
|
|
68
72
|
em,
|
|
69
73
|
CommunicationChannel,
|
|
70
|
-
{ id, tenantId: auth.tenantId, ...orgFilter
|
|
74
|
+
{ id, tenantId: auth.tenantId, ...channelOrgScopeWhereFromFilter(orgFilter), deletedAt: null },
|
|
71
75
|
undefined,
|
|
72
76
|
dscope,
|
|
73
77
|
)
|
|
@@ -7,7 +7,11 @@ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
|
7
7
|
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
8
8
|
import { resolveOrganizationScopeFilter } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'
|
|
9
9
|
import { CommunicationChannel } from '../../../../data/entities'
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
ChannelAccessDeniedError,
|
|
12
|
+
assertCanAccessChannel,
|
|
13
|
+
channelOrgScopeWhereFromFilter,
|
|
14
|
+
} from '../../../../lib/access-control'
|
|
11
15
|
|
|
12
16
|
type RbacServiceLike = {
|
|
13
17
|
loadAcl: (
|
|
@@ -50,7 +54,7 @@ export async function GET(req: Request, context: RouteContext): Promise<Response
|
|
|
50
54
|
{
|
|
51
55
|
id,
|
|
52
56
|
tenantId: auth.tenantId,
|
|
53
|
-
...orgFilter
|
|
57
|
+
...channelOrgScopeWhereFromFilter(orgFilter),
|
|
54
58
|
deletedAt: null,
|
|
55
59
|
},
|
|
56
60
|
undefined,
|
|
@@ -7,6 +7,7 @@ import { findAndCountWithDecryption } from '@open-mercato/shared/lib/encryption/
|
|
|
7
7
|
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
8
8
|
import { resolveOrganizationScopeFilter } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'
|
|
9
9
|
import { CommunicationChannel } from '../../../data/entities'
|
|
10
|
+
import { channelOrgScopeWhereFromFilter } from '../../../lib/access-control'
|
|
10
11
|
|
|
11
12
|
export const metadata = {
|
|
12
13
|
path: '/communication_channels/channels',
|
|
@@ -58,7 +59,7 @@ export async function GET(req: Request): Promise<Response> {
|
|
|
58
59
|
// one — admins included — can see another user's connected personal account.
|
|
59
60
|
const where: Record<string, unknown> = {
|
|
60
61
|
tenantId: auth.tenantId,
|
|
61
|
-
...orgFilter
|
|
62
|
+
...channelOrgScopeWhereFromFilter(orgFilter),
|
|
62
63
|
deletedAt: null,
|
|
63
64
|
userId: null,
|
|
64
65
|
}
|
|
@@ -7,7 +7,11 @@ import type { EntityManager } from '@mikro-orm/postgresql'
|
|
|
7
7
|
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
8
8
|
import { CommunicationChannel } from '../../../../../data/entities'
|
|
9
9
|
import { getChannelAdapter } from '../../../../../lib/adapter-registry-singleton'
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
ChannelAccessDeniedError,
|
|
12
|
+
assertCanManageChannel,
|
|
13
|
+
channelOrgScopeWhere,
|
|
14
|
+
} from '../../../../../lib/access-control'
|
|
11
15
|
import { refreshCredentialsIfNeeded } from '../../../../../lib/credential-refresh'
|
|
12
16
|
import { validateRouteMutationGuard } from '../../../../../lib/route-mutation-guard'
|
|
13
17
|
|
|
@@ -90,7 +94,7 @@ export async function POST(req: Request, context: RouteContext): Promise<Respons
|
|
|
90
94
|
{
|
|
91
95
|
id,
|
|
92
96
|
tenantId: auth.tenantId as string,
|
|
93
|
-
organizationId,
|
|
97
|
+
...channelOrgScopeWhere(organizationId),
|
|
94
98
|
deletedAt: null,
|
|
95
99
|
},
|
|
96
100
|
undefined,
|
|
@@ -99,6 +103,13 @@ export async function POST(req: Request, context: RouteContext): Promise<Respons
|
|
|
99
103
|
if (!channel) {
|
|
100
104
|
return NextResponse.json({ error: 'Channel not found' }, { status: 404 })
|
|
101
105
|
}
|
|
106
|
+
// Resolve credentials at the channel's OWN org, not the caller's selected org.
|
|
107
|
+
// Tenant-wide channels store `organization_id = NULL` and their credentials
|
|
108
|
+
// live at `organization_id = tenantId` (see connect-credential-channel.ts), so
|
|
109
|
+
// the read key must be `channel.organizationId ?? tenantId` — keying on the
|
|
110
|
+
// session org would resolve `{}` for a tenant-wide channel viewed from a
|
|
111
|
+
// non-null org.
|
|
112
|
+
const channelCredentialsOrg = channel.organizationId ?? (auth.tenantId as string)
|
|
102
113
|
// Load features via RBAC service so admin bypass (`communication_channels.admin`,
|
|
103
114
|
// wildcards, super-admin) is honoured. The `auth` object from
|
|
104
115
|
// `getAuthFromRequest` carries identity only — feature ACLs live in the RBAC
|
|
@@ -172,14 +183,14 @@ export async function POST(req: Request, context: RouteContext): Promise<Respons
|
|
|
172
183
|
(await credentialsService
|
|
173
184
|
.resolve(`channel_${channel.providerKey}`, {
|
|
174
185
|
tenantId: auth.tenantId as string,
|
|
175
|
-
organizationId:
|
|
186
|
+
organizationId: channelCredentialsOrg,
|
|
176
187
|
userId: channel.userId ?? null,
|
|
177
188
|
})
|
|
178
189
|
.catch(() => null)) ?? {}
|
|
179
190
|
}
|
|
180
191
|
const credentialScope = {
|
|
181
192
|
tenantId: auth.tenantId as string,
|
|
182
|
-
organizationId:
|
|
193
|
+
organizationId: channelCredentialsOrg,
|
|
183
194
|
userId: channel.userId ?? null,
|
|
184
195
|
}
|
|
185
196
|
const refreshed = await refreshCredentialsIfNeeded(
|
|
@@ -203,7 +214,7 @@ export async function POST(req: Request, context: RouteContext): Promise<Respons
|
|
|
203
214
|
credentials,
|
|
204
215
|
scope: {
|
|
205
216
|
tenantId: auth.tenantId as string,
|
|
206
|
-
organizationId:
|
|
217
|
+
organizationId: channelCredentialsOrg,
|
|
207
218
|
},
|
|
208
219
|
metadata: { to: body.to, subject: body.subject ?? 'Test send', testSend: true },
|
|
209
220
|
})
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type ConnectCredentialChannelInput,
|
|
10
10
|
type ConnectCredentialChannelResult,
|
|
11
11
|
} from '../../../../../commands/connect-credential-channel'
|
|
12
|
+
import type { ChannelAdapterRegistry } from '../../../../../lib/registry'
|
|
12
13
|
import { validateRouteMutationGuard } from '../../../../../lib/route-mutation-guard'
|
|
13
14
|
|
|
14
15
|
export const metadata = {
|
|
@@ -44,6 +45,24 @@ export async function POST(req: Request): Promise<Response> {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
const container = await createRequestContainer()
|
|
48
|
+
|
|
49
|
+
// Tenant-scoped providers (push: FCM/APNs/Expo) must go through the admin-gated
|
|
50
|
+
// tenant route, not this per-user one (granted to manager/employee via
|
|
51
|
+
// `connect_user_channel`). Reject them here so a non-admin can't mint a
|
|
52
|
+
// privileged tenant-wide channel. The command enforces the same invariant
|
|
53
|
+
// (`wrong_scope_for_route`) as defense-in-depth.
|
|
54
|
+
const adapterRegistry = container.resolve('channelAdapterRegistry') as ChannelAdapterRegistry
|
|
55
|
+
const earlyAdapter = adapterRegistry.get(body.providerKey)
|
|
56
|
+
if (earlyAdapter && earlyAdapter.channelScope === 'tenant') {
|
|
57
|
+
return NextResponse.json(
|
|
58
|
+
{
|
|
59
|
+
error: 'This provider is connected tenant-wide by an administrator, not per-user.',
|
|
60
|
+
code: 'provider_is_tenant_scoped',
|
|
61
|
+
},
|
|
62
|
+
{ status: 403 },
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
47
66
|
const guard = await validateRouteMutationGuard({
|
|
48
67
|
container,
|
|
49
68
|
req,
|
|
@@ -93,8 +112,24 @@ export async function POST(req: Request): Promise<Response> {
|
|
|
93
112
|
if (result.status === 'no_adapter') {
|
|
94
113
|
return NextResponse.json({ error: result.reason }, { status: 404 })
|
|
95
114
|
}
|
|
115
|
+
if (result.status === 'wrong_scope_for_route') {
|
|
116
|
+
return NextResponse.json(
|
|
117
|
+
{
|
|
118
|
+
error: 'This provider is connected tenant-wide by an administrator, not per-user.',
|
|
119
|
+
code: 'provider_is_tenant_scoped',
|
|
120
|
+
},
|
|
121
|
+
{ status: 403 },
|
|
122
|
+
)
|
|
123
|
+
}
|
|
96
124
|
if (result.status === 'validation_failed') {
|
|
97
|
-
return NextResponse.json(
|
|
125
|
+
return NextResponse.json(
|
|
126
|
+
{
|
|
127
|
+
error: 'Credential validation failed',
|
|
128
|
+
fieldErrors: result.errors,
|
|
129
|
+
...(result.errorCodes ? { fieldErrorCodes: result.errorCodes } : {}),
|
|
130
|
+
},
|
|
131
|
+
{ status: 422 },
|
|
132
|
+
)
|
|
98
133
|
}
|
|
99
134
|
if (result.status === 'duplicate_mailbox') {
|
|
100
135
|
return NextResponse.json(
|
|
@@ -124,6 +159,7 @@ export const openApi = {
|
|
|
124
159
|
responses: [
|
|
125
160
|
{ status: 201, description: 'Channel connected' },
|
|
126
161
|
{ status: 401, description: 'Unauthorized' },
|
|
162
|
+
{ status: 403, description: 'Provider is tenant-scoped — use the tenant connect route' },
|
|
127
163
|
{ status: 404, description: 'Unknown provider' },
|
|
128
164
|
{ status: 409, description: 'Mailbox already connected via another provider' },
|
|
129
165
|
{ status: 422, description: 'Invalid body or credential validation failed' },
|
package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
4
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
5
|
+
import type { CommandBus } from '@open-mercato/shared/lib/commands'
|
|
6
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
7
|
+
import {
|
|
8
|
+
COMMUNICATION_CHANNELS_CONNECT_CREDENTIAL_CHANNEL_COMMAND_ID,
|
|
9
|
+
type ConnectCredentialChannelInput,
|
|
10
|
+
type ConnectCredentialChannelResult,
|
|
11
|
+
} from '../../../../../commands/connect-credential-channel'
|
|
12
|
+
import type { ChannelAdapterRegistry } from '../../../../../lib/registry'
|
|
13
|
+
import { validateRouteMutationGuard } from '../../../../../lib/route-mutation-guard'
|
|
14
|
+
|
|
15
|
+
export const metadata = {
|
|
16
|
+
path: '/communication_channels/channels/connect/tenant-credentials',
|
|
17
|
+
POST: {
|
|
18
|
+
requireAuth: true,
|
|
19
|
+
requireFeatures: ['communication_channels.connect_tenant_channel'],
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const bodySchema = z.object({
|
|
24
|
+
providerKey: z.string().min(1).max(64),
|
|
25
|
+
displayName: z.string().min(1).max(255),
|
|
26
|
+
credentials: z.record(z.string(), z.unknown()),
|
|
27
|
+
pollIntervalSeconds: z.number().int().positive().max(86_400).optional(),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export async function POST(req: Request): Promise<Response> {
|
|
31
|
+
const auth = await getAuthFromRequest(req)
|
|
32
|
+
if (!auth?.sub || !auth?.tenantId) {
|
|
33
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let body: z.infer<typeof bodySchema>
|
|
37
|
+
try {
|
|
38
|
+
const json = await readJsonSafe(req, null)
|
|
39
|
+
body = bodySchema.parse(json)
|
|
40
|
+
} catch (err) {
|
|
41
|
+
return NextResponse.json(
|
|
42
|
+
{ error: err instanceof Error ? err.message : 'Invalid request body' },
|
|
43
|
+
{ status: 422 },
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const container = await createRequestContainer()
|
|
48
|
+
|
|
49
|
+
// Reject providers that are NOT tenant-scoped so an admin can't force a
|
|
50
|
+
// per-user provider (Gmail/IMAP) into a shared tenant-wide channel. Push
|
|
51
|
+
// providers (FCM/APNs/Expo) declare `channelScope: 'tenant'` on their adapter.
|
|
52
|
+
const adapterRegistry = container.resolve('channelAdapterRegistry') as ChannelAdapterRegistry
|
|
53
|
+
const adapter = adapterRegistry.get(body.providerKey)
|
|
54
|
+
if (!adapter) {
|
|
55
|
+
return NextResponse.json(
|
|
56
|
+
{ error: `No adapter registered for provider '${body.providerKey}'` },
|
|
57
|
+
{ status: 404 },
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
if (adapter.channelScope !== 'tenant') {
|
|
61
|
+
return NextResponse.json(
|
|
62
|
+
{
|
|
63
|
+
error: 'This provider connects per-user channels; use the personal channel connect flow instead.',
|
|
64
|
+
code: 'provider_not_tenant_scoped',
|
|
65
|
+
},
|
|
66
|
+
{ status: 400 },
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const guard = await validateRouteMutationGuard({
|
|
71
|
+
container,
|
|
72
|
+
req,
|
|
73
|
+
auth,
|
|
74
|
+
input: {
|
|
75
|
+
resourceKind: 'communication_channels.channel',
|
|
76
|
+
resourceId: `new:${body.providerKey}`,
|
|
77
|
+
operation: 'create',
|
|
78
|
+
mutationPayload: {
|
|
79
|
+
providerKey: body.providerKey,
|
|
80
|
+
displayName: body.displayName,
|
|
81
|
+
pollIntervalSeconds: body.pollIntervalSeconds,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
if ('response' in guard) return guard.response
|
|
86
|
+
|
|
87
|
+
const commandBus = container.resolve('commandBus') as CommandBus
|
|
88
|
+
const orgId = (auth as { orgId?: string | null }).orgId ?? null
|
|
89
|
+
|
|
90
|
+
const input: ConnectCredentialChannelInput = {
|
|
91
|
+
providerKey: body.providerKey,
|
|
92
|
+
displayName: body.displayName,
|
|
93
|
+
credentials: body.credentials,
|
|
94
|
+
pollIntervalSeconds: body.pollIntervalSeconds,
|
|
95
|
+
// Tenant-wide: no owning user. The command re-derives the effective scope
|
|
96
|
+
// from the adapter, so the channel + credentials are stored with user_id NULL.
|
|
97
|
+
userId: null,
|
|
98
|
+
scope: {
|
|
99
|
+
tenantId: auth.tenantId as string,
|
|
100
|
+
organizationId: orgId,
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
const { result } = await commandBus.execute<
|
|
104
|
+
ConnectCredentialChannelInput,
|
|
105
|
+
ConnectCredentialChannelResult
|
|
106
|
+
>(COMMUNICATION_CHANNELS_CONNECT_CREDENTIAL_CHANNEL_COMMAND_ID, {
|
|
107
|
+
input,
|
|
108
|
+
ctx: {
|
|
109
|
+
container,
|
|
110
|
+
auth: auth as never,
|
|
111
|
+
organizationScope: null,
|
|
112
|
+
selectedOrganizationId: orgId,
|
|
113
|
+
organizationIds: orgId ? [orgId] : null,
|
|
114
|
+
},
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
if (result.status === 'no_adapter') {
|
|
118
|
+
return NextResponse.json({ error: result.reason }, { status: 404 })
|
|
119
|
+
}
|
|
120
|
+
if (result.status === 'wrong_scope_for_route') {
|
|
121
|
+
// Unreachable in practice — this route always dispatches with userId: null —
|
|
122
|
+
// but handle it so the result union narrows and the invariant is explicit.
|
|
123
|
+
return NextResponse.json(
|
|
124
|
+
{ error: 'Unexpected per-user scope on the tenant connect route.', code: 'wrong_scope_for_route' },
|
|
125
|
+
{ status: 500 },
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
if (result.status === 'validation_failed') {
|
|
129
|
+
// `fieldErrors` stays English for API consumers and logs; `fieldErrorCodes`
|
|
130
|
+
// lets the connect widget render a localized message per field.
|
|
131
|
+
return NextResponse.json(
|
|
132
|
+
{
|
|
133
|
+
error: 'Credential validation failed',
|
|
134
|
+
fieldErrors: result.errors,
|
|
135
|
+
...(result.errorCodes ? { fieldErrorCodes: result.errorCodes } : {}),
|
|
136
|
+
},
|
|
137
|
+
{ status: 422 },
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
if (result.status === 'duplicate_mailbox') {
|
|
141
|
+
return NextResponse.json(
|
|
142
|
+
{
|
|
143
|
+
error: `This mailbox is already connected via "${result.existingProviderKey}". Disconnect it there before connecting it again with a different provider.`,
|
|
144
|
+
code: 'mailbox_already_connected',
|
|
145
|
+
},
|
|
146
|
+
{ status: 409 },
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
await guard.afterSuccess()
|
|
150
|
+
return NextResponse.json(
|
|
151
|
+
{
|
|
152
|
+
channelId: result.channelId,
|
|
153
|
+
externalIdentifier: result.externalIdentifier,
|
|
154
|
+
},
|
|
155
|
+
{ status: 201 },
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export const openApi = {
|
|
160
|
+
tags: ['CommunicationChannels'],
|
|
161
|
+
methods: {
|
|
162
|
+
POST: {
|
|
163
|
+
summary: 'Connect a tenant-wide credential-based channel (push: FCM/APNs/Expo)',
|
|
164
|
+
tags: ['CommunicationChannels'],
|
|
165
|
+
responses: [
|
|
166
|
+
{ status: 201, description: 'Channel connected' },
|
|
167
|
+
{ status: 400, description: 'Provider is not tenant-scoped' },
|
|
168
|
+
{ status: 401, description: 'Unauthorized' },
|
|
169
|
+
{ status: 404, description: 'Unknown provider' },
|
|
170
|
+
{ status: 422, description: 'Invalid body or credential validation failed' },
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
}
|
|
175
|
+
export default POST
|
|
@@ -34,8 +34,17 @@ export default function ChannelsListPage() {
|
|
|
34
34
|
const [page, setPage] = React.useState(1)
|
|
35
35
|
const [isLoading, setIsLoading] = React.useState(true)
|
|
36
36
|
const [errorMessage, setErrorMessage] = React.useState<string | null>(null)
|
|
37
|
+
// Bumped by injected connect widgets (FCM/APNs/Expo) so a freshly connected
|
|
38
|
+
// tenant-wide channel appears without a manual page reload.
|
|
39
|
+
const [reloadToken, setReloadToken] = React.useState(0)
|
|
37
40
|
const pageSize = 50
|
|
38
41
|
|
|
42
|
+
const reload = React.useCallback(() => setReloadToken((token) => token + 1), [])
|
|
43
|
+
const injectionContext = React.useMemo(
|
|
44
|
+
() => ({ tableId: extensionPoints.hosts.channelsTable.tableId, reload }),
|
|
45
|
+
[reload],
|
|
46
|
+
)
|
|
47
|
+
|
|
39
48
|
React.useEffect(() => {
|
|
40
49
|
let cancelled = false
|
|
41
50
|
async function load() {
|
|
@@ -70,7 +79,7 @@ export default function ChannelsListPage() {
|
|
|
70
79
|
return () => {
|
|
71
80
|
cancelled = true
|
|
72
81
|
}
|
|
73
|
-
}, [page, t])
|
|
82
|
+
}, [page, reloadToken, t])
|
|
74
83
|
|
|
75
84
|
const columns = React.useMemo<ColumnDef<ChannelRow>[]>(
|
|
76
85
|
() => [
|
|
@@ -116,6 +125,7 @@ export default function ChannelsListPage() {
|
|
|
116
125
|
<DataTable<ChannelRow>
|
|
117
126
|
title={t('communication_channels.nav.title', 'Communication Channels')}
|
|
118
127
|
extensionTableId={extensionPoints.hosts.channelsTable.tableId}
|
|
128
|
+
injectionContext={injectionContext}
|
|
119
129
|
columns={columns}
|
|
120
130
|
data={rows}
|
|
121
131
|
isLoading={isLoading}
|