@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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const notificationTypes = [
|
|
2
2
|
{
|
|
3
3
|
type: "messages.new",
|
|
4
|
+
// Ships without push — operators re-enable it per type from the Notification Delivery settings.
|
|
5
|
+
channels: ["in_app", "email"],
|
|
4
6
|
module: "messages",
|
|
5
7
|
titleKey: "messages.notifications.new.title",
|
|
6
8
|
bodyKey: "messages.notifications.new.body",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/messages/notifications.ts"],
|
|
4
|
-
"sourcesContent": ["import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/notifications/types'\n\nexport const notificationTypes: NotificationTypeDefinition[] = [\n {\n type: 'messages.new',\n module: 'messages',\n titleKey: 'messages.notifications.new.title',\n bodyKey: 'messages.notifications.new.body',\n icon: 'mail',\n severity: 'info',\n actions: [\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/messages/{sourceEntityId}',\n icon: 'external-link',\n },\n ],\n linkHref: '/backend/messages/{sourceEntityId}',\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: 'messages.new',\n // Ships without push \u2014 operators re-enable it per type from the Notification Delivery settings.\n channels: ['in_app', 'email'],\n module: 'messages',\n titleKey: 'messages.notifications.new.title',\n bodyKey: 'messages.notifications.new.body',\n icon: 'mail',\n severity: 'info',\n actions: [\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/messages/{sourceEntityId}',\n icon: 'external-link',\n },\n ],\n linkHref: '/backend/messages/{sourceEntityId}',\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;AACF;AAEA,IAAO,wBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
const features = [
|
|
2
2
|
{ id: "notifications.view", title: "View own notifications", module: "notifications" },
|
|
3
3
|
{ id: "notifications.create", title: "Create notifications for others", module: "notifications" },
|
|
4
|
-
{ id: "notifications.manage", title: "Manage all notifications", module: "notifications" }
|
|
4
|
+
{ id: "notifications.manage", title: "Manage all notifications", module: "notifications" },
|
|
5
|
+
{ id: "notifications.manage_preferences", title: "Manage own notification preferences", module: "notifications" },
|
|
6
|
+
{ id: "notifications.manage_user_preferences", title: "Manage other users' notification preferences", module: "notifications" }
|
|
5
7
|
];
|
|
6
8
|
var acl_default = features;
|
|
7
9
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/notifications/acl.ts"],
|
|
4
|
-
"sourcesContent": ["export const features = [\n { id: 'notifications.view', title: 'View own notifications', module: 'notifications' },\n { id: 'notifications.create', title: 'Create notifications for others', module: 'notifications' },\n { id: 'notifications.manage', title: 'Manage all notifications', module: 'notifications' },\n]\n\nexport default features\n"],
|
|
5
|
-
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,sBAAsB,OAAO,0BAA0B,QAAQ,gBAAgB;AAAA,EACrF,EAAE,IAAI,wBAAwB,OAAO,mCAAmC,QAAQ,gBAAgB;AAAA,EAChG,EAAE,IAAI,wBAAwB,OAAO,4BAA4B,QAAQ,gBAAgB;
|
|
4
|
+
"sourcesContent": ["export const features = [\n { id: 'notifications.view', title: 'View own notifications', module: 'notifications' },\n { id: 'notifications.create', title: 'Create notifications for others', module: 'notifications' },\n { id: 'notifications.manage', title: 'Manage all notifications', module: 'notifications' },\n { id: 'notifications.manage_preferences', title: 'Manage own notification preferences', module: 'notifications' },\n { id: 'notifications.manage_user_preferences', title: \"Manage other users' notification preferences\", module: 'notifications' },\n]\n\nexport default features\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,sBAAsB,OAAO,0BAA0B,QAAQ,gBAAgB;AAAA,EACrF,EAAE,IAAI,wBAAwB,OAAO,mCAAmC,QAAQ,gBAAgB;AAAA,EAChG,EAAE,IAAI,wBAAwB,OAAO,4BAA4B,QAAQ,gBAAgB;AAAA,EACzF,EAAE,IAAI,oCAAoC,OAAO,uCAAuC,QAAQ,gBAAgB;AAAA,EAChH,EAAE,IAAI,yCAAyC,OAAO,gDAAgD,QAAQ,gBAAgB;AAChI;AAEA,IAAO,cAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
6
|
+
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
7
|
+
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
8
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
9
|
+
import { User } from "../../../../auth/data/entities.js";
|
|
10
|
+
import { assertActorCanAccessUserTarget } from "../../../../auth/lib/grantChecks.js";
|
|
11
|
+
import { resolveOrganizationScopeForRequest } from "../../../../directory/utils/organizationScope.js";
|
|
12
|
+
import { resolveNotificationPreferenceService } from "../../../lib/notificationPreferenceService.js";
|
|
13
|
+
import {
|
|
14
|
+
runGuardedNotificationWrite,
|
|
15
|
+
notificationValidationErrorResponse,
|
|
16
|
+
NOTIFICATION_PREFERENCE_RESOURCE_KIND
|
|
17
|
+
} from "../../../lib/routeHelpers.js";
|
|
18
|
+
import { PREFERENCE_UPDATED_EVENT, emitNotificationEvent } from "../../../events.js";
|
|
19
|
+
import {
|
|
20
|
+
adminPreferencesQuerySchema,
|
|
21
|
+
adminUpdatePreferencesSchema,
|
|
22
|
+
notificationPreferenceItemSchema
|
|
23
|
+
} from "../../../data/validators.js";
|
|
24
|
+
import { errorResponseSchema } from "../../openapi.js";
|
|
25
|
+
const metadata = {
|
|
26
|
+
GET: { requireAuth: true, requireFeatures: ["notifications.manage_user_preferences"] },
|
|
27
|
+
PUT: { requireAuth: true, requireFeatures: ["notifications.manage_user_preferences"] }
|
|
28
|
+
};
|
|
29
|
+
const unauthorized = async () => {
|
|
30
|
+
const { t } = await resolveTranslations();
|
|
31
|
+
return NextResponse.json({ error: t("api.errors.unauthorized", "Unauthorized") }, { status: 401 });
|
|
32
|
+
};
|
|
33
|
+
async function authorizeTargetUser(container, em, auth, req, targetUserId) {
|
|
34
|
+
const { t } = await resolveTranslations();
|
|
35
|
+
const user = await findOneWithDecryption(
|
|
36
|
+
em,
|
|
37
|
+
User,
|
|
38
|
+
{ id: targetUserId, tenantId: auth.tenantId, deletedAt: null },
|
|
39
|
+
void 0,
|
|
40
|
+
{ tenantId: auth.tenantId, organizationId: null }
|
|
41
|
+
);
|
|
42
|
+
if (!user) {
|
|
43
|
+
return NextResponse.json({ error: t("notifications.preferences.userNotFound", "User not found") }, { status: 404 });
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const rbacService = container.resolve("rbacService");
|
|
47
|
+
await assertActorCanAccessUserTarget({
|
|
48
|
+
em,
|
|
49
|
+
rbacService,
|
|
50
|
+
actorUserId: auth.sub,
|
|
51
|
+
tenantId: auth.tenantId,
|
|
52
|
+
organizationId: auth.orgId ?? null,
|
|
53
|
+
targetUserId,
|
|
54
|
+
organizationScope: await resolveOrganizationScopeForRequest({
|
|
55
|
+
container,
|
|
56
|
+
auth,
|
|
57
|
+
request: req,
|
|
58
|
+
tenantId: auth.tenantId
|
|
59
|
+
})
|
|
60
|
+
});
|
|
61
|
+
} catch (err) {
|
|
62
|
+
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
async function GET(req) {
|
|
68
|
+
const auth = await getAuthFromRequest(req);
|
|
69
|
+
if (!auth?.sub || !auth.tenantId) return await unauthorized();
|
|
70
|
+
const url = new URL(req.url);
|
|
71
|
+
const parsed = adminPreferencesQuerySchema.safeParse({ userId: url.searchParams.get("userId") ?? void 0 });
|
|
72
|
+
if (!parsed.success) return notificationValidationErrorResponse(parsed.error);
|
|
73
|
+
const container = await createRequestContainer();
|
|
74
|
+
try {
|
|
75
|
+
const em = container.resolve("em");
|
|
76
|
+
const denied = await authorizeTargetUser(container, em, { ...auth, sub: auth.sub, tenantId: auth.tenantId }, req, parsed.data.userId);
|
|
77
|
+
if (denied) return denied;
|
|
78
|
+
const service = resolveNotificationPreferenceService(container);
|
|
79
|
+
const scope = { tenantId: auth.tenantId, userId: parsed.data.userId };
|
|
80
|
+
const rows = await service.listForUser(scope);
|
|
81
|
+
const items = rows.map((row) => ({
|
|
82
|
+
notificationTypeId: row.notificationTypeId,
|
|
83
|
+
channel: row.channel,
|
|
84
|
+
enabled: row.enabled
|
|
85
|
+
}));
|
|
86
|
+
return NextResponse.json({ items });
|
|
87
|
+
} finally {
|
|
88
|
+
const disposable = container;
|
|
89
|
+
if (typeof disposable.dispose === "function") await disposable.dispose();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function PUT(req) {
|
|
93
|
+
const auth = await getAuthFromRequest(req);
|
|
94
|
+
if (!auth?.sub || !auth.tenantId) return await unauthorized();
|
|
95
|
+
const parsed = adminUpdatePreferencesSchema.safeParse(await readJsonSafe(req, {}));
|
|
96
|
+
if (!parsed.success) return notificationValidationErrorResponse(parsed.error);
|
|
97
|
+
const container = await createRequestContainer();
|
|
98
|
+
try {
|
|
99
|
+
const em = container.resolve("em");
|
|
100
|
+
const denied = await authorizeTargetUser(container, em, { ...auth, sub: auth.sub, tenantId: auth.tenantId }, req, parsed.data.userId);
|
|
101
|
+
if (denied) return denied;
|
|
102
|
+
const service = resolveNotificationPreferenceService(container);
|
|
103
|
+
const scope = { tenantId: auth.tenantId, userId: parsed.data.userId };
|
|
104
|
+
const guarded = await runGuardedNotificationWrite(
|
|
105
|
+
container,
|
|
106
|
+
{ tenantId: auth.tenantId, organizationId: auth.orgId ?? null, userId: auth.sub },
|
|
107
|
+
req,
|
|
108
|
+
{
|
|
109
|
+
resourceKind: NOTIFICATION_PREFERENCE_RESOURCE_KIND,
|
|
110
|
+
resourceId: parsed.data.userId,
|
|
111
|
+
operation: "update",
|
|
112
|
+
payload: parsed.data
|
|
113
|
+
},
|
|
114
|
+
() => service.setPreferences(
|
|
115
|
+
scope,
|
|
116
|
+
parsed.data.preferences.map((p) => ({ typeId: p.notificationTypeId, channel: p.channel, enabled: p.enabled }))
|
|
117
|
+
)
|
|
118
|
+
);
|
|
119
|
+
if (!guarded.ok) return guarded.response;
|
|
120
|
+
if (guarded.result > 0) {
|
|
121
|
+
await emitNotificationEvent(PREFERENCE_UPDATED_EVENT, {
|
|
122
|
+
tenantId: auth.tenantId,
|
|
123
|
+
organizationId: auth.orgId ?? null,
|
|
124
|
+
userId: parsed.data.userId
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return NextResponse.json({ ok: true });
|
|
128
|
+
} finally {
|
|
129
|
+
const disposable = container;
|
|
130
|
+
if (typeof disposable.dispose === "function") await disposable.dispose();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const openApi = {
|
|
134
|
+
GET: {
|
|
135
|
+
summary: "Get a user's notification preferences (admin)",
|
|
136
|
+
description: "Returns a target user's stored channel preferences. Requires notifications.manage_user_preferences.",
|
|
137
|
+
tags: ["Notifications"],
|
|
138
|
+
parameters: [{ name: "userId", in: "query", required: true, schema: { type: "string", format: "uuid" } }],
|
|
139
|
+
responses: {
|
|
140
|
+
200: {
|
|
141
|
+
description: "Stored preferences",
|
|
142
|
+
content: { "application/json": { schema: z.object({ items: z.array(notificationPreferenceItemSchema) }) } }
|
|
143
|
+
},
|
|
144
|
+
401: { description: "Unauthorized", content: { "application/json": { schema: errorResponseSchema } } },
|
|
145
|
+
404: { description: "User not found", content: { "application/json": { schema: errorResponseSchema } } }
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
PUT: {
|
|
149
|
+
summary: "Update a user's notification preferences (admin)",
|
|
150
|
+
description: "Bulk-updates a target user's channel preferences. Requires notifications.manage_user_preferences.",
|
|
151
|
+
tags: ["Notifications"],
|
|
152
|
+
requestBody: { required: true, content: { "application/json": { schema: adminUpdatePreferencesSchema } } },
|
|
153
|
+
responses: {
|
|
154
|
+
200: { description: "Preferences updated", content: { "application/json": { schema: z.object({ ok: z.boolean() }) } } },
|
|
155
|
+
400: { description: "Invalid request body", content: { "application/json": { schema: errorResponseSchema } } },
|
|
156
|
+
401: { description: "Unauthorized", content: { "application/json": { schema: errorResponseSchema } } },
|
|
157
|
+
404: { description: "User not found", content: { "application/json": { schema: errorResponseSchema } } }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
export {
|
|
162
|
+
GET,
|
|
163
|
+
PUT,
|
|
164
|
+
metadata,
|
|
165
|
+
openApi
|
|
166
|
+
};
|
|
167
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/notifications/api/admin/preferences/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { User } from '../../../../auth/data/entities'\nimport { assertActorCanAccessUserTarget } from '../../../../auth/lib/grantChecks'\nimport { resolveOrganizationScopeForRequest } from '../../../../directory/utils/organizationScope'\nimport type { RbacService } from '../../../../auth/services/rbacService'\nimport { resolveNotificationPreferenceService, type NotificationPreferenceScope } from '../../../lib/notificationPreferenceService'\nimport {\n runGuardedNotificationWrite,\n notificationValidationErrorResponse,\n NOTIFICATION_PREFERENCE_RESOURCE_KIND,\n} from '../../../lib/routeHelpers'\nimport { PREFERENCE_UPDATED_EVENT, emitNotificationEvent } from '../../../events'\nimport {\n adminPreferencesQuerySchema,\n adminUpdatePreferencesSchema,\n notificationPreferenceItemSchema,\n} from '../../../data/validators'\nimport { errorResponseSchema } from '../../openapi'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['notifications.manage_user_preferences'] },\n PUT: { requireAuth: true, requireFeatures: ['notifications.manage_user_preferences'] },\n}\n\nconst unauthorized = async () => {\n const { t } = await resolveTranslations()\n return NextResponse.json({ error: t('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n}\n\n/**\n * Authorize the acting admin to manage a target user's preferences. Returns an error response to\n * send, or null when access is granted. Two gates, mirroring the platform's user-management scoping:\n * - 404 when the user does not exist in the acting tenant (existence/tenant check \u2014 the shared\n * `assertActorCanAccessUserTarget` delegates silently on a fully-missing target).\n * - standard org scoping via `assertActorCanAccessUserTarget` (403 out-of-org / 404 cross-tenant,\n * super-admin and `__all__` bypass) \u2014 the same guard `auth` user CRUD uses, instead of a\n * hand-rolled tenant-only check.\n */\nasync function authorizeTargetUser(\n container: AwilixContainer,\n em: EntityManager,\n auth: AuthContext & { sub: string; tenantId: string },\n req: Request,\n targetUserId: string,\n): Promise<NextResponse | null> {\n const { t } = await resolveTranslations()\n const user = await findOneWithDecryption(\n em,\n User,\n { id: targetUserId, tenantId: auth.tenantId, deletedAt: null },\n undefined,\n { tenantId: auth.tenantId, organizationId: null },\n )\n if (!user) {\n return NextResponse.json({ error: t('notifications.preferences.userNotFound', 'User not found') }, { status: 404 })\n }\n try {\n const rbacService = container.resolve('rbacService') as RbacService\n await assertActorCanAccessUserTarget({\n em,\n rbacService,\n actorUserId: auth.sub,\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n targetUserId,\n organizationScope: await resolveOrganizationScopeForRequest({\n container,\n auth,\n request: req,\n tenantId: auth.tenantId,\n }),\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n return null\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub || !auth.tenantId) return await unauthorized()\n\n const url = new URL(req.url)\n const parsed = adminPreferencesQuerySchema.safeParse({ userId: url.searchParams.get('userId') ?? undefined })\n if (!parsed.success) return notificationValidationErrorResponse(parsed.error)\n\n const container = await createRequestContainer()\n try {\n const em = container.resolve('em') as EntityManager\n const denied = await authorizeTargetUser(container, em, { ...auth, sub: auth.sub, tenantId: auth.tenantId }, req, parsed.data.userId)\n if (denied) return denied\n const service = resolveNotificationPreferenceService(container)\n const scope: NotificationPreferenceScope = { tenantId: auth.tenantId, userId: parsed.data.userId }\n const rows = await service.listForUser(scope)\n const items = rows.map((row) => ({\n notificationTypeId: row.notificationTypeId,\n channel: row.channel,\n enabled: row.enabled,\n }))\n return NextResponse.json({ items })\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') await disposable.dispose()\n }\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub || !auth.tenantId) return await unauthorized()\n\n const parsed = adminUpdatePreferencesSchema.safeParse(await readJsonSafe(req, {}))\n if (!parsed.success) return notificationValidationErrorResponse(parsed.error)\n\n const container = await createRequestContainer()\n try {\n const em = container.resolve('em') as EntityManager\n const denied = await authorizeTargetUser(container, em, { ...auth, sub: auth.sub, tenantId: auth.tenantId }, req, parsed.data.userId)\n if (denied) return denied\n const service = resolveNotificationPreferenceService(container)\n const scope: NotificationPreferenceScope = { tenantId: auth.tenantId, userId: parsed.data.userId }\n const guarded = await runGuardedNotificationWrite(\n container,\n { tenantId: auth.tenantId, organizationId: auth.orgId ?? null, userId: auth.sub },\n req,\n {\n resourceKind: NOTIFICATION_PREFERENCE_RESOURCE_KIND,\n resourceId: parsed.data.userId,\n operation: 'update',\n payload: parsed.data as Record<string, unknown>,\n },\n () =>\n service.setPreferences(\n scope,\n parsed.data.preferences.map((p) => ({ typeId: p.notificationTypeId, channel: p.channel, enabled: p.enabled })),\n ),\n )\n if (!guarded.ok) return guarded.response\n\n // Skip the event on no-op writes (nothing actually changed).\n if (guarded.result > 0) {\n await emitNotificationEvent(PREFERENCE_UPDATED_EVENT, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n userId: parsed.data.userId,\n })\n }\n\n return NextResponse.json({ ok: true })\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') await disposable.dispose()\n }\n}\n\nexport const openApi = {\n GET: {\n summary: \"Get a user's notification preferences (admin)\",\n description: \"Returns a target user's stored channel preferences. Requires notifications.manage_user_preferences.\",\n tags: ['Notifications'],\n parameters: [{ name: 'userId', in: 'query', required: true, schema: { type: 'string', format: 'uuid' } }],\n responses: {\n 200: {\n description: 'Stored preferences',\n content: { 'application/json': { schema: z.object({ items: z.array(notificationPreferenceItemSchema) }) } },\n },\n 401: { description: 'Unauthorized', content: { 'application/json': { schema: errorResponseSchema } } },\n 404: { description: 'User not found', content: { 'application/json': { schema: errorResponseSchema } } },\n },\n },\n PUT: {\n summary: \"Update a user's notification preferences (admin)\",\n description: \"Bulk-updates a target user's channel preferences. Requires notifications.manage_user_preferences.\",\n tags: ['Notifications'],\n requestBody: { required: true, content: { 'application/json': { schema: adminUpdatePreferencesSchema } } },\n responses: {\n 200: { description: 'Preferences updated', content: { 'application/json': { schema: z.object({ ok: z.boolean() }) } } },\n 400: { description: 'Invalid request body', content: { 'application/json': { schema: errorResponseSchema } } },\n 401: { description: 'Unauthorized', content: { 'application/json': { schema: errorResponseSchema } } },\n 404: { description: 'User not found', content: { 'application/json': { schema: errorResponseSchema } } },\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
5
|
+
import { getNotificationChannels } from "../../lib/notification-channel-registry.js";
|
|
6
|
+
import { errorResponseSchema } from "../openapi.js";
|
|
7
|
+
const metadata = {
|
|
8
|
+
GET: { requireAuth: true, requireFeatures: ["notifications.view"] }
|
|
9
|
+
};
|
|
10
|
+
const channelItemSchema = z.object({
|
|
11
|
+
id: z.string(),
|
|
12
|
+
labelKey: z.string(),
|
|
13
|
+
descriptionKey: z.string().nullable()
|
|
14
|
+
});
|
|
15
|
+
async function GET(req) {
|
|
16
|
+
const { t } = await resolveTranslations();
|
|
17
|
+
const auth = await getAuthFromRequest(req);
|
|
18
|
+
if (!auth?.sub || !auth.tenantId) {
|
|
19
|
+
return NextResponse.json({ error: t("api.errors.unauthorized", "Unauthorized") }, { status: 401 });
|
|
20
|
+
}
|
|
21
|
+
const items = getNotificationChannels().map((channel) => ({
|
|
22
|
+
id: channel.id,
|
|
23
|
+
labelKey: channel.labelKey,
|
|
24
|
+
descriptionKey: channel.descriptionKey ?? null
|
|
25
|
+
}));
|
|
26
|
+
return NextResponse.json({ items });
|
|
27
|
+
}
|
|
28
|
+
const openApi = {
|
|
29
|
+
GET: {
|
|
30
|
+
summary: "List notification delivery channels",
|
|
31
|
+
description: "Returns the registered delivery channel catalogue (in-app, email, push, \u2026) so a preferences screen can render one column per channel without a hardcoded list.",
|
|
32
|
+
tags: ["Notifications"],
|
|
33
|
+
responses: {
|
|
34
|
+
200: {
|
|
35
|
+
description: "Notification channel catalogue",
|
|
36
|
+
content: {
|
|
37
|
+
"application/json": {
|
|
38
|
+
schema: z.object({ items: z.array(channelItemSchema) })
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
401: {
|
|
43
|
+
description: "Unauthorized",
|
|
44
|
+
content: { "application/json": { schema: errorResponseSchema } }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
export {
|
|
50
|
+
GET,
|
|
51
|
+
metadata,
|
|
52
|
+
openApi
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/notifications/api/channels/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { getNotificationChannels } from '../../lib/notification-channel-registry'\nimport { errorResponseSchema } from '../openapi'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['notifications.view'] },\n}\n\nconst channelItemSchema = z.object({\n id: z.string(),\n labelKey: z.string(),\n descriptionKey: z.string().nullable(),\n})\n\nexport async function GET(req: Request) {\n const { t } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub || !auth.tenantId) {\n return NextResponse.json({ error: t('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n\n const items = getNotificationChannels().map((channel) => ({\n id: channel.id,\n labelKey: channel.labelKey,\n descriptionKey: channel.descriptionKey ?? null,\n }))\n return NextResponse.json({ items })\n}\n\nexport const openApi = {\n GET: {\n summary: 'List notification delivery channels',\n description:\n 'Returns the registered delivery channel catalogue (in-app, email, push, \u2026) so a preferences screen can render one column per channel without a hardcoded list.',\n tags: ['Notifications'],\n responses: {\n 200: {\n description: 'Notification channel catalogue',\n content: {\n 'application/json': {\n schema: z.object({ items: z.array(channelItemSchema) }),\n },\n },\n },\n 401: {\n description: 'Unauthorized',\n content: { 'application/json': { schema: errorResponseSchema } },\n },\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,2BAA2B;AACpC,SAAS,+BAA+B;AACxC,SAAS,2BAA2B;AAE7B,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,oBAAoB,EAAE;AACpE;AAEA,MAAM,oBAAoB,EAAE,OAAO;AAAA,EACjC,IAAI,EAAE,OAAO;AAAA,EACb,UAAU,EAAE,OAAO;AAAA,EACnB,gBAAgB,EAAE,OAAO,EAAE,SAAS;AACtC,CAAC;AAED,eAAsB,IAAI,KAAc;AACtC,QAAM,EAAE,EAAE,IAAI,MAAM,oBAAoB;AACxC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,OAAO,CAAC,KAAK,UAAU;AAChC,WAAO,aAAa,KAAK,EAAE,OAAO,EAAE,2BAA2B,cAAc,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnG;AAEA,QAAM,QAAQ,wBAAwB,EAAE,IAAI,CAAC,aAAa;AAAA,IACxD,IAAI,QAAQ;AAAA,IACZ,UAAU,QAAQ;AAAA,IAClB,gBAAgB,QAAQ,kBAAkB;AAAA,EAC5C,EAAE;AACF,SAAO,aAAa,KAAK,EAAE,MAAM,CAAC;AACpC;AAEO,MAAM,UAAU;AAAA,EACrB,KAAK;AAAA,IACH,SAAS;AAAA,IACT,aACE;AAAA,IACF,MAAM,CAAC,eAAe;AAAA,IACtB,WAAW;AAAA,MACT,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,EAAE,CAAC;AAAA,UACxD;AAAA,QACF;AAAA,MACF;AAAA,MACA,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS,EAAE,oBAAoB,EAAE,QAAQ,oBAAoB,EAAE;AAAA,MACjE;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
6
|
+
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
7
|
+
import { resolveNotificationPreferenceService } from "../../lib/notificationPreferenceService.js";
|
|
8
|
+
import {
|
|
9
|
+
runGuardedNotificationWrite,
|
|
10
|
+
notificationValidationErrorResponse,
|
|
11
|
+
NOTIFICATION_PREFERENCE_RESOURCE_KIND
|
|
12
|
+
} from "../../lib/routeHelpers.js";
|
|
13
|
+
import { PREFERENCE_UPDATED_EVENT, emitNotificationEvent } from "../../events.js";
|
|
14
|
+
import { updatePreferencesSchema, notificationPreferenceItemSchema } from "../../data/validators.js";
|
|
15
|
+
import { errorResponseSchema } from "../openapi.js";
|
|
16
|
+
const metadata = {
|
|
17
|
+
GET: { requireAuth: true, requireFeatures: ["notifications.manage_preferences"] },
|
|
18
|
+
PUT: { requireAuth: true, requireFeatures: ["notifications.manage_preferences"] }
|
|
19
|
+
};
|
|
20
|
+
const unauthorized = async () => {
|
|
21
|
+
const { t } = await resolveTranslations();
|
|
22
|
+
return NextResponse.json({ error: t("api.errors.unauthorized", "Unauthorized") }, { status: 401 });
|
|
23
|
+
};
|
|
24
|
+
async function GET(req) {
|
|
25
|
+
const auth = await getAuthFromRequest(req);
|
|
26
|
+
if (!auth?.sub || !auth.tenantId) return await unauthorized();
|
|
27
|
+
const container = await createRequestContainer();
|
|
28
|
+
try {
|
|
29
|
+
const service = resolveNotificationPreferenceService(container);
|
|
30
|
+
const scope = { tenantId: auth.tenantId, userId: auth.sub };
|
|
31
|
+
const rows = await service.listForUser(scope);
|
|
32
|
+
const items = rows.map((row) => ({
|
|
33
|
+
notificationTypeId: row.notificationTypeId,
|
|
34
|
+
channel: row.channel,
|
|
35
|
+
enabled: row.enabled
|
|
36
|
+
}));
|
|
37
|
+
return NextResponse.json({ items });
|
|
38
|
+
} finally {
|
|
39
|
+
const disposable = container;
|
|
40
|
+
if (typeof disposable.dispose === "function") await disposable.dispose();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function PUT(req) {
|
|
44
|
+
const auth = await getAuthFromRequest(req);
|
|
45
|
+
if (!auth?.sub || !auth.tenantId) return await unauthorized();
|
|
46
|
+
const parsed = updatePreferencesSchema.safeParse(await readJsonSafe(req, {}));
|
|
47
|
+
if (!parsed.success) return notificationValidationErrorResponse(parsed.error);
|
|
48
|
+
const container = await createRequestContainer();
|
|
49
|
+
try {
|
|
50
|
+
const service = resolveNotificationPreferenceService(container);
|
|
51
|
+
const scope = { tenantId: auth.tenantId, userId: auth.sub };
|
|
52
|
+
const guarded = await runGuardedNotificationWrite(
|
|
53
|
+
container,
|
|
54
|
+
{ tenantId: auth.tenantId, organizationId: auth.orgId ?? null, userId: auth.sub },
|
|
55
|
+
req,
|
|
56
|
+
{
|
|
57
|
+
resourceKind: NOTIFICATION_PREFERENCE_RESOURCE_KIND,
|
|
58
|
+
resourceId: auth.sub,
|
|
59
|
+
operation: "update",
|
|
60
|
+
payload: parsed.data
|
|
61
|
+
},
|
|
62
|
+
() => service.setPreferences(
|
|
63
|
+
scope,
|
|
64
|
+
parsed.data.preferences.map((p) => ({
|
|
65
|
+
typeId: p.notificationTypeId,
|
|
66
|
+
channel: p.channel,
|
|
67
|
+
enabled: p.enabled
|
|
68
|
+
}))
|
|
69
|
+
)
|
|
70
|
+
);
|
|
71
|
+
if (!guarded.ok) return guarded.response;
|
|
72
|
+
if (guarded.result > 0) {
|
|
73
|
+
await emitNotificationEvent(PREFERENCE_UPDATED_EVENT, {
|
|
74
|
+
tenantId: auth.tenantId,
|
|
75
|
+
organizationId: auth.orgId ?? null,
|
|
76
|
+
userId: auth.sub
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return NextResponse.json({ ok: true });
|
|
80
|
+
} finally {
|
|
81
|
+
const disposable = container;
|
|
82
|
+
if (typeof disposable.dispose === "function") await disposable.dispose();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const openApi = {
|
|
86
|
+
GET: {
|
|
87
|
+
summary: "Get notification preferences",
|
|
88
|
+
description: "Returns the current user's stored channel preferences (absent rows default to enabled).",
|
|
89
|
+
tags: ["Notifications"],
|
|
90
|
+
responses: {
|
|
91
|
+
200: {
|
|
92
|
+
description: "Stored preferences",
|
|
93
|
+
content: {
|
|
94
|
+
"application/json": {
|
|
95
|
+
schema: z.object({ items: z.array(notificationPreferenceItemSchema) })
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
401: { description: "Unauthorized", content: { "application/json": { schema: errorResponseSchema } } }
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
PUT: {
|
|
103
|
+
summary: "Update notification preferences",
|
|
104
|
+
description: "Bulk-updates the current user's channel preferences.",
|
|
105
|
+
tags: ["Notifications"],
|
|
106
|
+
requestBody: {
|
|
107
|
+
required: true,
|
|
108
|
+
content: { "application/json": { schema: updatePreferencesSchema } }
|
|
109
|
+
},
|
|
110
|
+
responses: {
|
|
111
|
+
200: {
|
|
112
|
+
description: "Preferences updated",
|
|
113
|
+
content: { "application/json": { schema: z.object({ ok: z.boolean() }) } }
|
|
114
|
+
},
|
|
115
|
+
400: { description: "Invalid request body", content: { "application/json": { schema: errorResponseSchema } } },
|
|
116
|
+
401: { description: "Unauthorized", content: { "application/json": { schema: errorResponseSchema } } }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
export {
|
|
121
|
+
GET,
|
|
122
|
+
PUT,
|
|
123
|
+
metadata,
|
|
124
|
+
openApi
|
|
125
|
+
};
|
|
126
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/notifications/api/preferences/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { resolveNotificationPreferenceService, type NotificationPreferenceScope } from '../../lib/notificationPreferenceService'\nimport {\n runGuardedNotificationWrite,\n notificationValidationErrorResponse,\n NOTIFICATION_PREFERENCE_RESOURCE_KIND,\n} from '../../lib/routeHelpers'\nimport { PREFERENCE_UPDATED_EVENT, emitNotificationEvent } from '../../events'\nimport { updatePreferencesSchema, notificationPreferenceItemSchema } from '../../data/validators'\nimport { errorResponseSchema } from '../openapi'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['notifications.manage_preferences'] },\n PUT: { requireAuth: true, requireFeatures: ['notifications.manage_preferences'] },\n}\n\nconst unauthorized = async () => {\n const { t } = await resolveTranslations()\n return NextResponse.json({ error: t('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub || !auth.tenantId) return await unauthorized()\n\n const container = await createRequestContainer()\n try {\n const service = resolveNotificationPreferenceService(container)\n const scope: NotificationPreferenceScope = { tenantId: auth.tenantId, userId: auth.sub }\n const rows = await service.listForUser(scope)\n const items = rows.map((row) => ({\n notificationTypeId: row.notificationTypeId,\n channel: row.channel,\n enabled: row.enabled,\n }))\n return NextResponse.json({ items })\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') await disposable.dispose()\n }\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub || !auth.tenantId) return await unauthorized()\n\n const parsed = updatePreferencesSchema.safeParse(await readJsonSafe(req, {}))\n if (!parsed.success) return notificationValidationErrorResponse(parsed.error)\n\n const container = await createRequestContainer()\n try {\n const service = resolveNotificationPreferenceService(container)\n const scope: NotificationPreferenceScope = { tenantId: auth.tenantId, userId: auth.sub }\n const guarded = await runGuardedNotificationWrite(\n container,\n { tenantId: auth.tenantId, organizationId: auth.orgId ?? null, userId: auth.sub },\n req,\n {\n resourceKind: NOTIFICATION_PREFERENCE_RESOURCE_KIND,\n resourceId: auth.sub,\n operation: 'update',\n payload: parsed.data as Record<string, unknown>,\n },\n () =>\n service.setPreferences(\n scope,\n parsed.data.preferences.map((p) => ({\n typeId: p.notificationTypeId,\n channel: p.channel,\n enabled: p.enabled,\n })),\n ),\n )\n if (!guarded.ok) return guarded.response\n\n // Skip the event on no-op writes (nothing actually changed).\n if (guarded.result > 0) {\n await emitNotificationEvent(PREFERENCE_UPDATED_EVENT, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n })\n }\n\n return NextResponse.json({ ok: true })\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') await disposable.dispose()\n }\n}\n\nexport const openApi = {\n GET: {\n summary: 'Get notification preferences',\n description: \"Returns the current user's stored channel preferences (absent rows default to enabled).\",\n tags: ['Notifications'],\n responses: {\n 200: {\n description: 'Stored preferences',\n content: {\n 'application/json': {\n schema: z.object({ items: z.array(notificationPreferenceItemSchema) }),\n },\n },\n },\n 401: { description: 'Unauthorized', content: { 'application/json': { schema: errorResponseSchema } } },\n },\n },\n PUT: {\n summary: 'Update notification preferences',\n description: \"Bulk-updates the current user's channel preferences.\",\n tags: ['Notifications'],\n requestBody: {\n required: true,\n content: { 'application/json': { schema: updatePreferencesSchema } },\n },\n responses: {\n 200: {\n description: 'Preferences updated',\n content: { 'application/json': { schema: z.object({ ok: z.boolean() }) } },\n },\n 400: { description: 'Invalid request body', content: { 'application/json': { schema: errorResponseSchema } } },\n 401: { description: 'Unauthorized', content: { 'application/json': { schema: errorResponseSchema } } },\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { Notification } from "../data/entities.js";
|
|
11
11
|
import { listNotificationsSchema, createNotificationSchema } from "../data/validators.js";
|
|
12
12
|
import { toNotificationDto } from "../lib/notificationMapper.js";
|
|
13
|
+
import { inAppVisibleFilter } from "../lib/notificationVisibility.js";
|
|
13
14
|
import {
|
|
14
15
|
buildNotificationReadScopeWhere,
|
|
15
16
|
getNotificationReadScopeTagOrganizationIds
|
|
@@ -89,7 +90,13 @@ async function GET(req) {
|
|
|
89
90
|
const filters = {
|
|
90
91
|
recipientUserId: userId,
|
|
91
92
|
tenantId: scope.tenantId,
|
|
92
|
-
|
|
93
|
+
// Read scope AND in-app visibility: both fragments carry their own `$or`, so they must be
|
|
94
|
+
// AND-composed instead of spread (a spread would silently drop one of them).
|
|
95
|
+
// Hidden rows are those not delivered to the in-app channel (push-only / opted-out); they remain as records.
|
|
96
|
+
$and: [
|
|
97
|
+
buildNotificationReadScopeWhere(scope),
|
|
98
|
+
inAppVisibleFilter()
|
|
99
|
+
]
|
|
93
100
|
};
|
|
94
101
|
if (input.status) {
|
|
95
102
|
filters.status = Array.isArray(input.status) ? { $in: input.status } : input.status;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/notifications/api/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { createHash } from 'node:crypto'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/core'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport {\n buildCollectionTags,\n debugCrudCache,\n isCrudCacheEnabled,\n resolveCrudCache,\n} from '@open-mercato/shared/lib/crud/cache'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { Notification } from '../data/entities'\nimport { listNotificationsSchema, createNotificationSchema } from '../data/validators'\nimport { toNotificationDto } from '../lib/notificationMapper'\nimport {\n buildNotificationReadScopeWhere,\n getNotificationReadScopeTagOrganizationIds,\n} from '../lib/notificationScope'\nimport {\n NOTIFICATION_RESOURCE_KIND,\n notificationCrudErrorResponse,\n notificationValidationErrorResponse,\n resolveGuardedNotificationContext,\n resolveNotificationContext,\n runGuardedNotificationWrite,\n TENANT_SCOPE_REQUIRED_ERROR_CODE,\n} from '../lib/routeHelpers'\nimport {\n buildNotificationsCrudOpenApi,\n createPagedListResponseSchema,\n notificationItemSchema,\n scopeErrorResponseSchema,\n} from './openapi'\n\nexport const metadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true, requireFeatures: ['notifications.create'] },\n}\n\nconst NOTIFICATIONS_LIST_TTL_MS = 10_000\n// v2: the key gained the selected-organization scope (#4840). Bumping the version\n// retires v1 entries, which were shared across organizations for the same user.\nconst NOTIFICATIONS_LIST_CACHE_VERSION = 2\nconst NOTIFICATIONS_LIST_RESOURCE = 'notifications.notification'\n\ntype NotificationsListPayload = {\n items: ReturnType<typeof toNotificationDto>[]\n total: number\n page: number\n pageSize: number\n totalPages: number\n}\n\nexport function buildNotificationsListCacheKey(\n userId: string,\n organizationScope: { organizationId: string | null; organizationIds: string[] },\n input: z.infer<typeof listNotificationsSchema>,\n): string {\n const normalizedIds = Array.from(new Set(\n organizationScope.organizationIds.filter((value) => value.trim().length > 0),\n )).sort((left, right) => left.localeCompare(right))\n const scopeKey = normalizedIds.length === 0\n ? 'no-access'\n : `${organizationScope.organizationId ?? 'none'}:scope=${createHash('sha256')\n .update(normalizedIds.join('\\0'))\n .digest('hex')\n .slice(0, 16)}`\n const filterSignature = JSON.stringify({\n status: Array.isArray(input.status)\n ? [...input.status].sort((left, right) => left.localeCompare(right))\n : input.status ?? null,\n type: input.type ?? null,\n severity: input.severity ?? null,\n sourceEntityType: input.sourceEntityType ?? null,\n sourceEntityId: input.sourceEntityId ?? null,\n since: input.since ?? null,\n page: input.page,\n pageSize: input.pageSize,\n })\n return `notifications:list:v${NOTIFICATIONS_LIST_CACHE_VERSION}:u=${userId}:org=${scopeKey}:filters=${filterSignature}`\n}\n\nfunction isNotificationsListPayload(value: unknown): value is NotificationsListPayload {\n if (!value || typeof value !== 'object') return false\n const payload = value as Partial<NotificationsListPayload>\n return (\n Array.isArray(payload.items)\n && typeof payload.total === 'number'\n && typeof payload.page === 'number'\n && typeof payload.pageSize === 'number'\n && typeof payload.totalPages === 'number'\n )\n}\n\nexport async function GET(req: Request) {\n const resolved = await resolveGuardedNotificationContext(req)\n if (!resolved.ok) return resolved.response\n const { ctx, scope } = resolved\n const em = ctx.container.resolve('em') as EntityManager\n\n const url = new URL(req.url)\n const queryParams = Object.fromEntries(url.searchParams.entries())\n const input = listNotificationsSchema.parse(queryParams)\n const userId = scope.userId\n // Mirrors api/unread-count/route.ts: unrestricted and omitted legacy organization\n // scopes cannot be safely tagged for invalidation, because organization-specific\n // writes only invalidate their own collection tag. Leave those scopes uncached\n // rather than serving a stale tenant-wide list until the TTL expires.\n const cacheableOrganizationIds = Array.isArray(scope.organizationIds)\n ? scope.organizationIds\n : null\n const cache = userId && cacheableOrganizationIds && isCrudCacheEnabled()\n ? resolveCrudCache(ctx.container)\n : null\n const cacheKey = cache && userId && cacheableOrganizationIds\n ? buildNotificationsListCacheKey(\n userId,\n { organizationId: scope.organizationId, organizationIds: cacheableOrganizationIds },\n input,\n )\n : null\n\n if (cache && cacheKey) {\n try {\n const cached = await runWithCacheTenant(scope.tenantId, () => cache.get(cacheKey))\n if (isNotificationsListPayload(cached)) {\n return Response.json(cached)\n }\n } catch (error) {\n debugCrudCache('notifications-list-cache-read-failed', {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n const filters: Record<string, unknown> = {\n recipientUserId: userId,\n tenantId: scope.tenantId,\n
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { createHash } from 'node:crypto'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/core'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport {\n buildCollectionTags,\n debugCrudCache,\n isCrudCacheEnabled,\n resolveCrudCache,\n} from '@open-mercato/shared/lib/crud/cache'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { Notification } from '../data/entities'\nimport { listNotificationsSchema, createNotificationSchema } from '../data/validators'\nimport { toNotificationDto } from '../lib/notificationMapper'\nimport { inAppVisibleFilter } from '../lib/notificationVisibility'\nimport {\n buildNotificationReadScopeWhere,\n getNotificationReadScopeTagOrganizationIds,\n} from '../lib/notificationScope'\nimport {\n NOTIFICATION_RESOURCE_KIND,\n notificationCrudErrorResponse,\n notificationValidationErrorResponse,\n resolveGuardedNotificationContext,\n resolveNotificationContext,\n runGuardedNotificationWrite,\n TENANT_SCOPE_REQUIRED_ERROR_CODE,\n} from '../lib/routeHelpers'\nimport {\n buildNotificationsCrudOpenApi,\n createPagedListResponseSchema,\n notificationItemSchema,\n scopeErrorResponseSchema,\n} from './openapi'\n\nexport const metadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true, requireFeatures: ['notifications.create'] },\n}\n\nconst NOTIFICATIONS_LIST_TTL_MS = 10_000\n// v2: the key gained the selected-organization scope (#4840). Bumping the version\n// retires v1 entries, which were shared across organizations for the same user.\nconst NOTIFICATIONS_LIST_CACHE_VERSION = 2\nconst NOTIFICATIONS_LIST_RESOURCE = 'notifications.notification'\n\ntype NotificationsListPayload = {\n items: ReturnType<typeof toNotificationDto>[]\n total: number\n page: number\n pageSize: number\n totalPages: number\n}\n\nexport function buildNotificationsListCacheKey(\n userId: string,\n organizationScope: { organizationId: string | null; organizationIds: string[] },\n input: z.infer<typeof listNotificationsSchema>,\n): string {\n const normalizedIds = Array.from(new Set(\n organizationScope.organizationIds.filter((value) => value.trim().length > 0),\n )).sort((left, right) => left.localeCompare(right))\n const scopeKey = normalizedIds.length === 0\n ? 'no-access'\n : `${organizationScope.organizationId ?? 'none'}:scope=${createHash('sha256')\n .update(normalizedIds.join('\\0'))\n .digest('hex')\n .slice(0, 16)}`\n const filterSignature = JSON.stringify({\n status: Array.isArray(input.status)\n ? [...input.status].sort((left, right) => left.localeCompare(right))\n : input.status ?? null,\n type: input.type ?? null,\n severity: input.severity ?? null,\n sourceEntityType: input.sourceEntityType ?? null,\n sourceEntityId: input.sourceEntityId ?? null,\n since: input.since ?? null,\n page: input.page,\n pageSize: input.pageSize,\n })\n return `notifications:list:v${NOTIFICATIONS_LIST_CACHE_VERSION}:u=${userId}:org=${scopeKey}:filters=${filterSignature}`\n}\n\nfunction isNotificationsListPayload(value: unknown): value is NotificationsListPayload {\n if (!value || typeof value !== 'object') return false\n const payload = value as Partial<NotificationsListPayload>\n return (\n Array.isArray(payload.items)\n && typeof payload.total === 'number'\n && typeof payload.page === 'number'\n && typeof payload.pageSize === 'number'\n && typeof payload.totalPages === 'number'\n )\n}\n\nexport async function GET(req: Request) {\n const resolved = await resolveGuardedNotificationContext(req)\n if (!resolved.ok) return resolved.response\n const { ctx, scope } = resolved\n const em = ctx.container.resolve('em') as EntityManager\n\n const url = new URL(req.url)\n const queryParams = Object.fromEntries(url.searchParams.entries())\n const input = listNotificationsSchema.parse(queryParams)\n const userId = scope.userId\n // Mirrors api/unread-count/route.ts: unrestricted and omitted legacy organization\n // scopes cannot be safely tagged for invalidation, because organization-specific\n // writes only invalidate their own collection tag. Leave those scopes uncached\n // rather than serving a stale tenant-wide list until the TTL expires.\n const cacheableOrganizationIds = Array.isArray(scope.organizationIds)\n ? scope.organizationIds\n : null\n const cache = userId && cacheableOrganizationIds && isCrudCacheEnabled()\n ? resolveCrudCache(ctx.container)\n : null\n const cacheKey = cache && userId && cacheableOrganizationIds\n ? buildNotificationsListCacheKey(\n userId,\n { organizationId: scope.organizationId, organizationIds: cacheableOrganizationIds },\n input,\n )\n : null\n\n if (cache && cacheKey) {\n try {\n const cached = await runWithCacheTenant(scope.tenantId, () => cache.get(cacheKey))\n if (isNotificationsListPayload(cached)) {\n return Response.json(cached)\n }\n } catch (error) {\n debugCrudCache('notifications-list-cache-read-failed', {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n const filters: Record<string, unknown> = {\n recipientUserId: userId,\n tenantId: scope.tenantId,\n // Read scope AND in-app visibility: both fragments carry their own `$or`, so they must be\n // AND-composed instead of spread (a spread would silently drop one of them).\n // Hidden rows are those not delivered to the in-app channel (push-only / opted-out); they remain as records.\n $and: [\n buildNotificationReadScopeWhere(scope),\n inAppVisibleFilter(),\n ],\n }\n\n if (input.status) {\n filters.status = Array.isArray(input.status) ? { $in: input.status } : input.status\n } else {\n filters.status = { $ne: 'dismissed' }\n }\n if (input.type) {\n filters.type = input.type\n }\n if (input.severity) {\n filters.severity = input.severity\n }\n if (input.sourceEntityType) {\n filters.sourceEntityType = input.sourceEntityType\n }\n if (input.sourceEntityId) {\n filters.sourceEntityId = input.sourceEntityId\n }\n if (input.since) {\n filters.createdAt = { $gt: new Date(input.since) }\n }\n\n const [notifications, total] = await Promise.all([\n em.find(Notification, filters, {\n orderBy: { createdAt: 'desc' },\n limit: input.pageSize,\n offset: (input.page - 1) * input.pageSize,\n }),\n em.count(Notification, filters),\n ])\n\n const items = notifications.map(toNotificationDto)\n\n const payload: NotificationsListPayload = {\n items,\n total,\n page: input.page,\n pageSize: input.pageSize,\n totalPages: Math.ceil(total / input.pageSize),\n }\n\n if (cache && cacheKey) {\n try {\n await runWithCacheTenant(scope.tenantId, () =>\n cache.set(cacheKey, payload, {\n ttl: NOTIFICATIONS_LIST_TTL_MS,\n tags: buildCollectionTags(\n NOTIFICATIONS_LIST_RESOURCE,\n scope.tenantId,\n getNotificationReadScopeTagOrganizationIds(scope),\n ),\n }),\n )\n } catch (error) {\n debugCrudCache('notifications-list-cache-write-failed', {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n return Response.json(payload)\n}\n\nexport async function POST(req: Request) {\n const { service, scope, ctx } = await resolveNotificationContext(req)\n\n const body = await req.json().catch(() => ({}))\n const parsed = createNotificationSchema.safeParse(body)\n if (!parsed.success) {\n return notificationValidationErrorResponse(parsed.error)\n }\n\n try {\n const guarded = await runGuardedNotificationWrite(\n ctx.container,\n scope,\n req,\n {\n resourceKind: NOTIFICATION_RESOURCE_KIND,\n operation: 'create',\n payload: parsed.data as Record<string, unknown>,\n },\n () => service.create(parsed.data, scope),\n )\n if (!guarded.ok) return guarded.response\n\n return Response.json({ id: guarded.result.id }, { status: 201 })\n } catch (error) {\n const errorResponse = notificationCrudErrorResponse(error)\n if (errorResponse) return errorResponse\n throw error\n }\n}\n\nconst notificationsCrudOpenApi = buildNotificationsCrudOpenApi({\n resourceName: 'Notification',\n querySchema: listNotificationsSchema,\n listResponseSchema: createPagedListResponseSchema(notificationItemSchema),\n create: {\n schema: createNotificationSchema,\n responseSchema: z.object({ id: z.string().uuid() }),\n description: 'Creates a notification for a user.',\n },\n})\n\nconst notificationsCrudGet = notificationsCrudOpenApi.methods?.GET ?? {}\n\n// The CRUD factory documents errors only for DELETE, and POST already gets an auto-generated 403\n// from its `requireFeatures` metadata. GET is authenticated-only, so its tenant-scope rejection has\n// to be declared here to reach the generated spec.\nexport const openApi: OpenApiRouteDoc = {\n ...notificationsCrudOpenApi,\n methods: {\n ...notificationsCrudOpenApi.methods,\n GET: {\n ...notificationsCrudGet,\n errors: [\n ...(notificationsCrudGet.errors ?? []),\n {\n status: 403,\n description: `Request could not be resolved to a tenant scope (code: ${TENANT_SCOPE_REQUIRED_ERROR_CODE})`,\n schema: scopeErrorResponseSchema,\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|