@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
|
@@ -192,5 +192,6 @@
|
|
|
192
192
|
"inbox_ops.translate.show_original": "Show original",
|
|
193
193
|
"inbox_ops.translate.translate": "Translate",
|
|
194
194
|
"inbox_ops.untitled_proposal": "Untitled proposal",
|
|
195
|
-
"inbox_ops.view_in_messages": "View in Messages"
|
|
195
|
+
"inbox_ops.view_in_messages": "View in Messages",
|
|
196
|
+
"notifications.categories.inbox_ops": "Inbox"
|
|
196
197
|
}
|
|
@@ -192,5 +192,6 @@
|
|
|
192
192
|
"inbox_ops.translate.show_original": "Mostrar original",
|
|
193
193
|
"inbox_ops.translate.translate": "Traducir",
|
|
194
194
|
"inbox_ops.untitled_proposal": "Propuesta sin t\u00edtulo",
|
|
195
|
-
"inbox_ops.view_in_messages": "Ver en mensajes"
|
|
195
|
+
"inbox_ops.view_in_messages": "Ver en mensajes",
|
|
196
|
+
"notifications.categories.inbox_ops": "Bandeja de entrada"
|
|
196
197
|
}
|
|
@@ -192,5 +192,6 @@
|
|
|
192
192
|
"inbox_ops.translate.show_original": "원문 보기",
|
|
193
193
|
"inbox_ops.translate.translate": "번역",
|
|
194
194
|
"inbox_ops.untitled_proposal": "제목 없는 제안",
|
|
195
|
-
"inbox_ops.view_in_messages": "메시지에서 보기"
|
|
195
|
+
"inbox_ops.view_in_messages": "메시지에서 보기",
|
|
196
|
+
"notifications.categories.inbox_ops": "받은편지함"
|
|
196
197
|
}
|
|
@@ -192,5 +192,6 @@
|
|
|
192
192
|
"inbox_ops.translate.show_original": "Poka\u017c orygina\u0142",
|
|
193
193
|
"inbox_ops.translate.translate": "Przet\u0142umacz",
|
|
194
194
|
"inbox_ops.untitled_proposal": "Propozycja bez tytu\u0142u",
|
|
195
|
-
"inbox_ops.view_in_messages": "Zobacz w wiadomo\u015bciach"
|
|
195
|
+
"inbox_ops.view_in_messages": "Zobacz w wiadomo\u015bciach",
|
|
196
|
+
"notifications.categories.inbox_ops": "Skrzynka"
|
|
196
197
|
}
|
|
@@ -3,6 +3,8 @@ import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/no
|
|
|
3
3
|
export const notificationTypes: NotificationTypeDefinition[] = [
|
|
4
4
|
{
|
|
5
5
|
type: 'inbox_ops.proposal.created',
|
|
6
|
+
// Ships without push — operators re-enable it per type from the Notification Delivery settings.
|
|
7
|
+
channels: ['in_app', 'email'],
|
|
6
8
|
module: 'inbox_ops',
|
|
7
9
|
titleKey: 'inbox_ops.notifications.proposal_created.title',
|
|
8
10
|
bodyKey: 'inbox_ops.notifications.proposal_created.body',
|
|
@@ -259,5 +259,6 @@
|
|
|
259
259
|
"messages.visibilityInternal": "Intern",
|
|
260
260
|
"messages.visibilityInternalHint": "Interne Nachrichten werden an ausgewählte Systemnutzer gesendet.",
|
|
261
261
|
"messages.visibilityPublic": "Öffentlich",
|
|
262
|
-
"messages.visibilityPublicHint": "Öffentliche Nachrichten werden nur an externe E-Mail gesendet."
|
|
262
|
+
"messages.visibilityPublicHint": "Öffentliche Nachrichten werden nur an externe E-Mail gesendet.",
|
|
263
|
+
"notifications.categories.messages": "Nachrichten"
|
|
263
264
|
}
|
|
@@ -259,5 +259,6 @@
|
|
|
259
259
|
"messages.visibilityInternal": "Internal",
|
|
260
260
|
"messages.visibilityInternalHint": "Internal messages are sent to selected system users.",
|
|
261
261
|
"messages.visibilityPublic": "Public",
|
|
262
|
-
"messages.visibilityPublicHint": "Public messages are sent to external email only."
|
|
262
|
+
"messages.visibilityPublicHint": "Public messages are sent to external email only.",
|
|
263
|
+
"notifications.categories.messages": "Messages"
|
|
263
264
|
}
|
|
@@ -259,5 +259,6 @@
|
|
|
259
259
|
"messages.visibilityInternal": "Interna",
|
|
260
260
|
"messages.visibilityInternalHint": "Los mensajes internos se envían a los usuarios del sistema seleccionados.",
|
|
261
261
|
"messages.visibilityPublic": "Pública",
|
|
262
|
-
"messages.visibilityPublicHint": "Los mensajes públicos se envían solo al correo externo."
|
|
262
|
+
"messages.visibilityPublicHint": "Los mensajes públicos se envían solo al correo externo.",
|
|
263
|
+
"notifications.categories.messages": "Mensajes"
|
|
263
264
|
}
|
|
@@ -259,5 +259,6 @@
|
|
|
259
259
|
"messages.visibilityInternal": "내부",
|
|
260
260
|
"messages.visibilityInternalHint": "내부 메시지는 선택한 시스템 사용자에게 전송됩니다.",
|
|
261
261
|
"messages.visibilityPublic": "공개",
|
|
262
|
-
"messages.visibilityPublicHint": "공개 메시지는 외부 이메일로만 전송됩니다."
|
|
262
|
+
"messages.visibilityPublicHint": "공개 메시지는 외부 이메일로만 전송됩니다.",
|
|
263
|
+
"notifications.categories.messages": "메시지"
|
|
263
264
|
}
|
|
@@ -259,5 +259,6 @@
|
|
|
259
259
|
"messages.visibilityInternal": "Wewnętrzna",
|
|
260
260
|
"messages.visibilityInternalHint": "Wiadomości wewnętrzne są wysyłane do wybranych użytkowników systemu.",
|
|
261
261
|
"messages.visibilityPublic": "Publiczna",
|
|
262
|
-
"messages.visibilityPublicHint": "Wiadomości publiczne są wysyłane wyłącznie na zewnętrzny e-mail."
|
|
262
|
+
"messages.visibilityPublicHint": "Wiadomości publiczne są wysyłane wyłącznie na zewnętrzny e-mail.",
|
|
263
|
+
"notifications.categories.messages": "Wiadomo\u015bci"
|
|
263
264
|
}
|
|
@@ -3,6 +3,8 @@ import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/no
|
|
|
3
3
|
export const notificationTypes: NotificationTypeDefinition[] = [
|
|
4
4
|
{
|
|
5
5
|
type: 'messages.new',
|
|
6
|
+
// Ships without push — operators re-enable it per type from the Notification Delivery settings.
|
|
7
|
+
channels: ['in_app', 'email'],
|
|
6
8
|
module: 'messages',
|
|
7
9
|
titleKey: 'messages.notifications.new.title',
|
|
8
10
|
bodyKey: 'messages.notifications.new.body',
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Notifications Module — Agent Guidelines
|
|
2
|
+
|
|
3
|
+
In-app notifications plus two channel-agnostic surfaces that every delivery channel (in-app, push, future email/SMS) reads from: a **DB-mirrored notification type catalogue** and **per-user channel preferences**. See `.ai/specs/2026-04-28-push-notifications-and-devices.md` (Phase 2).
|
|
4
|
+
|
|
5
|
+
## Always
|
|
6
|
+
|
|
7
|
+
- Register new notification types in your **own** module's `notifications.ts` (`notificationTypes: NotificationTypeDefinition[]`). The generator aggregates them; `bootstrap.ts` feeds the aggregate into the in-memory registry (`lib/notification-type-registry.ts`) via `registerNotificationTypes(...)`. Do **not** add a defaults array here.
|
|
8
|
+
- Read channel preferences through `NotificationPreferenceService` (DI: `notificationPreferenceService`) — never query `notification_preferences` directly from another module. Use `isChannelEnabled(scope, typeId, channel)` (defaults to `true` when no row exists — lazy-seed, default-on).
|
|
9
|
+
- Keep type ids **FROZEN** (BACKWARD_COMPATIBILITY.md): once a `NotificationTypeDefinition.type` ships it is a stable contract; renames need the deprecation protocol.
|
|
10
|
+
- Keep API URLs **STABLE**: `/api/notifications/types`, `/api/notifications/preferences` are additive-only.
|
|
11
|
+
- Wire custom write routes through the mutation guard via `runGuardedNotificationWrite(...)` in `lib/routeHelpers.ts` (the preferences `PUT` does this).
|
|
12
|
+
|
|
13
|
+
## Delivery channels & the gate (Phase 7)
|
|
14
|
+
|
|
15
|
+
- **Every channel is a strategy on one seam.** `in_app`, `email`, and `push` are all `NotificationDeliveryStrategy` objects (`lib/deliveryStrategies.ts`), registered via a `notifications.delivery-strategies.ts` convention file discovered by the `delivery-strategies` generator plugin. `in_app` is a no-op strategy — the durable `Notification` row IS the in-app delivery; its bell/inbox **visibility** is `in_app ∈ notification.channels` (see `lib/notificationVisibility.ts`), applied at the read layer, not by the strategy.
|
|
16
|
+
- **One gate, at create time.** `notificationService.create*` calls `resolveEffectiveChannels` (`lib/shouldDeliver.ts`) once per recipient — composing per-send target (`input.channels`) ∩ per-type eligibility (`NotificationTypeDefinition.channels`) ∩ registered strategies ∩ the recipient's per-channel preference (`nonOptOut` bypasses opt-out; `silent` is orthogonal) — and snapshots the result on `Notification.channels`. The dispatch subscriber then just loops the registered strategies filtered by that set (`NULL ⇒ all channels`, legacy/BC). **Do not** re-implement opt-out logic inside a strategy — the gate already ran. Use `isConfigured(ctx)`/`supports(notification)` only for technical deliverability.
|
|
17
|
+
- **Absent `channels` on a create call ⇒ all channels** (unchanged behavior). Pass `channels: ['push']` (etc.) to target a subset.
|
|
18
|
+
- **Channel catalogue.** Add a channel's UI metadata by exporting `notificationChannels: NotificationChannelDefinition[]` from a `notification-channels.ts` (generator-discovered → `getNotificationChannels()` → `GET /api/notifications/channels`; the preferences UI reads it). Keep each delivery-strategy `id` and its channel-definition `id` in sync — behavior layer (strategy registry) + metadata layer (channel registry), same id.
|
|
19
|
+
- **When adding an in-app read surface** (a new bell/inbox/count query), AND-in `inAppVisibleFilter()` so suppressed rows stay hidden.
|
|
20
|
+
|
|
21
|
+
## Never
|
|
22
|
+
|
|
23
|
+
- Never create a cross-module ORM relationship to `notification_types` / `notification_preferences`. `NotificationPreference.notificationTypeId` is a **soft string ref** to a type id, not a FK relation.
|
|
24
|
+
- Never gate per-user opt-out inside a delivery strategy — enforcement is centralized in the create-time gate (`shouldDeliver`). A strategy that re-checks preferences double-counts and drifts.
|
|
25
|
+
- Never write per-tenant rows into `notification_types`. Code-registered types are **system-wide** (`tenant_id IS NULL`); the column is nullable only to leave room for future tenant-defined types.
|
|
26
|
+
- Never expose another tenant's preferences — all reads/writes are scoped by `(tenantId, userId)`.
|
|
27
|
+
|
|
28
|
+
## Type catalogue (read-through mirror)
|
|
29
|
+
|
|
30
|
+
- The in-memory `NotificationTypeDefinition` registry is the source of truth for code. `notification_types` is a **read-through DB mirror** so remote clients (mobile apps) can enumerate types over HTTP without shipping the catalogue.
|
|
31
|
+
- `syncNotificationTypes(em)` reconciles the registry into the table (idempotent, `tenant_id IS NULL`). It runs lazily on the first `GET /api/notifications/types` per process and on the `notifications.type_registry.sync` event (emitted from `setup.ts` `seedDefaults`; handled by `subscribers/sync-notification-types.ts`).
|
|
32
|
+
- Field map: `id ← def.type`, `label_key ← def.labelKey ?? def.titleKey`, `description_key ← def.descriptionKey ?? null`, `category ← def.category ?? deriveCategory(def.type)`. Give a type a distinct preferences-screen label by adding optional `labelKey`/`descriptionKey` to its `NotificationTypeDefinition` (additive — falls back to `titleKey`).
|
|
33
|
+
- **Grouping (`category`).** Defaults to the prefix before the first dot in the type id (`sales.order.created` → `sales`), resolved once in `syncNotificationTypes` so every mirrored row carries a key and read paths need no fallback branch. Declare `category` on the definition only to override that default.
|
|
34
|
+
- **Category labels live in the declaring module, never here.** This module ships no `notifications.categories.*` entries and holds no list of categories — it must stay ignorant of which ones exist. To localize a heading, add `notifications.categories.<key>` to **your own** module's `i18n/{en,pl,es,de}.json`; dictionaries merge flat across modules, so the shared key namespace costs no coupling. A category with no translation anywhere falls back to the raw key.
|
|
35
|
+
- **`GET /api/notifications/types` returns resolved display strings** — `label`, `description`, `categoryLabel` — alongside the existing `labelKey`/`descriptionKey`/`category`, so clients without the app dictionary (the mobile app) render directly. Locale comes from `resolveLocaleFromRequest` (`?locale=` → `x-locale` → cookie → `Accept-Language`), re-validated through `resolveSupportedLocale` because only that helper's `Accept-Language` branch validates its input. **Group on `category`, display `categoryLabel`** — grouping on the localized string re-partitions the list whenever the locale changes.
|
|
36
|
+
- Mark a type `silent: true` on its `NotificationTypeDefinition` to make its pushes content-available wake-ups (no banner, data-only). `silent` controls **delivery style only** — the notification still flows through the normal `notificationService.create()` path (in-app row created, per-channel push preference respected); to make a silent type always fire, also mark it `nonOptOut: true`. There is no separate silent-send API — the `push` delivery strategy handles `silent` types. The flag lives only in the in-memory registry (not mirrored to `notification_types`).
|
|
37
|
+
- A create call may carry an optional `data` (arbitrary app-readable string map — persisted on the row, exposed in the notification DTO, and delivered in the push data payload) and `pushOptions` (flat `sound`/`badge`/`image`/`priority`/`channelId`/`body` map — persisted, push-only, mapped per provider by the push adapters). Both are additive optional fields on the create/batch/role/feature schemas.
|
|
38
|
+
|
|
39
|
+
## Preferences & optimistic locking
|
|
40
|
+
|
|
41
|
+
- `NotificationPreference` carries `updated_at` but is **intentionally excluded** from the curated `optimistic-lock-editable-entities.test.ts` `notifications` list: it is an idempotent, lazy-seeded self-setting written through a service (`setPreferences`) + mutation guard, **not** `CrudForm`/`makeCrudRoute`, so a lost-update undo stack adds no value. When a preferences UI lands (Phase 5), its mutating call must either send the optimistic-lock version header or carry an inline `optimistic-lock-exempt` marker to satisfy `optimistic-lock-ui-coverage.test.ts`.
|
|
42
|
+
|
|
43
|
+
## ACL
|
|
44
|
+
|
|
45
|
+
- `notifications.view`, `notifications.create`, `notifications.manage`, `notifications.manage_preferences` (self-serve; granted to all default roles). After editing `acl.ts`, run `yarn mercato auth sync-role-acls`.
|
|
46
|
+
|
|
47
|
+
## Validation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
yarn generate
|
|
51
|
+
yarn db:generate # diff probe; keep only the notifications migration + snapshot
|
|
52
|
+
yarn workspace @open-mercato/core build
|
|
53
|
+
yarn workspace @open-mercato/core test
|
|
54
|
+
```
|
|
@@ -2,6 +2,8 @@ export 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
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
|
|
|
7
9
|
export default features
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { AwilixContainer } from 'awilix'
|
|
4
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
7
|
+
import type { AuthContext } from '@open-mercato/shared/lib/auth/server'
|
|
8
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
9
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
10
|
+
import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
11
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
12
|
+
import { User } from '../../../../auth/data/entities'
|
|
13
|
+
import { assertActorCanAccessUserTarget } from '../../../../auth/lib/grantChecks'
|
|
14
|
+
import { resolveOrganizationScopeForRequest } from '../../../../directory/utils/organizationScope'
|
|
15
|
+
import type { RbacService } from '../../../../auth/services/rbacService'
|
|
16
|
+
import { resolveNotificationPreferenceService, type NotificationPreferenceScope } from '../../../lib/notificationPreferenceService'
|
|
17
|
+
import {
|
|
18
|
+
runGuardedNotificationWrite,
|
|
19
|
+
notificationValidationErrorResponse,
|
|
20
|
+
NOTIFICATION_PREFERENCE_RESOURCE_KIND,
|
|
21
|
+
} from '../../../lib/routeHelpers'
|
|
22
|
+
import { PREFERENCE_UPDATED_EVENT, emitNotificationEvent } from '../../../events'
|
|
23
|
+
import {
|
|
24
|
+
adminPreferencesQuerySchema,
|
|
25
|
+
adminUpdatePreferencesSchema,
|
|
26
|
+
notificationPreferenceItemSchema,
|
|
27
|
+
} from '../../../data/validators'
|
|
28
|
+
import { errorResponseSchema } from '../../openapi'
|
|
29
|
+
|
|
30
|
+
export const metadata = {
|
|
31
|
+
GET: { requireAuth: true, requireFeatures: ['notifications.manage_user_preferences'] },
|
|
32
|
+
PUT: { requireAuth: true, requireFeatures: ['notifications.manage_user_preferences'] },
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const unauthorized = async () => {
|
|
36
|
+
const { t } = await resolveTranslations()
|
|
37
|
+
return NextResponse.json({ error: t('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Authorize the acting admin to manage a target user's preferences. Returns an error response to
|
|
42
|
+
* send, or null when access is granted. Two gates, mirroring the platform's user-management scoping:
|
|
43
|
+
* - 404 when the user does not exist in the acting tenant (existence/tenant check — the shared
|
|
44
|
+
* `assertActorCanAccessUserTarget` delegates silently on a fully-missing target).
|
|
45
|
+
* - standard org scoping via `assertActorCanAccessUserTarget` (403 out-of-org / 404 cross-tenant,
|
|
46
|
+
* super-admin and `__all__` bypass) — the same guard `auth` user CRUD uses, instead of a
|
|
47
|
+
* hand-rolled tenant-only check.
|
|
48
|
+
*/
|
|
49
|
+
async function authorizeTargetUser(
|
|
50
|
+
container: AwilixContainer,
|
|
51
|
+
em: EntityManager,
|
|
52
|
+
auth: AuthContext & { sub: string; tenantId: string },
|
|
53
|
+
req: Request,
|
|
54
|
+
targetUserId: string,
|
|
55
|
+
): Promise<NextResponse | null> {
|
|
56
|
+
const { t } = await resolveTranslations()
|
|
57
|
+
const user = await findOneWithDecryption(
|
|
58
|
+
em,
|
|
59
|
+
User,
|
|
60
|
+
{ id: targetUserId, tenantId: auth.tenantId, deletedAt: null },
|
|
61
|
+
undefined,
|
|
62
|
+
{ tenantId: auth.tenantId, organizationId: null },
|
|
63
|
+
)
|
|
64
|
+
if (!user) {
|
|
65
|
+
return NextResponse.json({ error: t('notifications.preferences.userNotFound', 'User not found') }, { status: 404 })
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const rbacService = container.resolve('rbacService') as RbacService
|
|
69
|
+
await assertActorCanAccessUserTarget({
|
|
70
|
+
em,
|
|
71
|
+
rbacService,
|
|
72
|
+
actorUserId: auth.sub,
|
|
73
|
+
tenantId: auth.tenantId,
|
|
74
|
+
organizationId: auth.orgId ?? null,
|
|
75
|
+
targetUserId,
|
|
76
|
+
organizationScope: await resolveOrganizationScopeForRequest({
|
|
77
|
+
container,
|
|
78
|
+
auth,
|
|
79
|
+
request: req,
|
|
80
|
+
tenantId: auth.tenantId,
|
|
81
|
+
}),
|
|
82
|
+
})
|
|
83
|
+
} catch (err) {
|
|
84
|
+
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
|
|
85
|
+
throw err
|
|
86
|
+
}
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function GET(req: Request) {
|
|
91
|
+
const auth = await getAuthFromRequest(req)
|
|
92
|
+
if (!auth?.sub || !auth.tenantId) return await unauthorized()
|
|
93
|
+
|
|
94
|
+
const url = new URL(req.url)
|
|
95
|
+
const parsed = adminPreferencesQuerySchema.safeParse({ userId: url.searchParams.get('userId') ?? undefined })
|
|
96
|
+
if (!parsed.success) return notificationValidationErrorResponse(parsed.error)
|
|
97
|
+
|
|
98
|
+
const container = await createRequestContainer()
|
|
99
|
+
try {
|
|
100
|
+
const em = container.resolve('em') as EntityManager
|
|
101
|
+
const denied = await authorizeTargetUser(container, em, { ...auth, sub: auth.sub, tenantId: auth.tenantId }, req, parsed.data.userId)
|
|
102
|
+
if (denied) return denied
|
|
103
|
+
const service = resolveNotificationPreferenceService(container)
|
|
104
|
+
const scope: NotificationPreferenceScope = { tenantId: auth.tenantId, userId: parsed.data.userId }
|
|
105
|
+
const rows = await service.listForUser(scope)
|
|
106
|
+
const items = rows.map((row) => ({
|
|
107
|
+
notificationTypeId: row.notificationTypeId,
|
|
108
|
+
channel: row.channel,
|
|
109
|
+
enabled: row.enabled,
|
|
110
|
+
}))
|
|
111
|
+
return NextResponse.json({ items })
|
|
112
|
+
} finally {
|
|
113
|
+
const disposable = container as unknown as { dispose?: () => Promise<void> }
|
|
114
|
+
if (typeof disposable.dispose === 'function') await disposable.dispose()
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function PUT(req: Request) {
|
|
119
|
+
const auth = await getAuthFromRequest(req)
|
|
120
|
+
if (!auth?.sub || !auth.tenantId) return await unauthorized()
|
|
121
|
+
|
|
122
|
+
const parsed = adminUpdatePreferencesSchema.safeParse(await readJsonSafe(req, {}))
|
|
123
|
+
if (!parsed.success) return notificationValidationErrorResponse(parsed.error)
|
|
124
|
+
|
|
125
|
+
const container = await createRequestContainer()
|
|
126
|
+
try {
|
|
127
|
+
const em = container.resolve('em') as EntityManager
|
|
128
|
+
const denied = await authorizeTargetUser(container, em, { ...auth, sub: auth.sub, tenantId: auth.tenantId }, req, parsed.data.userId)
|
|
129
|
+
if (denied) return denied
|
|
130
|
+
const service = resolveNotificationPreferenceService(container)
|
|
131
|
+
const scope: NotificationPreferenceScope = { tenantId: auth.tenantId, userId: parsed.data.userId }
|
|
132
|
+
const guarded = await runGuardedNotificationWrite(
|
|
133
|
+
container,
|
|
134
|
+
{ tenantId: auth.tenantId, organizationId: auth.orgId ?? null, userId: auth.sub },
|
|
135
|
+
req,
|
|
136
|
+
{
|
|
137
|
+
resourceKind: NOTIFICATION_PREFERENCE_RESOURCE_KIND,
|
|
138
|
+
resourceId: parsed.data.userId,
|
|
139
|
+
operation: 'update',
|
|
140
|
+
payload: parsed.data as Record<string, unknown>,
|
|
141
|
+
},
|
|
142
|
+
() =>
|
|
143
|
+
service.setPreferences(
|
|
144
|
+
scope,
|
|
145
|
+
parsed.data.preferences.map((p) => ({ typeId: p.notificationTypeId, channel: p.channel, enabled: p.enabled })),
|
|
146
|
+
),
|
|
147
|
+
)
|
|
148
|
+
if (!guarded.ok) return guarded.response
|
|
149
|
+
|
|
150
|
+
// Skip the event on no-op writes (nothing actually changed).
|
|
151
|
+
if (guarded.result > 0) {
|
|
152
|
+
await emitNotificationEvent(PREFERENCE_UPDATED_EVENT, {
|
|
153
|
+
tenantId: auth.tenantId,
|
|
154
|
+
organizationId: auth.orgId ?? null,
|
|
155
|
+
userId: parsed.data.userId,
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return NextResponse.json({ ok: true })
|
|
160
|
+
} finally {
|
|
161
|
+
const disposable = container as unknown as { dispose?: () => Promise<void> }
|
|
162
|
+
if (typeof disposable.dispose === 'function') await disposable.dispose()
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const openApi = {
|
|
167
|
+
GET: {
|
|
168
|
+
summary: "Get a user's notification preferences (admin)",
|
|
169
|
+
description: "Returns a target user's stored channel preferences. Requires notifications.manage_user_preferences.",
|
|
170
|
+
tags: ['Notifications'],
|
|
171
|
+
parameters: [{ name: 'userId', in: 'query', required: true, schema: { type: 'string', format: 'uuid' } }],
|
|
172
|
+
responses: {
|
|
173
|
+
200: {
|
|
174
|
+
description: 'Stored preferences',
|
|
175
|
+
content: { 'application/json': { schema: z.object({ items: z.array(notificationPreferenceItemSchema) }) } },
|
|
176
|
+
},
|
|
177
|
+
401: { description: 'Unauthorized', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
178
|
+
404: { description: 'User not found', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
PUT: {
|
|
182
|
+
summary: "Update a user's notification preferences (admin)",
|
|
183
|
+
description: "Bulk-updates a target user's channel preferences. Requires notifications.manage_user_preferences.",
|
|
184
|
+
tags: ['Notifications'],
|
|
185
|
+
requestBody: { required: true, content: { 'application/json': { schema: adminUpdatePreferencesSchema } } },
|
|
186
|
+
responses: {
|
|
187
|
+
200: { description: 'Preferences updated', content: { 'application/json': { schema: z.object({ ok: z.boolean() }) } } },
|
|
188
|
+
400: { description: 'Invalid request body', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
189
|
+
401: { description: 'Unauthorized', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
190
|
+
404: { description: 'User not found', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
}
|
|
@@ -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'
|
|
6
|
+
import { errorResponseSchema } from '../openapi'
|
|
7
|
+
|
|
8
|
+
export const metadata = {
|
|
9
|
+
GET: { requireAuth: true, requireFeatures: ['notifications.view'] },
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const channelItemSchema = z.object({
|
|
13
|
+
id: z.string(),
|
|
14
|
+
labelKey: z.string(),
|
|
15
|
+
descriptionKey: z.string().nullable(),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export async function GET(req: Request) {
|
|
19
|
+
const { t } = await resolveTranslations()
|
|
20
|
+
const auth = await getAuthFromRequest(req)
|
|
21
|
+
if (!auth?.sub || !auth.tenantId) {
|
|
22
|
+
return NextResponse.json({ error: t('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const items = getNotificationChannels().map((channel) => ({
|
|
26
|
+
id: channel.id,
|
|
27
|
+
labelKey: channel.labelKey,
|
|
28
|
+
descriptionKey: channel.descriptionKey ?? null,
|
|
29
|
+
}))
|
|
30
|
+
return NextResponse.json({ items })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const openApi = {
|
|
34
|
+
GET: {
|
|
35
|
+
summary: 'List notification delivery channels',
|
|
36
|
+
description:
|
|
37
|
+
'Returns the registered delivery channel catalogue (in-app, email, push, …) so a preferences screen can render one column per channel without a hardcoded list.',
|
|
38
|
+
tags: ['Notifications'],
|
|
39
|
+
responses: {
|
|
40
|
+
200: {
|
|
41
|
+
description: 'Notification channel catalogue',
|
|
42
|
+
content: {
|
|
43
|
+
'application/json': {
|
|
44
|
+
schema: z.object({ items: z.array(channelItemSchema) }),
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
401: {
|
|
49
|
+
description: 'Unauthorized',
|
|
50
|
+
content: { 'application/json': { schema: errorResponseSchema } },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
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, type NotificationPreferenceScope } from '../../lib/notificationPreferenceService'
|
|
8
|
+
import {
|
|
9
|
+
runGuardedNotificationWrite,
|
|
10
|
+
notificationValidationErrorResponse,
|
|
11
|
+
NOTIFICATION_PREFERENCE_RESOURCE_KIND,
|
|
12
|
+
} from '../../lib/routeHelpers'
|
|
13
|
+
import { PREFERENCE_UPDATED_EVENT, emitNotificationEvent } from '../../events'
|
|
14
|
+
import { updatePreferencesSchema, notificationPreferenceItemSchema } from '../../data/validators'
|
|
15
|
+
import { errorResponseSchema } from '../openapi'
|
|
16
|
+
|
|
17
|
+
export const metadata = {
|
|
18
|
+
GET: { requireAuth: true, requireFeatures: ['notifications.manage_preferences'] },
|
|
19
|
+
PUT: { requireAuth: true, requireFeatures: ['notifications.manage_preferences'] },
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const unauthorized = async () => {
|
|
23
|
+
const { t } = await resolveTranslations()
|
|
24
|
+
return NextResponse.json({ error: t('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function GET(req: Request) {
|
|
28
|
+
const auth = await getAuthFromRequest(req)
|
|
29
|
+
if (!auth?.sub || !auth.tenantId) return await unauthorized()
|
|
30
|
+
|
|
31
|
+
const container = await createRequestContainer()
|
|
32
|
+
try {
|
|
33
|
+
const service = resolveNotificationPreferenceService(container)
|
|
34
|
+
const scope: NotificationPreferenceScope = { tenantId: auth.tenantId, userId: auth.sub }
|
|
35
|
+
const rows = await service.listForUser(scope)
|
|
36
|
+
const items = rows.map((row) => ({
|
|
37
|
+
notificationTypeId: row.notificationTypeId,
|
|
38
|
+
channel: row.channel,
|
|
39
|
+
enabled: row.enabled,
|
|
40
|
+
}))
|
|
41
|
+
return NextResponse.json({ items })
|
|
42
|
+
} finally {
|
|
43
|
+
const disposable = container as unknown as { dispose?: () => Promise<void> }
|
|
44
|
+
if (typeof disposable.dispose === 'function') await disposable.dispose()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function PUT(req: Request) {
|
|
49
|
+
const auth = await getAuthFromRequest(req)
|
|
50
|
+
if (!auth?.sub || !auth.tenantId) return await unauthorized()
|
|
51
|
+
|
|
52
|
+
const parsed = updatePreferencesSchema.safeParse(await readJsonSafe(req, {}))
|
|
53
|
+
if (!parsed.success) return notificationValidationErrorResponse(parsed.error)
|
|
54
|
+
|
|
55
|
+
const container = await createRequestContainer()
|
|
56
|
+
try {
|
|
57
|
+
const service = resolveNotificationPreferenceService(container)
|
|
58
|
+
const scope: NotificationPreferenceScope = { tenantId: auth.tenantId, userId: auth.sub }
|
|
59
|
+
const guarded = await runGuardedNotificationWrite(
|
|
60
|
+
container,
|
|
61
|
+
{ tenantId: auth.tenantId, organizationId: auth.orgId ?? null, userId: auth.sub },
|
|
62
|
+
req,
|
|
63
|
+
{
|
|
64
|
+
resourceKind: NOTIFICATION_PREFERENCE_RESOURCE_KIND,
|
|
65
|
+
resourceId: auth.sub,
|
|
66
|
+
operation: 'update',
|
|
67
|
+
payload: parsed.data as Record<string, unknown>,
|
|
68
|
+
},
|
|
69
|
+
() =>
|
|
70
|
+
service.setPreferences(
|
|
71
|
+
scope,
|
|
72
|
+
parsed.data.preferences.map((p) => ({
|
|
73
|
+
typeId: p.notificationTypeId,
|
|
74
|
+
channel: p.channel,
|
|
75
|
+
enabled: p.enabled,
|
|
76
|
+
})),
|
|
77
|
+
),
|
|
78
|
+
)
|
|
79
|
+
if (!guarded.ok) return guarded.response
|
|
80
|
+
|
|
81
|
+
// Skip the event on no-op writes (nothing actually changed).
|
|
82
|
+
if (guarded.result > 0) {
|
|
83
|
+
await emitNotificationEvent(PREFERENCE_UPDATED_EVENT, {
|
|
84
|
+
tenantId: auth.tenantId,
|
|
85
|
+
organizationId: auth.orgId ?? null,
|
|
86
|
+
userId: auth.sub,
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return NextResponse.json({ ok: true })
|
|
91
|
+
} finally {
|
|
92
|
+
const disposable = container as unknown as { dispose?: () => Promise<void> }
|
|
93
|
+
if (typeof disposable.dispose === 'function') await disposable.dispose()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const openApi = {
|
|
98
|
+
GET: {
|
|
99
|
+
summary: 'Get notification preferences',
|
|
100
|
+
description: "Returns the current user's stored channel preferences (absent rows default to enabled).",
|
|
101
|
+
tags: ['Notifications'],
|
|
102
|
+
responses: {
|
|
103
|
+
200: {
|
|
104
|
+
description: 'Stored preferences',
|
|
105
|
+
content: {
|
|
106
|
+
'application/json': {
|
|
107
|
+
schema: z.object({ items: z.array(notificationPreferenceItemSchema) }),
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
401: { description: 'Unauthorized', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
PUT: {
|
|
115
|
+
summary: 'Update notification preferences',
|
|
116
|
+
description: "Bulk-updates the current user's channel preferences.",
|
|
117
|
+
tags: ['Notifications'],
|
|
118
|
+
requestBody: {
|
|
119
|
+
required: true,
|
|
120
|
+
content: { 'application/json': { schema: updatePreferencesSchema } },
|
|
121
|
+
},
|
|
122
|
+
responses: {
|
|
123
|
+
200: {
|
|
124
|
+
description: 'Preferences updated',
|
|
125
|
+
content: { 'application/json': { schema: z.object({ ok: z.boolean() }) } },
|
|
126
|
+
},
|
|
127
|
+
400: { description: 'Invalid request body', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
128
|
+
401: { description: 'Unauthorized', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
}
|
|
@@ -12,6 +12,7 @@ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'
|
|
|
12
12
|
import { Notification } from '../data/entities'
|
|
13
13
|
import { listNotificationsSchema, createNotificationSchema } from '../data/validators'
|
|
14
14
|
import { toNotificationDto } from '../lib/notificationMapper'
|
|
15
|
+
import { inAppVisibleFilter } from '../lib/notificationVisibility'
|
|
15
16
|
import {
|
|
16
17
|
buildNotificationReadScopeWhere,
|
|
17
18
|
getNotificationReadScopeTagOrganizationIds,
|
|
@@ -136,7 +137,13 @@ export async function GET(req: Request) {
|
|
|
136
137
|
const filters: Record<string, unknown> = {
|
|
137
138
|
recipientUserId: userId,
|
|
138
139
|
tenantId: scope.tenantId,
|
|
139
|
-
|
|
140
|
+
// Read scope AND in-app visibility: both fragments carry their own `$or`, so they must be
|
|
141
|
+
// AND-composed instead of spread (a spread would silently drop one of them).
|
|
142
|
+
// Hidden rows are those not delivered to the in-app channel (push-only / opted-out); they remain as records.
|
|
143
|
+
$and: [
|
|
144
|
+
buildNotificationReadScopeWhere(scope),
|
|
145
|
+
inAppVisibleFilter(),
|
|
146
|
+
],
|
|
140
147
|
}
|
|
141
148
|
|
|
142
149
|
if (input.status) {
|