@open-mercato/core 0.6.8-develop.6983.1.e4f794099e → 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
|
@@ -59,7 +59,8 @@ async function getAuthToken(request, roleOrEmail = "admin", password) {
|
|
|
59
59
|
async function apiRequest(request, method, path, options) {
|
|
60
60
|
const headers = {
|
|
61
61
|
Authorization: `Bearer ${options.token}`,
|
|
62
|
-
"Content-Type": "application/json"
|
|
62
|
+
"Content-Type": "application/json",
|
|
63
|
+
...options.headers ?? {}
|
|
63
64
|
};
|
|
64
65
|
const timeout = options.timeout ?? 3e4;
|
|
65
66
|
let lastError = null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/helpers/integration/api.ts"],
|
|
4
|
-
"sourcesContent": ["import { type APIRequestContext } from '@playwright/test';\nimport { DEFAULT_CREDENTIALS, type Role } from './auth';\n\nconst BASE_URL = process.env.BASE_URL?.trim() || null;\n\nfunction resolveUrl(path: string): string {\n return BASE_URL ? `${BASE_URL}${path}` : path;\n}\n\n// Cached tokens per credential to dodge the login rate limit\n// (5 attempts/60s per email). Tokens are reused across tests in the same\n// Playwright worker; each worker still mints its own.\nconst tokenCache = new Map<string, { token: string; mintedAt: number }>();\nconst TOKEN_TTL_MS = 45 * 60 * 1000; // 45 min; well under the default 2h session TTL.\n\n/**\n * Drops cached tokens so the next getAuthToken() mints a fresh session.\n *\n * A cached token points at a `sessions` row. Anything that deletes those rows \u2014\n * deactivating the user, a password change, an explicit session purge \u2014 leaves the\n * cached token resolving to 401 for the rest of the TTL, and `apiRequest` neither\n * retries nor re-mints. A spec that revokes sessions for an account other specs also\n * use MUST call this afterwards.\n */\nexport function clearAuthTokenCache(): void {\n tokenCache.clear();\n}\n\nexport async function getAuthToken(\n request: APIRequestContext,\n roleOrEmail: Role | string = 'admin',\n password?: string,\n): Promise<string> {\n const role = roleOrEmail in DEFAULT_CREDENTIALS ? (roleOrEmail as Role) : null;\n const credentialAttempts: Array<{ email: string; password: string }> = [];\n\n if (role) {\n const configured = DEFAULT_CREDENTIALS[role];\n credentialAttempts.push({ email: configured.email, password: password ?? configured.password });\n if (!password) {\n credentialAttempts.push({ email: `${role}@acme.com`, password: 'secret' });\n }\n } else {\n credentialAttempts.push({ email: roleOrEmail, password: password ?? 'secret' });\n }\n\n const cacheKey = credentialAttempts\n .map((entry) => `${entry.email}:${entry.password}`)\n .join('|');\n const cached = tokenCache.get(cacheKey);\n if (cached && Date.now() - cached.mintedAt < TOKEN_TTL_MS) {\n return cached.token;\n }\n\n let lastStatus = 0;\n\n for (const attempt of credentialAttempts) {\n const form = new URLSearchParams();\n form.set('email', attempt.email);\n form.set('password', attempt.password);\n\n // Retry on 429 (auth rate limit kicks in after ~25-30 rapid attempts from\n // the same test run). Capped exponential backoff: 1s, 2s, 4s; 3 retries.\n for (let retry = 0; retry < 4; retry += 1) {\n const response = await request.post(resolveUrl('/api/auth/login'), {\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n },\n data: form.toString(),\n });\n\n const raw = await response.text();\n let body: Record<string, unknown> | null = null;\n try {\n body = raw ? (JSON.parse(raw) as Record<string, unknown>) : null;\n } catch {\n body = null;\n }\n\n lastStatus = response.status();\n if (response.ok() && body && typeof body.token === 'string' && body.token) {\n tokenCache.set(cacheKey, { token: body.token, mintedAt: Date.now() });\n return body.token;\n }\n if (response.status() !== 429) break;\n const backoffMs = 1000 * 2 ** retry;\n await new Promise((resolve) => setTimeout(resolve, backoffMs));\n }\n }\n\n throw new Error(`Failed to obtain auth token (status ${lastStatus})`);\n}\n\nexport async function apiRequest(\n request: APIRequestContext,\n method: string,\n path: string,\n options: { token: string; data?: unknown; timeout?: number },\n) {\n const headers = {\n Authorization: `Bearer ${options.token}`,\n 'Content-Type': 'application/json',\n };\n const timeout = options.timeout ?? 30_000;\n let lastError: unknown = null;\n for (let attempt = 0; attempt < 2; attempt += 1) {\n try {\n return await request.fetch(resolveUrl(path), { method, headers, data: options.data, timeout });\n } catch (error) {\n lastError = error;\n const message = error instanceof Error ? error.message : '';\n const retryable = /timeout|idle-session|socket|ECONNRESET|Target page, context or browser has been closed/i.test(message);\n if (!retryable || attempt === 1) throw error;\n await new Promise((resolve) => setTimeout(resolve, 250));\n }\n }\n throw lastError;\n}\n\nexport async function postForm(\n request: APIRequestContext,\n path: string,\n data: Record<string, string>,\n options?: { headers?: Record<string, string> },\n) {\n const form = new URLSearchParams();\n for (const [key, value] of Object.entries(data)) form.set(key, value);\n return request.post(resolveUrl(path), {\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n ...(options?.headers ?? {}),\n },\n data: form.toString(),\n });\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,2BAAsC;AAE/C,MAAM,WAAW,QAAQ,IAAI,UAAU,KAAK,KAAK;AAEjD,SAAS,WAAW,MAAsB;AACxC,SAAO,WAAW,GAAG,QAAQ,GAAG,IAAI,KAAK;AAC3C;AAKA,MAAM,aAAa,oBAAI,IAAiD;AACxE,MAAM,eAAe,KAAK,KAAK;AAWxB,SAAS,sBAA4B;AAC1C,aAAW,MAAM;AACnB;AAEA,eAAsB,aACpB,SACA,cAA6B,SAC7B,UACiB;AACjB,QAAM,OAAO,eAAe,sBAAuB,cAAuB;AAC1E,QAAM,qBAAiE,CAAC;AAExE,MAAI,MAAM;AACR,UAAM,aAAa,oBAAoB,IAAI;AAC3C,uBAAmB,KAAK,EAAE,OAAO,WAAW,OAAO,UAAU,YAAY,WAAW,SAAS,CAAC;AAC9F,QAAI,CAAC,UAAU;AACb,yBAAmB,KAAK,EAAE,OAAO,GAAG,IAAI,aAAa,UAAU,SAAS,CAAC;AAAA,IAC3E;AAAA,EACF,OAAO;AACL,uBAAmB,KAAK,EAAE,OAAO,aAAa,UAAU,YAAY,SAAS,CAAC;AAAA,EAChF;AAEA,QAAM,WAAW,mBACd,IAAI,CAAC,UAAU,GAAG,MAAM,KAAK,IAAI,MAAM,QAAQ,EAAE,EACjD,KAAK,GAAG;AACX,QAAM,SAAS,WAAW,IAAI,QAAQ;AACtC,MAAI,UAAU,KAAK,IAAI,IAAI,OAAO,WAAW,cAAc;AACzD,WAAO,OAAO;AAAA,EAChB;AAEA,MAAI,aAAa;AAEjB,aAAW,WAAW,oBAAoB;AACxC,UAAM,OAAO,IAAI,gBAAgB;AACjC,SAAK,IAAI,SAAS,QAAQ,KAAK;AAC/B,SAAK,IAAI,YAAY,QAAQ,QAAQ;AAIrC,aAAS,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG;AACzC,YAAM,WAAW,MAAM,QAAQ,KAAK,WAAW,iBAAiB,GAAG;AAAA,QACjE,SAAS;AAAA,UACP,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,SAAS;AAAA,MACtB,CAAC;AAED,YAAM,MAAM,MAAM,SAAS,KAAK;AAChC,UAAI,OAAuC;AAC3C,UAAI;AACF,eAAO,MAAO,KAAK,MAAM,GAAG,IAAgC;AAAA,MAC9D,QAAQ;AACN,eAAO;AAAA,MACT;AAEA,mBAAa,SAAS,OAAO;AAC7B,UAAI,SAAS,GAAG,KAAK,QAAQ,OAAO,KAAK,UAAU,YAAY,KAAK,OAAO;AACzE,mBAAW,IAAI,UAAU,EAAE,OAAO,KAAK,OAAO,UAAU,KAAK,IAAI,EAAE,CAAC;AACpE,eAAO,KAAK;AAAA,MACd;AACA,UAAI,SAAS,OAAO,MAAM,IAAK;AAC/B,YAAM,YAAY,MAAO,KAAK;AAC9B,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,SAAS,CAAC;AAAA,IAC/D;AAAA,EACF;AAEA,QAAM,IAAI,MAAM,uCAAuC,UAAU,GAAG;AACtE;AAEA,eAAsB,WACpB,SACA,QACA,MACA,SACA;AACA,QAAM,UAAU;AAAA,IACd,eAAe,UAAU,QAAQ,KAAK;AAAA,IACtC,gBAAgB;AAAA,
|
|
4
|
+
"sourcesContent": ["import { type APIRequestContext } from '@playwright/test';\nimport { DEFAULT_CREDENTIALS, type Role } from './auth';\n\nconst BASE_URL = process.env.BASE_URL?.trim() || null;\n\nfunction resolveUrl(path: string): string {\n return BASE_URL ? `${BASE_URL}${path}` : path;\n}\n\n// Cached tokens per credential to dodge the login rate limit\n// (5 attempts/60s per email). Tokens are reused across tests in the same\n// Playwright worker; each worker still mints its own.\nconst tokenCache = new Map<string, { token: string; mintedAt: number }>();\nconst TOKEN_TTL_MS = 45 * 60 * 1000; // 45 min; well under the default 2h session TTL.\n\n/**\n * Drops cached tokens so the next getAuthToken() mints a fresh session.\n *\n * A cached token points at a `sessions` row. Anything that deletes those rows \u2014\n * deactivating the user, a password change, an explicit session purge \u2014 leaves the\n * cached token resolving to 401 for the rest of the TTL, and `apiRequest` neither\n * retries nor re-mints. A spec that revokes sessions for an account other specs also\n * use MUST call this afterwards.\n */\nexport function clearAuthTokenCache(): void {\n tokenCache.clear();\n}\n\nexport async function getAuthToken(\n request: APIRequestContext,\n roleOrEmail: Role | string = 'admin',\n password?: string,\n): Promise<string> {\n const role = roleOrEmail in DEFAULT_CREDENTIALS ? (roleOrEmail as Role) : null;\n const credentialAttempts: Array<{ email: string; password: string }> = [];\n\n if (role) {\n const configured = DEFAULT_CREDENTIALS[role];\n credentialAttempts.push({ email: configured.email, password: password ?? configured.password });\n if (!password) {\n credentialAttempts.push({ email: `${role}@acme.com`, password: 'secret' });\n }\n } else {\n credentialAttempts.push({ email: roleOrEmail, password: password ?? 'secret' });\n }\n\n const cacheKey = credentialAttempts\n .map((entry) => `${entry.email}:${entry.password}`)\n .join('|');\n const cached = tokenCache.get(cacheKey);\n if (cached && Date.now() - cached.mintedAt < TOKEN_TTL_MS) {\n return cached.token;\n }\n\n let lastStatus = 0;\n\n for (const attempt of credentialAttempts) {\n const form = new URLSearchParams();\n form.set('email', attempt.email);\n form.set('password', attempt.password);\n\n // Retry on 429 (auth rate limit kicks in after ~25-30 rapid attempts from\n // the same test run). Capped exponential backoff: 1s, 2s, 4s; 3 retries.\n for (let retry = 0; retry < 4; retry += 1) {\n const response = await request.post(resolveUrl('/api/auth/login'), {\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n },\n data: form.toString(),\n });\n\n const raw = await response.text();\n let body: Record<string, unknown> | null = null;\n try {\n body = raw ? (JSON.parse(raw) as Record<string, unknown>) : null;\n } catch {\n body = null;\n }\n\n lastStatus = response.status();\n if (response.ok() && body && typeof body.token === 'string' && body.token) {\n tokenCache.set(cacheKey, { token: body.token, mintedAt: Date.now() });\n return body.token;\n }\n if (response.status() !== 429) break;\n const backoffMs = 1000 * 2 ** retry;\n await new Promise((resolve) => setTimeout(resolve, backoffMs));\n }\n }\n\n throw new Error(`Failed to obtain auth token (status ${lastStatus})`);\n}\n\nexport async function apiRequest(\n request: APIRequestContext,\n method: string,\n path: string,\n options: { token: string; data?: unknown; timeout?: number; headers?: Record<string, string> },\n) {\n const headers = {\n Authorization: `Bearer ${options.token}`,\n 'Content-Type': 'application/json',\n ...(options.headers ?? {}),\n };\n const timeout = options.timeout ?? 30_000;\n let lastError: unknown = null;\n for (let attempt = 0; attempt < 2; attempt += 1) {\n try {\n return await request.fetch(resolveUrl(path), { method, headers, data: options.data, timeout });\n } catch (error) {\n lastError = error;\n const message = error instanceof Error ? error.message : '';\n const retryable = /timeout|idle-session|socket|ECONNRESET|Target page, context or browser has been closed/i.test(message);\n if (!retryable || attempt === 1) throw error;\n await new Promise((resolve) => setTimeout(resolve, 250));\n }\n }\n throw lastError;\n}\n\nexport async function postForm(\n request: APIRequestContext,\n path: string,\n data: Record<string, string>,\n options?: { headers?: Record<string, string> },\n) {\n const form = new URLSearchParams();\n for (const [key, value] of Object.entries(data)) form.set(key, value);\n return request.post(resolveUrl(path), {\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n ...(options?.headers ?? {}),\n },\n data: form.toString(),\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
function resolveAppRoot(input) {
|
|
3
|
+
const explicit = input?.trim() || process.env.OM_TEST_APP_ROOT?.trim();
|
|
4
|
+
return explicit ? path.resolve(explicit) : path.resolve(process.cwd(), "apps/mercato");
|
|
5
|
+
}
|
|
6
|
+
export {
|
|
7
|
+
resolveAppRoot
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=appRoot.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/helpers/integration/appRoot.ts"],
|
|
4
|
+
"sourcesContent": ["import path from 'node:path'\n\n/**\n * The app root an integration test targets: `OM_TEST_APP_ROOT` when a standalone app is under test,\n * else the monorepo's `apps/mercato` (the Playwright process runs from the repo root, so its `cwd` is\n * not the app root).\n *\n * Single source for what was previously re-derived in `queue.ts`, `dbFixtures.ts`, and `pushFake.ts`.\n */\nexport function resolveAppRoot(input?: string): string {\n const explicit = input?.trim() || process.env.OM_TEST_APP_ROOT?.trim()\n return explicit ? path.resolve(explicit) : path.resolve(process.cwd(), 'apps/mercato')\n}\n"],
|
|
5
|
+
"mappings": "AAAA,OAAO,UAAU;AASV,SAAS,eAAe,OAAwB;AACrD,QAAM,WAAW,OAAO,KAAK,KAAK,QAAQ,IAAI,kBAAkB,KAAK;AACrE,SAAO,WAAW,KAAK,QAAQ,QAAQ,IAAI,KAAK,QAAQ,QAAQ,IAAI,GAAG,cAAc;AACvF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { Client } from "pg";
|
|
4
|
-
|
|
5
|
-
const fromEnv = process.env.OM_TEST_APP_ROOT?.trim();
|
|
6
|
-
return fromEnv ? path.resolve(fromEnv) : path.resolve(process.cwd(), "apps/mercato");
|
|
7
|
-
}
|
|
4
|
+
import { resolveAppRoot } from "./appRoot.js";
|
|
8
5
|
function readEnvValue(key) {
|
|
9
6
|
if (process.env[key]) return process.env[key];
|
|
10
7
|
const candidatePaths = [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/helpers/integration/dbFixtures.ts"],
|
|
4
|
-
"sourcesContent": ["import { readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { Client } from 'pg';\n\n/**\n * Database fixtures for the org-scope fail-open hardening tests.\n *\n * These helpers talk to Postgres directly via `pg` rather than bootstrapping the\n * app DI container, because:\n * - the directory create command denies non-super-admin actors (the only\n * loginable accounts here), so orgs cannot be created over the API; and\n * - granting `customers.*` through the ACL API requires a super-admin actor.\n * Raw SQL keeps the test self-contained and avoids depending on built package\n * `dist/` output for an in-process MikroORM bootstrap.\n */\n\nfunction
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,OAAO,UAAU;AACjB,SAAS,cAAc;
|
|
4
|
+
"sourcesContent": ["import { readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { Client } from 'pg';\nimport { resolveAppRoot } from './appRoot';\n\n/**\n * Database fixtures for the org-scope fail-open hardening tests.\n *\n * These helpers talk to Postgres directly via `pg` rather than bootstrapping the\n * app DI container, because:\n * - the directory create command denies non-super-admin actors (the only\n * loginable accounts here), so orgs cannot be created over the API; and\n * - granting `customers.*` through the ACL API requires a super-admin actor.\n * Raw SQL keeps the test self-contained and avoids depending on built package\n * `dist/` output for an in-process MikroORM bootstrap.\n */\n\nfunction readEnvValue(key: string): string | undefined {\n if (process.env[key]) return process.env[key];\n const candidatePaths = [\n path.resolve(resolveAppRoot(), '.env'),\n path.resolve(process.cwd(), 'apps/mercato/.env'),\n path.resolve(process.cwd(), '.env'),\n ];\n for (const envPath of candidatePaths) {\n try {\n const content = readFileSync(envPath, 'utf-8');\n const match = content.match(new RegExp(`^${key}=(.+)$`, 'm'));\n if (match?.[1]) return match[1].trim();\n } catch {\n continue;\n }\n }\n return undefined;\n}\n\nfunction resolveDatabaseUrl(): string {\n const url = readEnvValue('DATABASE_URL');\n if (!url) throw new Error('[internal] DATABASE_URL is not configured for integration DB fixtures');\n return url;\n}\n\n/**\n * The slice of `pg`'s client these fixtures use, declared structurally.\n *\n * `pg` publishes `Client` as a class merged with a namespace, and the app project's module\n * resolution picks the namespace half in type position (`TS2709`), so naming the class here made\n * every caller outside `packages/core` fail to typecheck. A structural type is resolvable from\n * every project and keeps `query<T>()` generic, which `InstanceType<typeof Client>` did not.\n */\nexport type IntegrationDbClient = {\n query<T = Record<string, unknown>>(\n text: string,\n values?: readonly unknown[],\n ): Promise<{ rows: T[]; rowCount: number | null }>;\n};\n\nexport async function withClient<T>(run: (client: IntegrationDbClient) => Promise<T>): Promise<T> {\n const client = new Client({ connectionString: resolveDatabaseUrl() }) as unknown as IntegrationDbClient & {\n connect: () => Promise<void>;\n end: () => Promise<void>;\n };\n await client.connect();\n try {\n return await run(client);\n } finally {\n await client.end();\n }\n}\n\n/**\n * Nulls a user's home organization (`organization_id`) directly in the database.\n *\n * Required to construct the \"floating restricted user\" precondition: the JWT\n * `auth.orgId` is minted from `users.organization_id` at login, so this MUST run\n * BEFORE the user logs in.\n */\nexport async function clearUserHomeOrganization(userId: string): Promise<void> {\n await withClient(async (client) => {\n await client.query('update users set organization_id = null where id = $1', [userId]);\n });\n}\n\n/**\n * Upserts a per-user ACL row, setting the effective feature list and the\n * organization-visibility list.\n *\n * `organizations` maps to `OrganizationScope.allowedIds` (write path) and\n * `filterIds` (read path):\n * - `[orgA]` => restricted to orgA (write fail-open precondition, #2239)\n * - `[]` => restricted to zero orgs (read fail-open precondition, #2245)\n * - `null` => unrestricted\n */\nexport async function setUserAclInDb(input: {\n userId: string;\n tenantId: string;\n features: string[];\n organizations: string[] | null;\n}): Promise<void> {\n await withClient(async (client) => {\n const existing = await client.query<{ id: string }>(\n 'select id from user_acls where user_id = $1 and tenant_id = $2 limit 1',\n [input.userId, input.tenantId],\n );\n const featuresJson = JSON.stringify(input.features);\n const organizationsJson = input.organizations === null ? null : JSON.stringify(input.organizations);\n if (existing.rows.length > 0) {\n await client.query(\n 'update user_acls set features_json = $2::jsonb, organizations_json = $3::jsonb, is_super_admin = false, updated_at = now() where id = $1',\n [existing.rows[0].id, featuresJson, organizationsJson],\n );\n return;\n }\n await client.query(\n `insert into user_acls (id, user_id, tenant_id, features_json, organizations_json, is_super_admin, created_at)\n values (gen_random_uuid(), $1, $2, $3::jsonb, $4::jsonb, false, now())`,\n [input.userId, input.tenantId, featuresJson, organizationsJson],\n );\n });\n}\n\n/** Removes any per-user ACL rows for the user (best-effort test cleanup). */\nexport async function deleteUserAclInDb(userId: string): Promise<void> {\n if (!userId) return;\n await withClient(async (client) => {\n await client.query('delete from user_acls where user_id = $1', [userId]);\n });\n}\n\n/**\n * Creates an organization directly in the database within the given tenant.\n *\n * The directory create command routes through `enforceTenantSelection`, which\n * denies the (non-super-admin) accounts loginable on this instance. Landing the\n * org in a known tenant lets the floating user (created under it) share the\n * tenant \u2014 required because cross-tenant access returns 404 before the\n * org-scope guard ever runs.\n */\nexport async function createOrganizationInDb(input: { name: string; tenantId: string }): Promise<string> {\n return withClient(async (client) => {\n const result = await client.query<{ id: string }>(\n `insert into organizations\n (id, tenant_id, name, is_active, ancestor_ids, child_ids, descendant_ids, depth, created_at, updated_at)\n values (gen_random_uuid(), $1, $2, true, '[]'::jsonb, '[]'::jsonb, '[]'::jsonb, 0, now(), now())\n returning id`,\n [input.tenantId, input.name],\n );\n return result.rows[0].id;\n });\n}\n\n/** Hard-deletes an organization row (best-effort test cleanup). */\nexport async function deleteOrganizationInDb(organizationId: string | null): Promise<void> {\n if (!organizationId) return;\n await withClient(async (client) => {\n await client.query('delete from organizations where id = $1', [organizationId]);\n });\n}\n\n/**\n * Hard-deletes integration credential rows for an organization (best-effort test\n * cleanup). Integration credentials are stored per (integration_id,\n * organization_id, tenant_id, user_id) with no FK to organizations, so a\n * throwaway org's credential rows must be removed explicitly when the org row is\n * torn down.\n */\nexport async function deleteIntegrationCredentialsInDb(organizationId: string | null): Promise<void> {\n if (!organizationId) return;\n await withClient(async (client) => {\n await client.query('delete from integration_credentials where organization_id = $1', [organizationId]);\n });\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,OAAO,UAAU;AACjB,SAAS,cAAc;AACvB,SAAS,sBAAsB;AAc/B,SAAS,aAAa,KAAiC;AACrD,MAAI,QAAQ,IAAI,GAAG,EAAG,QAAO,QAAQ,IAAI,GAAG;AAC5C,QAAM,iBAAiB;AAAA,IACrB,KAAK,QAAQ,eAAe,GAAG,MAAM;AAAA,IACrC,KAAK,QAAQ,QAAQ,IAAI,GAAG,mBAAmB;AAAA,IAC/C,KAAK,QAAQ,QAAQ,IAAI,GAAG,MAAM;AAAA,EACpC;AACA,aAAW,WAAW,gBAAgB;AACpC,QAAI;AACF,YAAM,UAAU,aAAa,SAAS,OAAO;AAC7C,YAAM,QAAQ,QAAQ,MAAM,IAAI,OAAO,IAAI,GAAG,UAAU,GAAG,CAAC;AAC5D,UAAI,QAAQ,CAAC,EAAG,QAAO,MAAM,CAAC,EAAE,KAAK;AAAA,IACvC,QAAQ;AACN;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,qBAA6B;AACpC,QAAM,MAAM,aAAa,cAAc;AACvC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,uEAAuE;AACjG,SAAO;AACT;AAiBA,eAAsB,WAAc,KAA8D;AAChG,QAAM,SAAS,IAAI,OAAO,EAAE,kBAAkB,mBAAmB,EAAE,CAAC;AAIpE,QAAM,OAAO,QAAQ;AACrB,MAAI;AACF,WAAO,MAAM,IAAI,MAAM;AAAA,EACzB,UAAE;AACA,UAAM,OAAO,IAAI;AAAA,EACnB;AACF;AASA,eAAsB,0BAA0B,QAA+B;AAC7E,QAAM,WAAW,OAAO,WAAW;AACjC,UAAM,OAAO,MAAM,yDAAyD,CAAC,MAAM,CAAC;AAAA,EACtF,CAAC;AACH;AAYA,eAAsB,eAAe,OAKnB;AAChB,QAAM,WAAW,OAAO,WAAW;AACjC,UAAM,WAAW,MAAM,OAAO;AAAA,MAC5B;AAAA,MACA,CAAC,MAAM,QAAQ,MAAM,QAAQ;AAAA,IAC/B;AACA,UAAM,eAAe,KAAK,UAAU,MAAM,QAAQ;AAClD,UAAM,oBAAoB,MAAM,kBAAkB,OAAO,OAAO,KAAK,UAAU,MAAM,aAAa;AAClG,QAAI,SAAS,KAAK,SAAS,GAAG;AAC5B,YAAM,OAAO;AAAA,QACX;AAAA,QACA,CAAC,SAAS,KAAK,CAAC,EAAE,IAAI,cAAc,iBAAiB;AAAA,MACvD;AACA;AAAA,IACF;AACA,UAAM,OAAO;AAAA,MACX;AAAA;AAAA,MAEA,CAAC,MAAM,QAAQ,MAAM,UAAU,cAAc,iBAAiB;AAAA,IAChE;AAAA,EACF,CAAC;AACH;AAGA,eAAsB,kBAAkB,QAA+B;AACrE,MAAI,CAAC,OAAQ;AACb,QAAM,WAAW,OAAO,WAAW;AACjC,UAAM,OAAO,MAAM,4CAA4C,CAAC,MAAM,CAAC;AAAA,EACzE,CAAC;AACH;AAWA,eAAsB,uBAAuB,OAA4D;AACvG,SAAO,WAAW,OAAO,WAAW;AAClC,UAAM,SAAS,MAAM,OAAO;AAAA,MAC1B;AAAA;AAAA;AAAA;AAAA,MAIA,CAAC,MAAM,UAAU,MAAM,IAAI;AAAA,IAC7B;AACA,WAAO,OAAO,KAAK,CAAC,EAAE;AAAA,EACxB,CAAC;AACH;AAGA,eAAsB,uBAAuB,gBAA8C;AACzF,MAAI,CAAC,eAAgB;AACrB,QAAM,WAAW,OAAO,WAAW;AACjC,UAAM,OAAO,MAAM,2CAA2C,CAAC,cAAc,CAAC;AAAA,EAChF,CAAC;AACH;AASA,eAAsB,iCAAiC,gBAA8C;AACnG,MAAI,CAAC,eAAgB;AACrB,QAAM,WAAW,OAAO,WAAW;AACjC,UAAM,OAAO,MAAM,kEAAkE,CAAC,cAAc,CAAC;AAAA,EACvG,CAAC;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { generateKeyPairSync } from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { expect } from "@playwright/test";
|
|
4
|
+
import {
|
|
5
|
+
findFakePush,
|
|
6
|
+
resolveQueueBaseDir,
|
|
7
|
+
uniquePushTokenTail
|
|
8
|
+
} from "../../modules/push_notifications/lib/fake-provider-recorder.js";
|
|
9
|
+
import { apiRequest } from "./api.js";
|
|
10
|
+
import { resolveAppRoot } from "./appRoot.js";
|
|
11
|
+
import { deleteEntityByPathIfExists, expectId, readJsonSafe } from "./generalFixtures.js";
|
|
12
|
+
import { withClient } from "./dbFixtures.js";
|
|
13
|
+
if (!process.env.QUEUE_BASE_DIR?.trim()) {
|
|
14
|
+
process.env.QUEUE_BASE_DIR = path.resolve(resolveAppRoot(), ".mercato/queue");
|
|
15
|
+
}
|
|
16
|
+
import { deleteChannelIfExists } from "./communicationChannelsFixtures.js";
|
|
17
|
+
const TENANT_CONNECT_PATH = "/api/communication_channels/channels/connect/tenant-credentials";
|
|
18
|
+
const DEVICES_PATH = "/api/devices";
|
|
19
|
+
const NOTIFICATIONS_PATH = "/api/notifications";
|
|
20
|
+
function makeFakePushToken(provider) {
|
|
21
|
+
const tokenTail = uniquePushTokenTail();
|
|
22
|
+
return { pushToken: `qa-${provider}-token-${tokenTail}`, tokenTail };
|
|
23
|
+
}
|
|
24
|
+
function makeFakePushTokenFor(provider, sentinel) {
|
|
25
|
+
const tokenTail = uniquePushTokenTail();
|
|
26
|
+
return { pushToken: `qa-${provider}-${sentinel}-token-${tokenTail}`, tokenTail };
|
|
27
|
+
}
|
|
28
|
+
const FAKE_APNS_P8_KEY = generateKeyPairSync("ec", {
|
|
29
|
+
namedCurve: "P-256",
|
|
30
|
+
privateKeyEncoding: { type: "pkcs8", format: "pem" },
|
|
31
|
+
publicKeyEncoding: { type: "spki", format: "pem" }
|
|
32
|
+
}).privateKey;
|
|
33
|
+
const FAKE_PUSH_CREDENTIALS = {
|
|
34
|
+
fcm: {
|
|
35
|
+
serviceAccountJson: JSON.stringify({
|
|
36
|
+
project_id: "om-fake-project",
|
|
37
|
+
client_email: "fake@om-fake-project.iam.gserviceaccount.com",
|
|
38
|
+
private_key: "-----BEGIN PRIVATE KEY-----\nom-fake-key\n-----END PRIVATE KEY-----\n"
|
|
39
|
+
})
|
|
40
|
+
},
|
|
41
|
+
apns: {
|
|
42
|
+
p8Key: FAKE_APNS_P8_KEY,
|
|
43
|
+
keyId: "FAKEKEYID1",
|
|
44
|
+
teamId: "FAKETEAMID",
|
|
45
|
+
bundleId: "com.openmercato.fake",
|
|
46
|
+
production: false
|
|
47
|
+
},
|
|
48
|
+
expo: {
|
|
49
|
+
accessToken: "om-fake-expo-token"
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
async function connectFakePushChannel(request, token, provider, displayName) {
|
|
53
|
+
const response = await apiRequest(request, "POST", TENANT_CONNECT_PATH, {
|
|
54
|
+
token,
|
|
55
|
+
data: { providerKey: provider, displayName, credentials: FAKE_PUSH_CREDENTIALS[provider] }
|
|
56
|
+
});
|
|
57
|
+
expect(response.status(), `connect ${provider} channel`).toBe(201);
|
|
58
|
+
const body = await readJsonSafe(response);
|
|
59
|
+
return expectId(body?.channelId, `connect ${provider} response should include channelId`);
|
|
60
|
+
}
|
|
61
|
+
async function registerFakePushDevice(request, token, provider, pushToken, deviceId) {
|
|
62
|
+
const response = await apiRequest(request, "POST", DEVICES_PATH, {
|
|
63
|
+
token,
|
|
64
|
+
data: { deviceId, platform: provider === "apns" ? "ios" : "android", pushToken, pushProvider: provider }
|
|
65
|
+
});
|
|
66
|
+
expect(response.status(), `register ${provider} device`).toBe(201);
|
|
67
|
+
const body = await readJsonSafe(response);
|
|
68
|
+
return expectId(body?.id, `register ${provider} device response should include id`);
|
|
69
|
+
}
|
|
70
|
+
async function deleteFakePushDevice(request, token, userDeviceId) {
|
|
71
|
+
if (!userDeviceId) return;
|
|
72
|
+
await deleteEntityByPathIfExists(request, token, `${DEVICES_PATH}/${userDeviceId}`);
|
|
73
|
+
}
|
|
74
|
+
async function deleteDeliveriesForDevice(userDeviceId) {
|
|
75
|
+
if (!userDeviceId) return;
|
|
76
|
+
await withClient(async (client) => {
|
|
77
|
+
await client.query("delete from push_notification_deliveries where user_device_id = $1", [userDeviceId]);
|
|
78
|
+
}).catch(() => void 0);
|
|
79
|
+
}
|
|
80
|
+
async function readLatestDelivery(tenantId, userDeviceId) {
|
|
81
|
+
return withClient(async (client) => {
|
|
82
|
+
const res = await client.query(
|
|
83
|
+
`select status, token_snapshot, organization_id, provider, attempts, silent, sent_at, last_error, provider_response
|
|
84
|
+
from push_notification_deliveries
|
|
85
|
+
where tenant_id = $1 and user_device_id = $2
|
|
86
|
+
order by created_at desc
|
|
87
|
+
limit 1`,
|
|
88
|
+
[tenantId, userDeviceId]
|
|
89
|
+
);
|
|
90
|
+
return res.rows[0] ?? null;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async function isDeviceSoftDeleted(userDeviceId) {
|
|
94
|
+
return withClient(async (client) => {
|
|
95
|
+
const res = await client.query("select deleted_at from user_devices where id = $1", [userDeviceId]);
|
|
96
|
+
if (res.rows.length === 0) return false;
|
|
97
|
+
return res.rows[0].deleted_at != null;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function readNativeMessage(provider, tokenTail, sinceIso) {
|
|
101
|
+
return findFakePush(provider, tokenTail, sinceIso);
|
|
102
|
+
}
|
|
103
|
+
async function expectNativeMessage(provider, tokenTail, sinceIso = (/* @__PURE__ */ new Date(0)).toISOString()) {
|
|
104
|
+
await expect.poll(() => readNativeMessage(provider, tokenTail, sinceIso)?.native ?? null, { timeout: 3e4 }).not.toBeNull();
|
|
105
|
+
return readNativeMessage(provider, tokenTail, sinceIso).native;
|
|
106
|
+
}
|
|
107
|
+
export {
|
|
108
|
+
DEVICES_PATH,
|
|
109
|
+
FAKE_PUSH_CREDENTIALS,
|
|
110
|
+
NOTIFICATIONS_PATH,
|
|
111
|
+
TENANT_CONNECT_PATH,
|
|
112
|
+
connectFakePushChannel,
|
|
113
|
+
deleteChannelIfExists,
|
|
114
|
+
deleteDeliveriesForDevice,
|
|
115
|
+
deleteFakePushDevice,
|
|
116
|
+
expectNativeMessage,
|
|
117
|
+
isDeviceSoftDeleted,
|
|
118
|
+
makeFakePushToken,
|
|
119
|
+
makeFakePushTokenFor,
|
|
120
|
+
readLatestDelivery,
|
|
121
|
+
readNativeMessage,
|
|
122
|
+
registerFakePushDevice,
|
|
123
|
+
resolveQueueBaseDir,
|
|
124
|
+
uniquePushTokenTail
|
|
125
|
+
};
|
|
126
|
+
//# sourceMappingURL=pushFake.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/helpers/integration/pushFake.ts"],
|
|
4
|
+
"sourcesContent": ["import { generateKeyPairSync } from 'node:crypto'\nimport path from 'node:path'\nimport { expect, type APIRequestContext } from '@playwright/test'\nimport {\n findFakePush,\n resolveQueueBaseDir,\n uniquePushTokenTail,\n type FakePushEntry,\n type FakePushProvider,\n} from '../../modules/push_notifications/lib/fake-provider-recorder'\nimport { apiRequest } from './api'\nimport { resolveAppRoot } from './appRoot'\nimport { deleteEntityByPathIfExists, expectId, readJsonSafe } from './generalFixtures'\nimport { withClient } from './dbFixtures'\n\n/**\n * Harness for the `OM_PUSH_FAKE_PROVIDERS` integration specs (TC-PUSH-004+, TC-CHANNEL-PUSH-005+).\n *\n * The fake replaces each provider's SDK client, so the REAL adapter runs and records the\n * provider-native message it built. The worker does that in a different process than the spec, so the\n * message travels through a JSONL sink under the queue's own base dir rather than the delivery row \u2014\n * no adapter returns `metadata` on success, and APNs deliberately reports an empty `externalMessageId`.\n */\n/**\n * The Playwright process runs from the repo root, so its `cwd` is not the app root. Pin `QUEUE_BASE_DIR`\n * once, before anything resolves it, so the spec, the worker, and the drain child all agree on the queue\n * dir and the sink inside it. Never overrides a dir the harness already chose (the ephemeral runner\n * sets its own).\n */\nif (!process.env.QUEUE_BASE_DIR?.trim()) {\n process.env.QUEUE_BASE_DIR = path.resolve(resolveAppRoot(), '.mercato/queue')\n}\n\n/** Re-exported so specs never hand-roll a second, divergent fallback (see TC-PUSH-008). */\nexport { resolveQueueBaseDir, uniquePushTokenTail }\n\n/** The canonical channel teardown \u2014 API-based, not raw SQL. Re-exported so specs import one module. */\nexport { deleteChannelIfExists } from './communicationChannelsFixtures'\n\nexport const TENANT_CONNECT_PATH = '/api/communication_channels/channels/connect/tenant-credentials'\nexport const DEVICES_PATH = '/api/devices'\nexport const NOTIFICATIONS_PATH = '/api/notifications'\n\n/** A push token whose last 8 chars are unique to this run \u2014 the sink and delivery row are keyed on them. */\nexport function makeFakePushToken(provider: FakePushProvider): { pushToken: string; tokenTail: string } {\n const tokenTail = uniquePushTokenTail()\n return { pushToken: `qa-${provider}-token-${tokenTail}`, tokenTail }\n}\n\n/** A push token that drives the given branch, keeping the run-unique tail. */\nexport function makeFakePushTokenFor(\n provider: FakePushProvider,\n sentinel: 'unregistered' | 'fail',\n): { pushToken: string; tokenTail: string } {\n const tokenTail = uniquePushTokenTail()\n return { pushToken: `qa-${provider}-${sentinel}-token-${tokenTail}`, tokenTail }\n}\n\nexport type PushDeliveryRow = {\n status: string\n token_snapshot: string\n organization_id: string | null\n provider: string\n attempts: number\n silent: boolean\n sent_at: string | null\n last_error: string | null\n provider_response: Record<string, unknown> | null\n}\n\n/**\n * A throwaway ES256 (P-256) key in PKCS#8 PEM \u2014 the curve Apple's `.p8` signing\n * keys use. `apnsCredentialsSchema` structurally parses `p8Key`, so the fixture\n * must be a genuinely parseable key; generating it per process keeps a private\n * key out of the repository.\n */\nconst FAKE_APNS_P8_KEY = generateKeyPairSync('ec', {\n namedCurve: 'P-256',\n privateKeyEncoding: { type: 'pkcs8', format: 'pem' },\n publicKeyEncoding: { type: 'spki', format: 'pem' },\n}).privateKey\n\n/**\n * Valid-*shaped* fake credentials. `validateCredentials` is schema-only \u2014 it checks\n * structure (APNs additionally parses the `.p8` and pins Apple's identifier formats),\n * never that the provider accepts them; real parsing lives in the faked SDK client.\n */\nexport const FAKE_PUSH_CREDENTIALS: Record<FakePushProvider, Record<string, unknown>> = {\n fcm: {\n serviceAccountJson: JSON.stringify({\n project_id: 'om-fake-project',\n client_email: 'fake@om-fake-project.iam.gserviceaccount.com',\n private_key: '-----BEGIN PRIVATE KEY-----\\nom-fake-key\\n-----END PRIVATE KEY-----\\n',\n }),\n },\n apns: {\n p8Key: FAKE_APNS_P8_KEY,\n keyId: 'FAKEKEYID1',\n teamId: 'FAKETEAMID',\n bundleId: 'com.openmercato.fake',\n production: false,\n },\n expo: {\n accessToken: 'om-fake-expo-token',\n },\n}\n\n/**\n * Connect a tenant-wide push channel through the real credential-connect route.\n *\n * Not `seedConnectedChannel` from `communicationChannelsFixtures`: that seeds the `__test_seed__`\n * provider, whereas these specs must drive the real `fcm`/`apns`/`expo` connect path so the adapter's own\n * `validateCredentials` runs. Tear down with `deleteChannelIfExists` from that same module.\n */\nexport async function connectFakePushChannel(\n request: APIRequestContext,\n token: string,\n provider: FakePushProvider,\n displayName: string,\n): Promise<string> {\n const response = await apiRequest(request, 'POST', TENANT_CONNECT_PATH, {\n token,\n data: { providerKey: provider, displayName, credentials: FAKE_PUSH_CREDENTIALS[provider] },\n })\n expect(response.status(), `connect ${provider} channel`).toBe(201)\n const body = await readJsonSafe<{ channelId?: string }>(response)\n return expectId(body?.channelId, `connect ${provider} response should include channelId`)\n}\n\n/**\n * Register a device routed to `provider`. The token tail keys both the delivery row's\n * `token_snapshot` and the recorded native message, isolating concurrent specs.\n */\nexport async function registerFakePushDevice(\n request: APIRequestContext,\n token: string,\n provider: FakePushProvider,\n pushToken: string,\n deviceId: string,\n): Promise<string> {\n const response = await apiRequest(request, 'POST', DEVICES_PATH, {\n token,\n data: { deviceId, platform: provider === 'apns' ? 'ios' : 'android', pushToken, pushProvider: provider },\n })\n expect(response.status(), `register ${provider} device`).toBe(201)\n const body = await readJsonSafe<{ id?: string }>(response)\n return expectId(body?.id, `register ${provider} device response should include id`)\n}\n\n/** Teardown counterpart to {@link registerFakePushDevice}. */\nexport async function deleteFakePushDevice(\n request: APIRequestContext,\n token: string | null,\n userDeviceId: string | null,\n): Promise<void> {\n if (!userDeviceId) return\n await deleteEntityByPathIfExists(request, token, `${DEVICES_PATH}/${userDeviceId}`)\n}\n\nexport async function deleteDeliveriesForDevice(userDeviceId: string | null): Promise<void> {\n if (!userDeviceId) return\n await withClient(async (client) => {\n await client.query('delete from push_notification_deliveries where user_device_id = $1', [userDeviceId])\n }).catch(() => undefined)\n}\n\nexport async function readLatestDelivery(tenantId: string, userDeviceId: string): Promise<PushDeliveryRow | null> {\n return withClient(async (client) => {\n const res = await client.query(\n `select status, token_snapshot, organization_id, provider, attempts, silent, sent_at, last_error, provider_response\n from push_notification_deliveries\n where tenant_id = $1 and user_device_id = $2\n order by created_at desc\n limit 1`,\n [tenantId, userDeviceId],\n )\n return (res.rows[0] as PushDeliveryRow | undefined) ?? null\n })\n}\n\n/**\n * True once the device has been soft-deleted (the `device_unregistered` contract).\n *\n * A MISSING row returns `false`, not `true`: soft-delete keeps the row, so an absent one means the\n * device was never created or a neighbouring teardown hard-deleted it. Returning `true` there would let\n * the central assertion of TC-PUSH-004/008 pass vacuously.\n */\nexport async function isDeviceSoftDeleted(userDeviceId: string): Promise<boolean> {\n return withClient(async (client) => {\n const res = await client.query('select deleted_at from user_devices where id = $1', [userDeviceId])\n if (res.rows.length === 0) return false\n return res.rows[0].deleted_at != null\n })\n}\n\n/** The provider-native message the REAL adapter handed the faked SDK client. */\nexport function readNativeMessage(\n provider: FakePushProvider,\n tokenTail: string,\n sinceIso?: string,\n): FakePushEntry | undefined {\n return findFakePush(provider, tokenTail, sinceIso)\n}\n\n/**\n * Wait for the message this run's worker recorded. Pass `sinceIso` to reject entries from an earlier\n * run \u2014 the sink is append-only and the reused-environment path never truncates it. The default\n * (epoch) accepts every recorded entry.\n */\nexport async function expectNativeMessage(\n provider: FakePushProvider,\n tokenTail: string,\n sinceIso: string = new Date(0).toISOString(),\n): Promise<Record<string, unknown>> {\n await expect\n .poll(() => readNativeMessage(provider, tokenTail, sinceIso)?.native ?? null, { timeout: 30_000 })\n .not.toBeNull()\n return readNativeMessage(provider, tokenTail, sinceIso)!.native\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const notificationTypes = [
|
|
2
2
|
{
|
|
3
3
|
type: "auth.password_reset.requested",
|
|
4
|
+
// Ships without push — operators re-enable it per type from the Notification Delivery settings.
|
|
5
|
+
channels: ["in_app", "email"],
|
|
4
6
|
module: "auth",
|
|
5
7
|
titleKey: "auth.notifications.passwordReset.requested.title",
|
|
6
8
|
bodyKey: "auth.notifications.passwordReset.requested.body",
|
|
@@ -20,6 +22,8 @@ const notificationTypes = [
|
|
|
20
22
|
},
|
|
21
23
|
{
|
|
22
24
|
type: "auth.password_reset.completed",
|
|
25
|
+
// Ships without push — operators re-enable it per type from the Notification Delivery settings.
|
|
26
|
+
channels: ["in_app", "email"],
|
|
23
27
|
module: "auth",
|
|
24
28
|
titleKey: "auth.notifications.passwordReset.completed.title",
|
|
25
29
|
bodyKey: "auth.notifications.passwordReset.completed.body",
|
|
@@ -30,11 +34,15 @@ const notificationTypes = [
|
|
|
30
34
|
},
|
|
31
35
|
{
|
|
32
36
|
type: "auth.account.locked",
|
|
37
|
+
// Ships without push — operators re-enable it per type from the Notification Delivery settings.
|
|
38
|
+
channels: ["in_app", "email"],
|
|
33
39
|
module: "auth",
|
|
34
40
|
titleKey: "auth.notifications.account.locked.title",
|
|
35
41
|
bodyKey: "auth.notifications.account.locked.body",
|
|
36
42
|
icon: "lock",
|
|
37
43
|
severity: "warning",
|
|
44
|
+
// Security alert — the user must not be able to silence it.
|
|
45
|
+
nonOptOut: true,
|
|
38
46
|
actions: [
|
|
39
47
|
{
|
|
40
48
|
id: "contact_support",
|
|
@@ -48,11 +56,15 @@ const notificationTypes = [
|
|
|
48
56
|
},
|
|
49
57
|
{
|
|
50
58
|
type: "auth.login.new_device",
|
|
59
|
+
// Ships without push — operators re-enable it per type from the Notification Delivery settings.
|
|
60
|
+
channels: ["in_app", "email"],
|
|
51
61
|
module: "auth",
|
|
52
62
|
titleKey: "auth.notifications.login.newDevice.title",
|
|
53
63
|
bodyKey: "auth.notifications.login.newDevice.body",
|
|
54
64
|
icon: "smartphone",
|
|
55
65
|
severity: "info",
|
|
66
|
+
// Security alert — the user must not be able to silence it.
|
|
67
|
+
nonOptOut: true,
|
|
56
68
|
actions: [
|
|
57
69
|
{
|
|
58
70
|
id: "view_sessions",
|
|
@@ -68,6 +80,8 @@ const notificationTypes = [
|
|
|
68
80
|
},
|
|
69
81
|
{
|
|
70
82
|
type: "auth.role.assigned",
|
|
83
|
+
// Ships without push — operators re-enable it per type from the Notification Delivery settings.
|
|
84
|
+
channels: ["in_app", "email"],
|
|
71
85
|
module: "auth",
|
|
72
86
|
titleKey: "auth.notifications.role.assigned.title",
|
|
73
87
|
bodyKey: "auth.notifications.role.assigned.body",
|
|
@@ -87,6 +101,8 @@ const notificationTypes = [
|
|
|
87
101
|
},
|
|
88
102
|
{
|
|
89
103
|
type: "auth.role.revoked",
|
|
104
|
+
// Ships without push — operators re-enable it per type from the Notification Delivery settings.
|
|
105
|
+
channels: ["in_app", "email"],
|
|
90
106
|
module: "auth",
|
|
91
107
|
titleKey: "auth.notifications.role.revoked.title",
|
|
92
108
|
bodyKey: "auth.notifications.role.revoked.body",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/auth/notifications.ts"],
|
|
4
|
-
"sourcesContent": ["import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/notifications/types'\n\nexport const notificationTypes: NotificationTypeDefinition[] = [\n {\n type: 'auth.password_reset.requested',\n module: 'auth',\n titleKey: 'auth.notifications.passwordReset.requested.title',\n bodyKey: 'auth.notifications.passwordReset.requested.body',\n icon: 'key',\n severity: 'info',\n actions: [\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/auth/profile',\n icon: 'external-link',\n },\n ],\n linkHref: '/backend/auth/profile',\n expiresAfterHours: 24,\n },\n {\n type: 'auth.password_reset.completed',\n module: 'auth',\n titleKey: 'auth.notifications.passwordReset.completed.title',\n bodyKey: 'auth.notifications.passwordReset.completed.body',\n icon: 'check-circle',\n severity: 'success',\n actions: [],\n expiresAfterHours: 72,\n },\n {\n type: 'auth.account.locked',\n module: 'auth',\n titleKey: 'auth.notifications.account.locked.title',\n bodyKey: 'auth.notifications.account.locked.body',\n icon: 'lock',\n severity: 'warning',\n actions: [\n {\n id: 'contact_support',\n labelKey: 'auth.actions.contactSupport',\n variant: 'default',\n href: '/backend/support',\n icon: 'mail',\n },\n ],\n linkHref: '/backend/support',\n },\n {\n type: 'auth.login.new_device',\n module: 'auth',\n titleKey: 'auth.notifications.login.newDevice.title',\n bodyKey: 'auth.notifications.login.newDevice.body',\n icon: 'smartphone',\n severity: 'info',\n actions: [\n {\n id: 'view_sessions',\n labelKey: 'auth.actions.viewSessions',\n variant: 'outline',\n href: '/backend/auth/sessions',\n icon: 'list',\n },\n ],\n linkHref: '/backend/auth/sessions',\n expiresAfterHours: 168, // 7 days\n },\n {\n type: 'auth.role.assigned',\n module: 'auth',\n titleKey: 'auth.notifications.role.assigned.title',\n bodyKey: 'auth.notifications.role.assigned.body',\n icon: 'user-plus',\n severity: 'success',\n actions: [\n {\n id: 'view_permissions',\n labelKey: 'auth.actions.viewPermissions',\n variant: 'outline',\n href: '/backend/auth/profile',\n icon: 'shield',\n },\n ],\n linkHref: '/backend/auth/profile',\n expiresAfterHours: 168,\n },\n {\n type: 'auth.role.revoked',\n module: 'auth',\n titleKey: 'auth.notifications.role.revoked.title',\n bodyKey: 'auth.notifications.role.revoked.body',\n icon: 'user-minus',\n severity: 'warning',\n actions: [\n {\n id: 'view_profile',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/auth/profile',\n },\n ],\n linkHref: '/backend/auth/profile',\n expiresAfterHours: 168,\n },\n]\n\nexport default notificationTypes\n"],
|
|
5
|
-
"mappings": "AAEO,MAAM,oBAAkD;AAAA,EAC7D;AAAA,IACE,MAAM;AAAA,
|
|
4
|
+
"sourcesContent": ["import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/notifications/types'\n\nexport const notificationTypes: NotificationTypeDefinition[] = [\n {\n type: 'auth.password_reset.requested',\n // Ships without push \u2014 operators re-enable it per type from the Notification Delivery settings.\n channels: ['in_app', 'email'],\n module: 'auth',\n titleKey: 'auth.notifications.passwordReset.requested.title',\n bodyKey: 'auth.notifications.passwordReset.requested.body',\n icon: 'key',\n severity: 'info',\n actions: [\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/auth/profile',\n icon: 'external-link',\n },\n ],\n linkHref: '/backend/auth/profile',\n expiresAfterHours: 24,\n },\n {\n type: 'auth.password_reset.completed',\n // Ships without push \u2014 operators re-enable it per type from the Notification Delivery settings.\n channels: ['in_app', 'email'],\n module: 'auth',\n titleKey: 'auth.notifications.passwordReset.completed.title',\n bodyKey: 'auth.notifications.passwordReset.completed.body',\n icon: 'check-circle',\n severity: 'success',\n actions: [],\n expiresAfterHours: 72,\n },\n {\n type: 'auth.account.locked',\n // Ships without push \u2014 operators re-enable it per type from the Notification Delivery settings.\n channels: ['in_app', 'email'],\n module: 'auth',\n titleKey: 'auth.notifications.account.locked.title',\n bodyKey: 'auth.notifications.account.locked.body',\n icon: 'lock',\n severity: 'warning',\n // Security alert \u2014 the user must not be able to silence it.\n nonOptOut: true,\n actions: [\n {\n id: 'contact_support',\n labelKey: 'auth.actions.contactSupport',\n variant: 'default',\n href: '/backend/support',\n icon: 'mail',\n },\n ],\n linkHref: '/backend/support',\n },\n {\n type: 'auth.login.new_device',\n // Ships without push \u2014 operators re-enable it per type from the Notification Delivery settings.\n channels: ['in_app', 'email'],\n module: 'auth',\n titleKey: 'auth.notifications.login.newDevice.title',\n bodyKey: 'auth.notifications.login.newDevice.body',\n icon: 'smartphone',\n severity: 'info',\n // Security alert \u2014 the user must not be able to silence it.\n nonOptOut: true,\n actions: [\n {\n id: 'view_sessions',\n labelKey: 'auth.actions.viewSessions',\n variant: 'outline',\n href: '/backend/auth/sessions',\n icon: 'list',\n },\n ],\n linkHref: '/backend/auth/sessions',\n expiresAfterHours: 168, // 7 days\n },\n {\n type: 'auth.role.assigned',\n // Ships without push \u2014 operators re-enable it per type from the Notification Delivery settings.\n channels: ['in_app', 'email'],\n module: 'auth',\n titleKey: 'auth.notifications.role.assigned.title',\n bodyKey: 'auth.notifications.role.assigned.body',\n icon: 'user-plus',\n severity: 'success',\n actions: [\n {\n id: 'view_permissions',\n labelKey: 'auth.actions.viewPermissions',\n variant: 'outline',\n href: '/backend/auth/profile',\n icon: 'shield',\n },\n ],\n linkHref: '/backend/auth/profile',\n expiresAfterHours: 168,\n },\n {\n type: 'auth.role.revoked',\n // Ships without push \u2014 operators re-enable it per type from the Notification Delivery settings.\n channels: ['in_app', 'email'],\n module: 'auth',\n titleKey: 'auth.notifications.role.revoked.title',\n bodyKey: 'auth.notifications.role.revoked.body',\n icon: 'user-minus',\n severity: 'warning',\n actions: [\n {\n id: 'view_profile',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/auth/profile',\n },\n ],\n linkHref: '/backend/auth/profile',\n expiresAfterHours: 168,\n },\n]\n\nexport default notificationTypes\n"],
|
|
5
|
+
"mappings": "AAEO,MAAM,oBAAkD;AAAA,EAC7D;AAAA,IACE,MAAM;AAAA;AAAA,IAEN,UAAU,CAAC,UAAU,OAAO;AAAA,IAC5B,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,MACP;AAAA,QACE,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,UAAU;AAAA,IACV,mBAAmB;AAAA,EACrB;AAAA,EACA;AAAA,IACE,MAAM;AAAA;AAAA,IAEN,UAAU,CAAC,UAAU,OAAO;AAAA,IAC5B,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC;AAAA,IACV,mBAAmB;AAAA,EACrB;AAAA,EACA;AAAA,IACE,MAAM;AAAA;AAAA,IAEN,UAAU,CAAC,UAAU,OAAO;AAAA,IAC5B,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA;AAAA,IAEV,WAAW;AAAA,IACX,SAAS;AAAA,MACP;AAAA,QACE,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,MAAM;AAAA;AAAA,IAEN,UAAU,CAAC,UAAU,OAAO;AAAA,IAC5B,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA;AAAA,IAEV,WAAW;AAAA,IACX,SAAS;AAAA,MACP;AAAA,QACE,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,UAAU;AAAA,IACV,mBAAmB;AAAA;AAAA,EACrB;AAAA,EACA;AAAA,IACE,MAAM;AAAA;AAAA,IAEN,UAAU,CAAC,UAAU,OAAO;AAAA,IAC5B,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,MACP;AAAA,QACE,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,UAAU;AAAA,IACV,mBAAmB;AAAA,EACrB;AAAA,EACA;AAAA,IACE,MAAM;AAAA;AAAA,IAEN,UAAU,CAAC,UAAU,OAAO;AAAA,IAC5B,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,MACP;AAAA,QACE,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,UAAU;AAAA,IACV,mBAAmB;AAAA,EACrB;AACF;AAEA,IAAO,wBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const notificationTypes = [
|
|
2
2
|
{
|
|
3
3
|
type: "business_rules.rule.execution_failed",
|
|
4
|
+
// Ships without push — operators re-enable it per type from the Notification Delivery settings.
|
|
5
|
+
channels: ["in_app", "email"],
|
|
4
6
|
module: "business_rules",
|
|
5
7
|
titleKey: "businessRules.notifications.rule.executionFailed.title",
|
|
6
8
|
bodyKey: "businessRules.notifications.rule.executionFailed.body",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/business_rules/notifications.ts"],
|
|
4
|
-
"sourcesContent": ["import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/notifications/types'\n\nexport const notificationTypes: NotificationTypeDefinition[] = [\n {\n type: 'business_rules.rule.execution_failed',\n module: 'business_rules',\n titleKey: 'businessRules.notifications.rule.executionFailed.title',\n bodyKey: 'businessRules.notifications.rule.executionFailed.body',\n icon: 'alert-triangle',\n severity: 'error',\n actions: [\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/business-rules/{sourceEntityId}',\n icon: 'external-link',\n },\n ],\n linkHref: '/backend/business-rules/{sourceEntityId}',\n expiresAfterHours: 168, // 7 days\n },\n]\n\nexport default notificationTypes\n"],
|
|
5
|
-
"mappings": "AAEO,MAAM,oBAAkD;AAAA,EAC7D;AAAA,IACE,MAAM;AAAA,
|
|
4
|
+
"sourcesContent": ["import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/notifications/types'\n\nexport const notificationTypes: NotificationTypeDefinition[] = [\n {\n type: 'business_rules.rule.execution_failed',\n // Ships without push \u2014 operators re-enable it per type from the Notification Delivery settings.\n channels: ['in_app', 'email'],\n module: 'business_rules',\n titleKey: 'businessRules.notifications.rule.executionFailed.title',\n bodyKey: 'businessRules.notifications.rule.executionFailed.body',\n icon: 'alert-triangle',\n severity: 'error',\n actions: [\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/business-rules/{sourceEntityId}',\n icon: 'external-link',\n },\n ],\n linkHref: '/backend/business-rules/{sourceEntityId}',\n expiresAfterHours: 168, // 7 days\n },\n]\n\nexport default notificationTypes\n"],
|
|
5
|
+
"mappings": "AAEO,MAAM,oBAAkD;AAAA,EAC7D;AAAA,IACE,MAAM;AAAA;AAAA,IAEN,UAAU,CAAC,UAAU,OAAO;AAAA,IAC5B,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,MACP;AAAA,QACE,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,UAAU;AAAA,IACV,mBAAmB;AAAA;AAAA,EACrB;AACF;AAEA,IAAO,wBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const notificationTypes = [
|
|
2
2
|
{
|
|
3
3
|
type: "catalog.product.low_stock",
|
|
4
|
+
// Ships without push — operators re-enable it per type from the Notification Delivery settings.
|
|
5
|
+
channels: ["in_app", "email"],
|
|
4
6
|
module: "catalog",
|
|
5
7
|
titleKey: "catalog.notifications.product.lowStock.title",
|
|
6
8
|
bodyKey: "catalog.notifications.product.lowStock.body",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/catalog/notifications.ts"],
|
|
4
|
-
"sourcesContent": ["import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/notifications/types'\n\nexport const notificationTypes: NotificationTypeDefinition[] = [\n {\n type: 'catalog.product.low_stock',\n module: 'catalog',\n titleKey: 'catalog.notifications.product.lowStock.title',\n bodyKey: 'catalog.notifications.product.lowStock.body',\n icon: 'package-x',\n severity: 'warning',\n actions: [\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/catalog/products/{sourceEntityId}',\n icon: 'external-link',\n },\n ],\n linkHref: '/backend/catalog/products/{sourceEntityId}',\n expiresAfterHours: 72, // 3 days\n },\n]\n\nexport default notificationTypes\n"],
|
|
5
|
-
"mappings": "AAEO,MAAM,oBAAkD;AAAA,EAC7D;AAAA,IACE,MAAM;AAAA,
|
|
4
|
+
"sourcesContent": ["import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/notifications/types'\n\nexport const notificationTypes: NotificationTypeDefinition[] = [\n {\n type: 'catalog.product.low_stock',\n // Ships without push \u2014 operators re-enable it per type from the Notification Delivery settings.\n channels: ['in_app', 'email'],\n module: 'catalog',\n titleKey: 'catalog.notifications.product.lowStock.title',\n bodyKey: 'catalog.notifications.product.lowStock.body',\n icon: 'package-x',\n severity: 'warning',\n actions: [\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/catalog/products/{sourceEntityId}',\n icon: 'external-link',\n },\n ],\n linkHref: '/backend/catalog/products/{sourceEntityId}',\n expiresAfterHours: 72, // 3 days\n },\n]\n\nexport default notificationTypes\n"],
|
|
5
|
+
"mappings": "AAEO,MAAM,oBAAkD;AAAA,EAC7D;AAAA,IACE,MAAM;AAAA;AAAA,IAEN,UAAU,CAAC,UAAU,OAAO;AAAA,IAC5B,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,MACP;AAAA,QACE,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,UAAU;AAAA,IACV,mBAAmB;AAAA;AAAA,EACrB;AACF;AAEA,IAAO,wBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -12,6 +12,15 @@ 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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/communication_channels/acl.ts"],
|
|
4
|
-
"sourcesContent": ["export const features = [\n { id: 'communication_channels.view', title: 'View communication channels', module: 'communication_channels' },\n { id: 'communication_channels.manage', title: 'Manage communication channels', module: 'communication_channels' },\n { id: 'communication_channels.react', title: 'React to channel messages', module: 'communication_channels' },\n { id: 'communication_channels.assign', title: 'Assign channel conversations', module: 'communication_channels' },\n /**\n * Per-user channel ownership (added by the email integration spec).\n *\n * Gates the \"Connect my mailbox\" flow on the per-user profile page. Split from\n * `communication_channels.manage` so policy can disable new linking while\n * preserving existing accounts (e.g. during a security incident response).\n * Default-granted to all roles in `setup.ts`.\n */\n { id: 'communication_channels.connect_user_channel', title: 'Connect own communication channel', module: 'communication_channels' },\n /**\n * Reserved for a future v2 team-oversight capability. NOT consulted in v1:\n * personal mailboxes (`CommunicationChannel.user_id` set) follow the strict\n * owner-only privacy model, so this feature grants NO cross-user channel view.\n * The admin channels list (`GET /api/communication_channels/channels`) returns\n * `user_id IS NULL` rows only; personal mailboxes surface exclusively on the\n * owner's profile page and are never exposed to admins/superadmins in v1.\n * Granted to `superadmin` + `admin` only so the inert grant is in place ahead\n * of the audited v2 oversight feature that will re-activate it.\n */\n { id: 'communication_channels.admin', title: 'Administer all communication channels (tenant-wide)', module: 'communication_channels' },\n /**\n * Trigger the \"Import history\" job for a channel \u2014 fetch older messages\n * the channel never saw at bootstrap (Spec B \u00A7 Phase B6). Separate from\n * `manage` so policy can gate bulk historical imports during quiet hours\n * or cost-controlled rollouts while leaving normal channel CRUD open.\n */\n { id: 'communication_channels.channel.import_history', title: 'Import channel history', module: 'communication_channels' },\n /**\n * Manage provider push delivery (Spec C \u2014 Gmail Pub/Sub push\n * subscriptions). Gates the \"Re-register push\" operator button and any\n * future push-status manipulation. Granted to admin + superadmin only \u2014\n * regular users don't need to think about whether mail arrives via\n * push or polling, the system handles it.\n */\n { id: 'communication_channels.channel.push.manage', title: 'Manage push delivery', module: 'communication_channels' },\n] as const\n\nexport default features\n"],
|
|
5
|
-
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,+BAA+B,OAAO,+BAA+B,QAAQ,yBAAyB;AAAA,EAC5G,EAAE,IAAI,iCAAiC,OAAO,iCAAiC,QAAQ,yBAAyB;AAAA,EAChH,EAAE,IAAI,gCAAgC,OAAO,6BAA6B,QAAQ,yBAAyB;AAAA,EAC3G,EAAE,IAAI,iCAAiC,OAAO,gCAAgC,QAAQ,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS/G,EAAE,IAAI,+CAA+C,OAAO,qCAAqC,QAAQ,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,
|
|
4
|
+
"sourcesContent": ["export const features = [\n { id: 'communication_channels.view', title: 'View communication channels', module: 'communication_channels' },\n { id: 'communication_channels.manage', title: 'Manage communication channels', module: 'communication_channels' },\n { id: 'communication_channels.react', title: 'React to channel messages', module: 'communication_channels' },\n { id: 'communication_channels.assign', title: 'Assign channel conversations', module: 'communication_channels' },\n /**\n * Per-user channel ownership (added by the email integration spec).\n *\n * Gates the \"Connect my mailbox\" flow on the per-user profile page. Split from\n * `communication_channels.manage` so policy can disable new linking while\n * preserving existing accounts (e.g. during a security incident response).\n * Default-granted to all roles in `setup.ts`.\n */\n { id: 'communication_channels.connect_user_channel', title: 'Connect own communication channel', module: 'communication_channels' },\n /**\n * Connect a tenant-wide channel (`CommunicationChannel.user_id IS NULL`) that\n * serves every user in the tenant \u2014 used for push providers (FCM/APNs/Expo)\n * whose service account / signing key is shared tenant infrastructure. Gates\n * `POST /channels/connect/tenant-credentials`. Admin/superadmin only: unlike\n * `connect_user_channel` (everyone links their own mailbox), connecting shared\n * push credentials is an administrative action.\n */\n { id: 'communication_channels.connect_tenant_channel', title: 'Connect tenant-wide communication channel', module: 'communication_channels' },\n /**\n * Reserved for a future v2 team-oversight capability. NOT consulted in v1:\n * personal mailboxes (`CommunicationChannel.user_id` set) follow the strict\n * owner-only privacy model, so this feature grants NO cross-user channel view.\n * The admin channels list (`GET /api/communication_channels/channels`) returns\n * `user_id IS NULL` rows only; personal mailboxes surface exclusively on the\n * owner's profile page and are never exposed to admins/superadmins in v1.\n * Granted to `superadmin` + `admin` only so the inert grant is in place ahead\n * of the audited v2 oversight feature that will re-activate it.\n */\n { id: 'communication_channels.admin', title: 'Administer all communication channels (tenant-wide)', module: 'communication_channels' },\n /**\n * Trigger the \"Import history\" job for a channel \u2014 fetch older messages\n * the channel never saw at bootstrap (Spec B \u00A7 Phase B6). Separate from\n * `manage` so policy can gate bulk historical imports during quiet hours\n * or cost-controlled rollouts while leaving normal channel CRUD open.\n */\n { id: 'communication_channels.channel.import_history', title: 'Import channel history', module: 'communication_channels' },\n /**\n * Manage provider push delivery (Spec C \u2014 Gmail Pub/Sub push\n * subscriptions). Gates the \"Re-register push\" operator button and any\n * future push-status manipulation. Granted to admin + superadmin only \u2014\n * regular users don't need to think about whether mail arrives via\n * push or polling, the system handles it.\n */\n { id: 'communication_channels.channel.push.manage', title: 'Manage push delivery', module: 'communication_channels' },\n] as const\n\nexport default features\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,+BAA+B,OAAO,+BAA+B,QAAQ,yBAAyB;AAAA,EAC5G,EAAE,IAAI,iCAAiC,OAAO,iCAAiC,QAAQ,yBAAyB;AAAA,EAChH,EAAE,IAAI,gCAAgC,OAAO,6BAA6B,QAAQ,yBAAyB;AAAA,EAC3G,EAAE,IAAI,iCAAiC,OAAO,gCAAgC,QAAQ,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS/G,EAAE,IAAI,+CAA+C,OAAO,qCAAqC,QAAQ,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASlI,EAAE,IAAI,iDAAiD,OAAO,6CAA6C,QAAQ,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAW5I,EAAE,IAAI,gCAAgC,OAAO,uDAAuD,QAAQ,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOrI,EAAE,IAAI,iDAAiD,OAAO,0BAA0B,QAAQ,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQzH,EAAE,IAAI,8CAA8C,OAAO,wBAAwB,QAAQ,yBAAyB;AACtH;AAEA,IAAO,cAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,133 @@
|
|
|
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 { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
6
|
+
import { CommunicationChannel } from "../../../../../data/entities.js";
|
|
7
|
+
import {
|
|
8
|
+
ChannelAccessDeniedError,
|
|
9
|
+
assertCanManageChannel,
|
|
10
|
+
channelOrgScopeWhere
|
|
11
|
+
} from "../../../../../lib/access-control.js";
|
|
12
|
+
import {
|
|
13
|
+
COMMUNICATION_CHANNELS_ADMIN_DELETE_CHANNEL_COMMAND_ID
|
|
14
|
+
} from "../../../../../commands/admin-delete-channel.js";
|
|
15
|
+
import { validateRouteMutationGuard } from "../../../../../lib/route-mutation-guard.js";
|
|
16
|
+
const metadata = {
|
|
17
|
+
path: "/communication_channels/admin/channels/[id]",
|
|
18
|
+
DELETE: {
|
|
19
|
+
requireAuth: true,
|
|
20
|
+
requireFeatures: ["communication_channels.admin"]
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
async function DELETE(req, context) {
|
|
24
|
+
const { id } = await context.params;
|
|
25
|
+
if (!z.string().uuid().safeParse(id).success) {
|
|
26
|
+
return NextResponse.json({ error: "Invalid channel id" }, { status: 400 });
|
|
27
|
+
}
|
|
28
|
+
const auth = await getAuthFromRequest(req);
|
|
29
|
+
if (!auth?.sub || !auth?.tenantId) {
|
|
30
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
31
|
+
}
|
|
32
|
+
const container = await createRequestContainer();
|
|
33
|
+
const em = container.resolve("em").fork();
|
|
34
|
+
const organizationId = auth.orgId ?? null;
|
|
35
|
+
const dscope = { tenantId: auth.tenantId, organizationId };
|
|
36
|
+
const channel = await findOneWithDecryption(
|
|
37
|
+
em,
|
|
38
|
+
CommunicationChannel,
|
|
39
|
+
{
|
|
40
|
+
id,
|
|
41
|
+
tenantId: auth.tenantId,
|
|
42
|
+
...channelOrgScopeWhere(organizationId),
|
|
43
|
+
deletedAt: null
|
|
44
|
+
},
|
|
45
|
+
void 0,
|
|
46
|
+
dscope
|
|
47
|
+
);
|
|
48
|
+
if (!channel || channel.userId != null) {
|
|
49
|
+
return NextResponse.json({ error: "Channel not found" }, { status: 404 });
|
|
50
|
+
}
|
|
51
|
+
let userFeatures = [];
|
|
52
|
+
try {
|
|
53
|
+
const rbac = container.resolve("rbacService");
|
|
54
|
+
const acl = await rbac.loadAcl(auth.sub, {
|
|
55
|
+
tenantId: auth.tenantId,
|
|
56
|
+
organizationId
|
|
57
|
+
});
|
|
58
|
+
userFeatures = acl?.isSuperAdmin ? ["*"] : Array.isArray(acl?.features) ? acl.features : [];
|
|
59
|
+
} catch {
|
|
60
|
+
userFeatures = [];
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
assertCanManageChannel(
|
|
64
|
+
{ userId: channel.userId },
|
|
65
|
+
auth.sub,
|
|
66
|
+
userFeatures,
|
|
67
|
+
"communication_channels.manage"
|
|
68
|
+
);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (err instanceof ChannelAccessDeniedError) {
|
|
71
|
+
return NextResponse.json({ error: "Channel not found" }, { status: 404 });
|
|
72
|
+
}
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
const guard = await validateRouteMutationGuard({
|
|
76
|
+
container,
|
|
77
|
+
req,
|
|
78
|
+
auth,
|
|
79
|
+
input: {
|
|
80
|
+
resourceKind: "communication_channels.channel",
|
|
81
|
+
resourceId: id,
|
|
82
|
+
operation: "delete"
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
if ("response" in guard) return guard.response;
|
|
86
|
+
const commandBus = container.resolve("commandBus");
|
|
87
|
+
const channelOrganizationId = channel.organizationId ?? null;
|
|
88
|
+
const input = {
|
|
89
|
+
channelId: id,
|
|
90
|
+
scope: { tenantId: auth.tenantId, organizationId: channelOrganizationId }
|
|
91
|
+
};
|
|
92
|
+
const { result } = await commandBus.execute(
|
|
93
|
+
COMMUNICATION_CHANNELS_ADMIN_DELETE_CHANNEL_COMMAND_ID,
|
|
94
|
+
{
|
|
95
|
+
input,
|
|
96
|
+
ctx: {
|
|
97
|
+
container,
|
|
98
|
+
auth,
|
|
99
|
+
organizationScope: null,
|
|
100
|
+
selectedOrganizationId: organizationId,
|
|
101
|
+
organizationIds: organizationId ? [organizationId] : null
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
if (result.status !== "deleted") {
|
|
106
|
+
return NextResponse.json({ error: "Channel not found" }, { status: 404 });
|
|
107
|
+
}
|
|
108
|
+
await guard.afterSuccess();
|
|
109
|
+
return new NextResponse(null, { status: 204 });
|
|
110
|
+
}
|
|
111
|
+
const openApi = {
|
|
112
|
+
tags: ["CommunicationChannels"],
|
|
113
|
+
methods: {
|
|
114
|
+
DELETE: {
|
|
115
|
+
summary: "Admin delete (soft-delete) a tenant-wide communication channel",
|
|
116
|
+
tags: ["CommunicationChannels"],
|
|
117
|
+
responses: [
|
|
118
|
+
{ status: 204, description: "Channel deleted" },
|
|
119
|
+
{ status: 400, description: "Invalid channel id" },
|
|
120
|
+
{ status: 401, description: "Unauthorized" },
|
|
121
|
+
{ status: 404, description: "Channel not found or not a tenant-wide channel" }
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
var route_default = DELETE;
|
|
127
|
+
export {
|
|
128
|
+
DELETE,
|
|
129
|
+
route_default as default,
|
|
130
|
+
metadata,
|
|
131
|
+
openApi
|
|
132
|
+
};
|
|
133
|
+
//# sourceMappingURL=route.js.map
|