@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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/notifications/subscribers/deliver-notification.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { Notification } from '../data/entities'\nimport { NOTIFICATION_EVENTS } from '../lib/events'\nimport { DEFAULT_NOTIFICATION_DELIVERY_CONFIG, resolveNotificationDeliveryConfig, resolveNotificationPanelUrl } from '../lib/deliveryConfig'\nimport { getNotificationDeliveryStrategies } from '../lib/deliveryStrategies'\nimport { sendEmail } from '@open-mercato/shared/lib/email/send'\nimport NotificationEmail from '../emails/NotificationEmail'\nimport { loadDictionary } from '@open-mercato/shared/lib/i18n/server'\nimport { createFallbackTranslator } from '@open-mercato/shared/lib/i18n/translate'\nimport { defaultLocale } from '@open-mercato/shared/lib/i18n/config'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { User } from '../../auth/data/entities'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('notifications').child({ component: 'deliver' })\n\nexport const metadata = {\n event: NOTIFICATION_EVENTS.CREATED,\n persistent: true,\n id: 'notifications:deliver',\n}\n\nfunction debug(message: string, ...details: unknown[]): void {\n logger.debug(message, details.length ? { details } : undefined)\n}\n\ntype NotificationCreatedPayload = {\n notificationId: string\n recipientUserId: string\n tenantId: string\n organizationId?: string | null\n}\n\ntype ResolverContext = {\n resolve: <T = unknown>(name: string) => T\n}\n\nconst buildPanelLink = (panelUrl: string, notificationId: string) => {\n if (panelUrl.startsWith('http://') || panelUrl.startsWith('https://')) {\n const url = new URL(panelUrl)\n url.searchParams.set('notificationId', notificationId)\n return url.toString()\n }\n const separator = panelUrl.includes('?') ? '&' : '?'\n return `${panelUrl}${separator}notificationId=${encodeURIComponent(notificationId)}`\n}\n\nconst resolveNotificationCopy = async (\n notification: Notification\n) => {\n const dict = await loadDictionary(defaultLocale)\n const t = createFallbackTranslator(dict)\n\n const title = notification.titleKey\n ? t(notification.titleKey, notification.title ?? notification.titleKey, notification.titleVariables ?? undefined)\n : notification.title\n\n const body = notification.bodyKey\n ? t(notification.bodyKey, notification.body ?? notification.bodyKey ?? '', notification.bodyVariables ?? undefined)\n : notification.body ?? null\n\n return { title, body, t }\n}\n\nconst resolveRecipient = async (\n em: EntityManager,\n notification: Notification,\n encryptionService?: TenantDataEncryptionService | null,\n) => {\n const where: Partial<User> & { deletedAt?: null } = {\n id: notification.recipientUserId,\n tenantId: notification.tenantId,\n deletedAt: null,\n }\n if (notification.organizationId) {\n where.organizationId = notification.organizationId\n }\n const record = await findOneWithDecryption(\n em,\n User,\n where,\n undefined,\n {\n tenantId: notification.tenantId,\n organizationId: notification.organizationId ?? null,\n encryptionService: encryptionService ?? null,\n },\n )\n if (!record) return null\n return {\n email: typeof record.email === 'string' ? record.email : null,\n name: typeof record.name === 'string' ? record.name : null,\n }\n}\n\n\nexport default async function handle(payload: NotificationCreatedPayload, ctx: ResolverContext) {\n debug('deliver notification event', payload)\n const deliveryConfig = await resolveNotificationDeliveryConfig(ctx, { defaultValue: DEFAULT_NOTIFICATION_DELIVERY_CONFIG })\n if (!deliveryConfig.strategies.email.enabled) {\n debug('email delivery disabled')\n }\n\n const em = ctx.resolve('em') as EntityManager\n const notification = await findOneWithDecryption(\n em,\n Notification,\n {\n id: payload.notificationId,\n tenantId: payload.tenantId,\n organizationId: payload.organizationId ?? null,\n },\n undefined,\n {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId ?? null,\n encryptionService: null,\n },\n )\n if (!notification) {\n debug('notification not found', payload.notificationId)\n return\n }\n\n let encryptionService: TenantDataEncryptionService | null = null\n try {\n encryptionService = ctx.resolve<TenantDataEncryptionService>('tenantEncryptionService')\n } catch {\n encryptionService = null\n }\n\n try {\n const recipient = (await resolveRecipient(em, notification, encryptionService)) ?? { email: null, name: null }\n if (!recipient?.email) {\n debug('recipient has no email', notification.recipientUserId)\n }\n const { title, body, t } = await resolveNotificationCopy(notification)\n const panelUrl = resolveNotificationPanelUrl(deliveryConfig)\n if (!panelUrl) {\n debug('missing panelUrl; check appUrl/panelPath settings')\n }\n\n const panelLink = panelUrl ? buildPanelLink(panelUrl, notification.id) : null\n const baseOrigin = panelUrl ? new URL(panelUrl).origin : null\n const actionLinks = (notification.actionData?.actions ?? [])\n .map((action) => {\n let href = action.href\n if (href && notification.sourceEntityId) {\n href = href.replace('{sourceEntityId}', notification.sourceEntityId)\n }\n const fullHref = (href && baseOrigin) ? `${baseOrigin}${href}` : panelLink\n if (!fullHref) return null\n return {\n id: action.id,\n label: action.labelKey ? t(action.labelKey, action.label) : action.label,\n href: fullHref,\n }\n })\n .filter((action): action is NonNullable<typeof action> => action !== null)\n\n if (deliveryConfig.strategies.email.enabled && recipient?.email && panelLink) {\n const subjectPrefix = deliveryConfig.strategies.email.subjectPrefix?.trim()\n const subject = subjectPrefix ? `${subjectPrefix} ${title}` : title\n const copy = {\n preview: t('notifications.delivery.email.preview', 'New notification'),\n heading: t('notifications.delivery.email.heading', 'You have a new notification'),\n bodyIntro: t('notifications.delivery.email.bodyIntro', 'Review the notification details and take any required actions.'),\n actionNotice: t('notifications.delivery.email.actionNotice', 'Actions are available in Open Mercato and are read-only in this email.'),\n openCta: t('notifications.delivery.email.openCta', 'Open notification center'),\n footer: t('notifications.delivery.email.footer', 'Open Mercato notifications'),\n }\n\n try {\n debug('sending email', { recipientUserId: notification.recipientUserId })\n await sendEmail({\n to: recipient.email,\n subject,\n from: deliveryConfig.strategies.email.from,\n replyTo: deliveryConfig.strategies.email.replyTo,\n react: NotificationEmail({\n title,\n body,\n actions: actionLinks,\n panelUrl: panelLink,\n copy,\n }),\n })\n } catch (error) {\n logger.error('Email delivery failed', { err: error })\n }\n }\n\n const strategyConfigs = deliveryConfig.strategies.custom ?? {}\n const strategies = getNotificationDeliveryStrategies()\n for (const strategy of strategies) {\n const strategyConfig = strategyConfigs[strategy.id]\n const enabled = strategyConfig?.enabled ?? strategy.defaultEnabled ?? false\n if (!enabled) {\n debug('custom delivery disabled', strategy.id)\n continue\n }\n try {\n await strategy.deliver({\n notification,\n recipient,\n title,\n body,\n panelUrl,\n panelLink,\n actionLinks,\n deliveryConfig,\n config: strategyConfig ?? {},\n resolve: ctx.resolve,\n t,\n })\n } catch (error) {\n logger.error('Delivery strategy failed', { strategyId: strategy.id, err: error })\n }\n }\n } catch (err) {\n logger.error('Failed to deliver notification', { notificationId: payload.notificationId, recipientUserId: notification.recipientUserId, err })\n throw err\n }\n\n return\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AACpC,SAAS,sCAAsC,mCAAmC,mCAAmC;AACrH,SAAS,
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { Notification } from '../data/entities'\nimport { NOTIFICATION_EVENTS } from '../lib/events'\nimport { DEFAULT_NOTIFICATION_DELIVERY_CONFIG, resolveNotificationDeliveryConfig, resolveNotificationPanelUrl } from '../lib/deliveryConfig'\nimport { getNotificationDeliveryStrategies, type NotificationDeliveryContext } from '../lib/deliveryStrategies'\nimport { resolveEffectiveChannels } from '../lib/shouldDeliver'\nimport { getNotificationType } from '../lib/notification-type-registry'\nimport { getNotificationTypeOverrides } from '../lib/typeOverrides'\nimport { resolveNotificationPreferenceService } from '../lib/notificationPreferenceService'\nimport { resolveNotificationCopy } from '../lib/notificationCopy'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { User } from '../../auth/data/entities'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('notifications').child({ component: 'deliver' })\n\nexport const metadata = {\n event: NOTIFICATION_EVENTS.CREATED,\n persistent: true,\n id: 'notifications:deliver',\n}\n\nfunction debug(message: string, ...details: unknown[]): void {\n logger.debug(message, details.length ? { details } : undefined)\n}\n\ntype NotificationCreatedPayload = {\n notificationId: string\n recipientUserId: string\n tenantId: string\n organizationId?: string | null\n}\n\ntype ResolverContext = {\n resolve: <T = unknown>(name: string) => T\n}\n\nconst buildPanelLink = (panelUrl: string, notificationId: string) => {\n if (panelUrl.startsWith('http://') || panelUrl.startsWith('https://')) {\n const url = new URL(panelUrl)\n url.searchParams.set('notificationId', notificationId)\n return url.toString()\n }\n const separator = panelUrl.includes('?') ? '&' : '?'\n return `${panelUrl}${separator}notificationId=${encodeURIComponent(notificationId)}`\n}\n\nconst resolveRecipient = async (\n em: EntityManager,\n notification: Notification,\n encryptionService?: TenantDataEncryptionService | null,\n) => {\n const where: Partial<User> & { deletedAt?: null } = {\n id: notification.recipientUserId,\n tenantId: notification.tenantId,\n deletedAt: null,\n }\n if (notification.organizationId) {\n where.organizationId = notification.organizationId\n }\n const record = await findOneWithDecryption(\n em,\n User,\n where,\n undefined,\n {\n tenantId: notification.tenantId,\n organizationId: notification.organizationId ?? null,\n encryptionService: encryptionService ?? null,\n },\n )\n if (!record) return null\n return {\n email: typeof record.email === 'string' ? record.email : null,\n name: typeof record.name === 'string' ? record.name : null,\n }\n}\n\n/**\n * Dispatches a created notification across every registered delivery channel. This is a pure\n * \"resolve copy \u2192 loop strategies\" loop with NO channel-specific branches: in-app, email, and push\n * are all first-class strategies on the seam. Per-channel opt-out / `nonOptOut` / `silent` /\n * eligibility / per-send targeting were already resolved once at create time into\n * `notification.channels` (see `shouldDeliver`); here we only replay that target set and apply each\n * strategy's technical short-circuits (`supports`, `isConfigured`). `notification.channels === null`\n * means \"all channels\" (legacy rows + pre-Phase-7 behavior).\n */\nexport default async function handle(payload: NotificationCreatedPayload, ctx: ResolverContext) {\n debug('deliver notification event', payload)\n const deliveryConfig = await resolveNotificationDeliveryConfig(ctx, { defaultValue: DEFAULT_NOTIFICATION_DELIVERY_CONFIG })\n\n const em = ctx.resolve('em') as EntityManager\n const notification = await findOneWithDecryption(\n em,\n Notification,\n {\n id: payload.notificationId,\n tenantId: payload.tenantId,\n organizationId: payload.organizationId ?? null,\n },\n undefined,\n {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId ?? null,\n encryptionService: null,\n },\n )\n if (!notification) {\n debug('notification not found', payload.notificationId)\n return\n }\n\n let encryptionService: TenantDataEncryptionService | null = null\n try {\n encryptionService = ctx.resolve<TenantDataEncryptionService>('tenantEncryptionService')\n } catch {\n encryptionService = null\n }\n\n try {\n const recipient = (await resolveRecipient(em, notification, encryptionService)) ?? { email: null, name: null }\n if (!recipient?.email) {\n debug('recipient has no email', notification.recipientUserId)\n }\n const { title, body, t } = await resolveNotificationCopy(notification)\n const panelUrl = resolveNotificationPanelUrl(deliveryConfig)\n if (!panelUrl) {\n debug('missing panelUrl; check appUrl/panelPath settings')\n }\n\n const panelLink = panelUrl ? buildPanelLink(panelUrl, notification.id) : null\n const baseOrigin = panelUrl ? new URL(panelUrl).origin : null\n const actionLinks = (notification.actionData?.actions ?? [])\n .map((action) => {\n let href = action.href\n if (href && notification.sourceEntityId) {\n href = href.replace('{sourceEntityId}', notification.sourceEntityId)\n }\n const fullHref = (href && baseOrigin) ? `${baseOrigin}${href}` : panelLink\n if (!fullHref) return null\n return {\n id: action.id,\n label: action.labelKey ? t(action.labelKey, action.label) : action.label,\n href: fullHref,\n }\n })\n .filter((action): action is NonNullable<typeof action> => action !== null)\n\n const strategyConfigs = deliveryConfig.strategies.custom ?? {}\n const strategies = getNotificationDeliveryStrategies()\n // Authoritative per-send target resolved at create time. A null snapshot (legacy pre-Phase-7 row,\n // or a create that ran before the delivery strategies were registered) would otherwise deliver\n // every channel with no gate \u2014 so recompute the effective set from current preferences here,\n // keeping the single `shouldDeliver` gate instead of re-checking opt-out inside each strategy.\n const persistedChannels = notification.channels\n const registeredStrategyIds = strategies.map((strategy) => strategy.id)\n let targetChannels: string[] | null = persistedChannels ?? null\n // Only recompute for a null-snapshot row when strategies are actually registered \u2014 mirror the\n // create path (`resolveChannelsFor`: `registeredChannels.length === 0 => null`). With zero\n // strategies, `resolveEffectiveChannels` returns `[]` (never null); persisting that would HIDE\n // the row (the visibility layer treats `[]` as \"no channels\"), so leave `channels` null and keep\n // legacy all-channels back-compat instead.\n if (persistedChannels == null && registeredStrategyIds.length > 0) {\n try {\n const typeOverrides = (\n await getNotificationTypeOverrides(em as EntityManager, notification.tenantId, [notification.type])\n ).get(notification.type)\n targetChannels = await resolveEffectiveChannels({\n typeId: notification.type,\n type: getNotificationType(notification.type),\n scope: { tenantId: notification.tenantId, userId: notification.recipientUserId },\n targetChannels: null,\n registeredChannels: registeredStrategyIds,\n preferences: resolveNotificationPreferenceService({ resolve: ctx.resolve }),\n channelsOverride: typeOverrides?.channels ?? null,\n nonOptOutOverride: typeOverrides?.nonOptOut ?? null,\n })\n } catch (err) {\n // Fail CLOSED, not open. Falling back to `null` here means \"deliver EVERY registered channel\"\n // \u2014 which now includes push and email \u2014 for a type the user may have opted out of, so a\n // transient overrides/preference-service blip would leak a push/email on opt-out. Re-throw\n // instead: this subscriber is `persistent` (retried with backoff) and the recompute runs\n // BEFORE any strategy delivers, so the retry recomputes the correct target set with no\n // double-send. In-app visibility is unaffected meanwhile \u2014 the row's `channels` stays null,\n // which the visibility layer already treats as \"visible everywhere\".\n logger.error('failed to recompute delivery channels; retrying via persistent subscriber', {\n notificationId: notification.id,\n type: notification.type,\n err,\n })\n throw err\n }\n }\n // Persist the recomputed set back onto a null-channels row so the in-app\n // VISIBILITY path (bell/inbox/unread \u2014 see notificationVisibility.ts) reads\n // the same authoritative target the DELIVERY gate just applied. Without this\n // the row stays `null` \u21D2 \"visible everywhere\", so a notification suppressed\n // from in_app by the user's opt-out would still surface in the bell while\n // delivery correctly skipped it. `targetChannels` is null (skip persist) only\n // when no strategies are registered or the recompute fell back \u2014 both keep the\n // row null for legacy all-channels back-compat. `channels` is a plaintext JSONB\n // column, so a forked nativeUpdate avoids the shared UoW.\n if (persistedChannels == null && targetChannels != null) {\n try {\n await (em as EntityManager)\n .fork()\n .nativeUpdate(\n Notification,\n { id: notification.id, tenantId: notification.tenantId },\n { channels: targetChannels },\n )\n } catch (err) {\n debug('failed to persist recomputed channels', err)\n }\n }\n for (const strategy of strategies) {\n if (targetChannels && !targetChannels.includes(strategy.id)) {\n debug('channel not targeted', strategy.id)\n continue\n }\n const strategyConfig = strategyConfigs[strategy.id]\n const enabled = strategyConfig?.enabled ?? strategy.defaultEnabled ?? false\n if (!enabled) {\n debug('delivery disabled', strategy.id)\n continue\n }\n if (strategy.supports && !strategy.supports(notification)) {\n debug('strategy does not support notification', strategy.id)\n continue\n }\n const context: NotificationDeliveryContext = {\n notification,\n recipient,\n title,\n body,\n panelUrl,\n panelLink,\n actionLinks,\n deliveryConfig,\n config: strategyConfig ?? {},\n resolve: ctx.resolve,\n t,\n }\n try {\n if (strategy.isConfigured && !(await strategy.isConfigured(context))) {\n debug('strategy not configured', strategy.id)\n continue\n }\n await strategy.deliver(context)\n } catch (error) {\n logger.error('Delivery strategy failed', { strategyId: strategy.id, err: error })\n }\n }\n } catch (err) {\n logger.error('Failed to deliver notification', { notificationId: payload.notificationId, recipientUserId: notification.recipientUserId, err })\n throw err\n }\n\n return\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { TYPE_REGISTRY_SYNC_EVENT } from "../events.js";
|
|
2
|
+
import { syncNotificationTypes } from "../lib/notification-type-registry.js";
|
|
3
|
+
const metadata = {
|
|
4
|
+
event: TYPE_REGISTRY_SYNC_EVENT,
|
|
5
|
+
persistent: true,
|
|
6
|
+
id: "notifications:sync-notification-types"
|
|
7
|
+
};
|
|
8
|
+
async function handler(_payload, ctx) {
|
|
9
|
+
const em = ctx.resolve("em");
|
|
10
|
+
await syncNotificationTypes(em, { force: true });
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
handler as default,
|
|
14
|
+
metadata
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=sync-notification-types.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/notifications/subscribers/sync-notification-types.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { TYPE_REGISTRY_SYNC_EVENT } from '../events'\nimport { syncNotificationTypes } from '../lib/notification-type-registry'\n\nexport const metadata = {\n event: TYPE_REGISTRY_SYNC_EVENT,\n persistent: true,\n id: 'notifications:sync-notification-types',\n}\n\ntype ResolverContext = {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport default async function handler(_payload: unknown, ctx: ResolverContext): Promise<void> {\n const em = ctx.resolve<EntityManager>('em')\n await syncNotificationTypes(em, { force: true })\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,gCAAgC;AACzC,SAAS,6BAA6B;AAE/B,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAMA,eAAO,QAA+B,UAAmB,KAAqC;AAC5F,QAAM,KAAK,IAAI,QAAuB,IAAI;AAC1C,QAAM,sBAAsB,IAAI,EAAE,OAAO,KAAK,CAAC;AACjD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { InjectionPosition } from "@open-mercato/shared/modules/widgets/injection-position";
|
|
2
|
+
const widget = {
|
|
3
|
+
metadata: {
|
|
4
|
+
id: "notifications.injection.profile-preferences-menu",
|
|
5
|
+
title: "Notification preferences profile menu item"
|
|
6
|
+
},
|
|
7
|
+
menuItems: [
|
|
8
|
+
{
|
|
9
|
+
id: "notification-preferences-profile-link",
|
|
10
|
+
labelKey: "notifications.preferences.pageTitle",
|
|
11
|
+
label: "Notification Preferences",
|
|
12
|
+
icon: "Bell",
|
|
13
|
+
href: "/backend/profile/notification-preferences",
|
|
14
|
+
features: ["notifications.manage_preferences"],
|
|
15
|
+
placement: { position: InjectionPosition.After, relativeTo: "change-password" }
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
};
|
|
19
|
+
var widget_default = widget;
|
|
20
|
+
export {
|
|
21
|
+
widget_default as default
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=widget.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/notifications/widgets/injection/profile-preferences-menu/widget.ts"],
|
|
4
|
+
"sourcesContent": ["import { InjectionPosition } from '@open-mercato/shared/modules/widgets/injection-position'\nimport type { InjectionMenuItemWidget } from '@open-mercato/shared/modules/widgets/injection'\n\n/**\n * Adds a \"Notification Preferences\" entry to the top-right profile dropdown,\n * next to \"My communication channels\". The per-user preferences page\n * (`/backend/profile/notification-preferences`) is `pageContext: 'profile'`, so\n * it is excluded from the main sidebar and otherwise only reachable via the\n * profile-mode sidebar or a direct URL \u2014 this gives it a discoverable entry.\n * Mirrors `communication_channels.injection.profile-channels-menu`.\n *\n * Feature-gated on `notifications.manage_preferences` (the same guard as the\n * page), so users who cannot edit preferences don't see a dead link. Wildcard\n * grants (`notifications.*`, `*`) are honored by the menu filter's\n * wildcard-aware matcher.\n */\nconst widget: InjectionMenuItemWidget = {\n metadata: {\n id: 'notifications.injection.profile-preferences-menu',\n title: 'Notification preferences profile menu item',\n },\n menuItems: [\n {\n id: 'notification-preferences-profile-link',\n labelKey: 'notifications.preferences.pageTitle',\n label: 'Notification Preferences',\n icon: 'Bell',\n href: '/backend/profile/notification-preferences',\n features: ['notifications.manage_preferences'],\n placement: { position: InjectionPosition.After, relativeTo: 'change-password' },\n },\n ],\n}\n\nexport default widget\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,yBAAyB;AAgBlC,MAAM,SAAkC;AAAA,EACtC,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,EACT;AAAA,EACA,WAAW;AAAA,IACT;AAAA,MACE,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,OAAO;AAAA,MACP,MAAM;AAAA,MACN,MAAM;AAAA,MACN,UAAU,CAAC,kCAAkC;AAAA,MAC7C,WAAW,EAAE,UAAU,kBAAkB,OAAO,YAAY,kBAAkB;AAAA,IAChF;AAAA,EACF;AACF;AAEA,IAAO,iBAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const injectionTable = {
|
|
2
|
+
"menu:topbar:profile-dropdown": [
|
|
3
|
+
{
|
|
4
|
+
widgetId: "notifications.injection.profile-preferences-menu",
|
|
5
|
+
priority: 90
|
|
6
|
+
}
|
|
7
|
+
]
|
|
8
|
+
};
|
|
9
|
+
var injection_table_default = injectionTable;
|
|
10
|
+
export {
|
|
11
|
+
injection_table_default as default,
|
|
12
|
+
injectionTable
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=injection-table.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/notifications/widgets/injection-table.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'\n\n/**\n * Profile dropdown (top-right avatar): a discoverable entry to the per-user\n * notification-preferences page, placed directly under \"Change Password\" \u2014\n * the same pattern `communication_channels` uses for its per-user\n * channel-connect page.\n */\nexport const injectionTable: ModuleInjectionTable = {\n 'menu:topbar:profile-dropdown': [\n {\n widgetId: 'notifications.injection.profile-preferences-menu',\n priority: 90,\n },\n ],\n}\n\nexport default injectionTable\n"],
|
|
5
|
+
"mappings": "AAQO,MAAM,iBAAuC;AAAA,EAClD,gCAAgC;AAAA,IAC9B;AAAA,MACE,UAAU;AAAA,MACV,UAAU;AAAA,IACZ;AAAA,EACF;AACF;AAEA,IAAO,0BAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const features = [
|
|
2
|
+
// Admin observability over the push delivery log. There are no self-serve push features:
|
|
3
|
+
// device/token lifecycle lives in the `devices` module, per-user opt-out in `notifications`.
|
|
4
|
+
{ id: "push_notifications.view_deliveries", title: "View push delivery log", module: "push_notifications" },
|
|
5
|
+
// Admin one-off custom push send (bypasses opt-out via the nonOptOut admin.custom_message type).
|
|
6
|
+
{ id: "push_notifications.send_custom", title: "Send custom push notifications", module: "push_notifications" }
|
|
7
|
+
];
|
|
8
|
+
var acl_default = features;
|
|
9
|
+
export {
|
|
10
|
+
acl_default as default,
|
|
11
|
+
features
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=acl.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/modules/push_notifications/acl.ts"],
|
|
4
|
+
"sourcesContent": ["export const features = [\n // Admin observability over the push delivery log. There are no self-serve push features:\n // device/token lifecycle lives in the `devices` module, per-user opt-out in `notifications`.\n { id: 'push_notifications.view_deliveries', title: 'View push delivery log', module: 'push_notifications' },\n // Admin one-off custom push send (bypasses opt-out via the nonOptOut admin.custom_message type).\n { id: 'push_notifications.send_custom', title: 'Send custom push notifications', module: 'push_notifications' },\n]\n\nexport default features\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,WAAW;AAAA;AAAA;AAAA,EAGtB,EAAE,IAAI,sCAAsC,OAAO,0BAA0B,QAAQ,qBAAqB;AAAA;AAAA,EAE1G,EAAE,IAAI,kCAAkC,OAAO,kCAAkC,QAAQ,qBAAqB;AAChH;AAEA,IAAO,cAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
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 { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
6
|
+
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
7
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
8
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
9
|
+
import {
|
|
10
|
+
bridgeLegacyGuard,
|
|
11
|
+
runMutationGuards
|
|
12
|
+
} from "@open-mercato/shared/lib/crud/mutation-guard-registry";
|
|
13
|
+
import {
|
|
14
|
+
customSendSchema,
|
|
15
|
+
customSendResponseSchema,
|
|
16
|
+
CUSTOM_SEND_NO_DEVICES_WARNING
|
|
17
|
+
} from "../../data/validators.js";
|
|
18
|
+
const logger = createLogger("push_notifications");
|
|
19
|
+
const RESOURCE_KIND = "push_notifications.push_notification_delivery";
|
|
20
|
+
const errorResponseSchema = z.object({ error: z.string() });
|
|
21
|
+
const metadata = {
|
|
22
|
+
POST: { requireAuth: true, requireFeatures: ["push_notifications.send_custom"] }
|
|
23
|
+
};
|
|
24
|
+
function resolveUserFeatures(auth) {
|
|
25
|
+
const features = auth?.features;
|
|
26
|
+
if (!Array.isArray(features)) return [];
|
|
27
|
+
return features.filter((value) => typeof value === "string");
|
|
28
|
+
}
|
|
29
|
+
async function runGuards(container, userFeatures, input) {
|
|
30
|
+
const legacyGuard = bridgeLegacyGuard(container);
|
|
31
|
+
if (!legacyGuard) return { ok: true, afterSuccessCallbacks: [] };
|
|
32
|
+
return runMutationGuards([legacyGuard], input, { userFeatures });
|
|
33
|
+
}
|
|
34
|
+
async function POST(req) {
|
|
35
|
+
const { translate } = await resolveTranslations();
|
|
36
|
+
try {
|
|
37
|
+
const container = await createRequestContainer();
|
|
38
|
+
const auth = await getAuthFromRequest(req);
|
|
39
|
+
if (!auth || !auth.tenantId || !auth.sub) {
|
|
40
|
+
return NextResponse.json(
|
|
41
|
+
{ error: translate("push_notifications.errors.unauthorized", "Unauthorized") },
|
|
42
|
+
{ status: 401 }
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const body = customSendSchema.parse(await readJsonSafe(req, {}));
|
|
46
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
|
|
47
|
+
const organizationId = scope?.selectedId ?? auth.orgId ?? null;
|
|
48
|
+
const guardInput = {
|
|
49
|
+
tenantId: auth.tenantId,
|
|
50
|
+
organizationId,
|
|
51
|
+
userId: auth.sub,
|
|
52
|
+
resourceKind: RESOURCE_KIND,
|
|
53
|
+
resourceId: body.recipientUserId,
|
|
54
|
+
operation: "create",
|
|
55
|
+
requestMethod: req.method,
|
|
56
|
+
requestHeaders: req.headers,
|
|
57
|
+
mutationPayload: body
|
|
58
|
+
};
|
|
59
|
+
const guardResult = await runGuards(container, resolveUserFeatures(auth), guardInput);
|
|
60
|
+
if (!guardResult.ok) {
|
|
61
|
+
return NextResponse.json(
|
|
62
|
+
guardResult.errorBody ?? { error: translate("push_notifications.errors.send_failed", "Operation blocked") },
|
|
63
|
+
{ status: guardResult.errorStatus ?? 422 }
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const service = container.resolve("pushNotificationService");
|
|
67
|
+
const result = await service.sendCustomPush({
|
|
68
|
+
resolve: ((name) => container.resolve(name)),
|
|
69
|
+
tenantId: auth.tenantId,
|
|
70
|
+
userId: body.recipientUserId,
|
|
71
|
+
organizationId,
|
|
72
|
+
deviceId: body.deviceId,
|
|
73
|
+
title: body.title,
|
|
74
|
+
body: body.body ?? null,
|
|
75
|
+
data: body.data,
|
|
76
|
+
pushOptions: body.pushOptions,
|
|
77
|
+
silent: body.silent ?? false
|
|
78
|
+
});
|
|
79
|
+
for (const callback of guardResult.afterSuccessCallbacks) {
|
|
80
|
+
if (!callback.guard.afterSuccess) continue;
|
|
81
|
+
await callback.guard.afterSuccess({
|
|
82
|
+
...guardInput,
|
|
83
|
+
resourceId: body.recipientUserId,
|
|
84
|
+
metadata: callback.metadata ?? null
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const responseBody = result.enqueued === 0 ? {
|
|
88
|
+
enqueued: 0,
|
|
89
|
+
warning: CUSTOM_SEND_NO_DEVICES_WARNING,
|
|
90
|
+
message: translate(
|
|
91
|
+
"push_notifications.warnings.no_matching_devices_in_scope",
|
|
92
|
+
"No push-capable devices matched this recipient in the selected scope, so nothing was sent."
|
|
93
|
+
)
|
|
94
|
+
} : { enqueued: result.enqueued };
|
|
95
|
+
return NextResponse.json(responseBody, { status: result.enqueued === 0 ? 200 : 201 });
|
|
96
|
+
} catch (err) {
|
|
97
|
+
if (err instanceof z.ZodError) {
|
|
98
|
+
return NextResponse.json(
|
|
99
|
+
{ error: translate("push_notifications.errors.invalid_payload", "Invalid request"), details: err.flatten() },
|
|
100
|
+
{ status: 400 }
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
logger.error("push_notifications.custom-send.POST failed", { err });
|
|
104
|
+
return NextResponse.json(
|
|
105
|
+
{ error: translate("push_notifications.errors.send_failed", "Failed to send push notification") },
|
|
106
|
+
{ status: 500 }
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const openApi = {
|
|
111
|
+
POST: {
|
|
112
|
+
summary: "Send a custom push notification",
|
|
113
|
+
description: "Admin-only: deliver a one-off, free-text visible push to all of a single user's push-capable devices. No in-app notification or email is created.",
|
|
114
|
+
tags: ["PushNotifications"],
|
|
115
|
+
requestBody: { schema: customSendSchema },
|
|
116
|
+
responses: {
|
|
117
|
+
200: {
|
|
118
|
+
description: "Nothing was deliverable in scope: `enqueued` is 0 and a `warning` code plus human `message` explain why (no silent no-op). Returned instead of 201 because nothing was created.",
|
|
119
|
+
content: { "application/json": { schema: customSendResponseSchema } }
|
|
120
|
+
},
|
|
121
|
+
201: {
|
|
122
|
+
description: "Per-device push jobs enqueued.",
|
|
123
|
+
content: { "application/json": { schema: customSendResponseSchema } }
|
|
124
|
+
},
|
|
125
|
+
400: {
|
|
126
|
+
description: "Invalid request",
|
|
127
|
+
content: { "application/json": { schema: errorResponseSchema } }
|
|
128
|
+
},
|
|
129
|
+
401: {
|
|
130
|
+
description: "Unauthorized",
|
|
131
|
+
content: { "application/json": { schema: errorResponseSchema } }
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
export {
|
|
137
|
+
POST,
|
|
138
|
+
metadata,
|
|
139
|
+
openApi
|
|
140
|
+
};
|
|
141
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/push_notifications/api/custom-send/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 { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport {\n bridgeLegacyGuard,\n runMutationGuards,\n type MutationGuard,\n type MutationGuardInput,\n} from '@open-mercato/shared/lib/crud/mutation-guard-registry'\nimport type { AwilixContainer } from 'awilix'\nimport {\n customSendSchema,\n customSendResponseSchema,\n CUSTOM_SEND_NO_DEVICES_WARNING,\n} from '../../data/validators'\nimport type { PushNotificationService } from '../../lib/send-custom-push'\n\nconst logger = createLogger('push_notifications')\n\nconst RESOURCE_KIND = 'push_notifications.push_notification_delivery'\n\nconst errorResponseSchema = z.object({ error: z.string() })\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['push_notifications.send_custom'] },\n}\n\nfunction resolveUserFeatures(auth: unknown): string[] {\n const features = (auth as { features?: unknown })?.features\n if (!Array.isArray(features)) return []\n return features.filter((value): value is string => typeof value === 'string')\n}\n\nasync function runGuards(\n container: AwilixContainer,\n userFeatures: string[],\n input: MutationGuardInput,\n): Promise<{\n ok: boolean\n errorBody?: Record<string, unknown>\n errorStatus?: number\n afterSuccessCallbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>\n}> {\n const legacyGuard = bridgeLegacyGuard(container)\n if (!legacyGuard) return { ok: true, afterSuccessCallbacks: [] }\n return runMutationGuards([legacyGuard], input, { userFeatures })\n}\n\nexport async function POST(req: Request) {\n const { translate } = await resolveTranslations()\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.sub) {\n return NextResponse.json(\n { error: translate('push_notifications.errors.unauthorized', 'Unauthorized') },\n { status: 401 },\n )\n }\n\n const body = customSendSchema.parse(await readJsonSafe(req, {}))\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n\n // Custom write route \u2192 wire the mutation-guard registry (AGENTS \u2192 API Routes). The send creates\n // append-only delivery rows; map it to a `create` on the delivery resource keyed by recipient.\n const guardInput: MutationGuardInput = {\n tenantId: auth.tenantId,\n organizationId,\n userId: auth.sub,\n resourceKind: RESOURCE_KIND,\n resourceId: body.recipientUserId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: body,\n }\n const guardResult = await runGuards(container, resolveUserFeatures(auth), guardInput)\n if (!guardResult.ok) {\n return NextResponse.json(\n guardResult.errorBody ?? { error: translate('push_notifications.errors.send_failed', 'Operation blocked') },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n const service = container.resolve('pushNotificationService') as PushNotificationService\n const result = await service.sendCustomPush({\n resolve: (<T = unknown,>(name: string): T => container.resolve(name) as T),\n tenantId: auth.tenantId,\n userId: body.recipientUserId,\n organizationId,\n deviceId: body.deviceId,\n title: body.title,\n body: body.body ?? null,\n data: body.data,\n pushOptions: body.pushOptions,\n silent: body.silent ?? false,\n })\n\n for (const callback of guardResult.afterSuccessCallbacks) {\n if (!callback.guard.afterSuccess) continue\n await callback.guard.afterSuccess({\n ...guardInput,\n resourceId: body.recipientUserId,\n metadata: callback.metadata ?? null,\n })\n }\n\n // A well-formed request that enqueued nothing (no push channel, no in-scope device, or no device\n // whose provider matches an active channel) previously returned a bare 201 \u2014 a silent\n // success-with-no-send that hid, for example, a tenant-level admin targeting org-scoped devices.\n // Surface an explicit machine-readable warning + human message so the caller can react.\n const responseBody: z.infer<typeof customSendResponseSchema> =\n result.enqueued === 0\n ? {\n enqueued: 0,\n warning: CUSTOM_SEND_NO_DEVICES_WARNING,\n message: translate(\n 'push_notifications.warnings.no_matching_devices_in_scope',\n 'No push-capable devices matched this recipient in the selected scope, so nothing was sent.',\n ),\n }\n : { enqueued: result.enqueued }\n\n // 201 Created only when jobs were actually enqueued; the no-op branch returns 200 OK so callers\n // that key off the status code aren't told something was created when nothing was.\n return NextResponse.json(responseBody, { status: result.enqueued === 0 ? 200 : 201 })\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json(\n { error: translate('push_notifications.errors.invalid_payload', 'Invalid request'), details: err.flatten() },\n { status: 400 },\n )\n }\n logger.error('push_notifications.custom-send.POST failed', { err })\n return NextResponse.json(\n { error: translate('push_notifications.errors.send_failed', 'Failed to send push notification') },\n { status: 500 },\n )\n }\n}\n\nexport const openApi = {\n POST: {\n summary: 'Send a custom push notification',\n description:\n \"Admin-only: deliver a one-off, free-text visible push to all of a single user's push-capable devices. No in-app notification or email is created.\",\n tags: ['PushNotifications'],\n requestBody: { schema: customSendSchema },\n responses: {\n 200: {\n description:\n 'Nothing was deliverable in scope: `enqueued` is 0 and a `warning` code plus human `message` explain why (no silent no-op). Returned instead of 201 because nothing was created.',\n content: { 'application/json': { schema: customSendResponseSchema } },\n },\n 201: {\n description: 'Per-device push jobs enqueued.',\n content: { 'application/json': { schema: customSendResponseSchema } },\n },\n 400: {\n description: 'Invalid request',\n content: { 'application/json': { schema: errorResponseSchema } },\n },\n 401: {\n description: 'Unauthorized',\n content: { 'application/json': { schema: errorResponseSchema } },\n },\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { logCrudAccess } from "@open-mercato/shared/lib/crud/factory";
|
|
5
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
6
|
+
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
7
|
+
import { isOrganizationReadAccessAllowed } from "@open-mercato/core/modules/directory/utils/organizationScopeGuard";
|
|
8
|
+
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
9
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
10
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
11
|
+
import { PushNotificationDelivery } from "../../../data/entities.js";
|
|
12
|
+
const logger = createLogger("push_notifications");
|
|
13
|
+
import { deliveryDetailItemSchema } from "../../../data/validators.js";
|
|
14
|
+
const metadata = {
|
|
15
|
+
GET: { requireAuth: true, requireFeatures: ["push_notifications.view_deliveries"] }
|
|
16
|
+
};
|
|
17
|
+
const paramsSchema = z.object({ id: z.string().uuid() });
|
|
18
|
+
function serializeDelivery(delivery) {
|
|
19
|
+
return {
|
|
20
|
+
id: delivery.id,
|
|
21
|
+
tenant_id: delivery.tenantId,
|
|
22
|
+
organization_id: delivery.organizationId ?? null,
|
|
23
|
+
notification_id: delivery.notificationId ?? null,
|
|
24
|
+
notification_type_id: delivery.notificationTypeId,
|
|
25
|
+
user_device_id: delivery.userDeviceId,
|
|
26
|
+
user_id: delivery.userId,
|
|
27
|
+
provider: delivery.provider,
|
|
28
|
+
token_snapshot: delivery.tokenSnapshot,
|
|
29
|
+
status: delivery.status,
|
|
30
|
+
attempts: delivery.attempts,
|
|
31
|
+
last_error: delivery.lastError ?? null,
|
|
32
|
+
payload: delivery.payload ?? null,
|
|
33
|
+
provider_response: delivery.providerResponse ?? null,
|
|
34
|
+
created_at: delivery.createdAt ? delivery.createdAt.toISOString() : null,
|
|
35
|
+
sent_at: delivery.sentAt ? delivery.sentAt.toISOString() : null,
|
|
36
|
+
next_retry_at: delivery.nextRetryAt ? delivery.nextRetryAt.toISOString() : null,
|
|
37
|
+
updated_at: delivery.updatedAt ? delivery.updatedAt.toISOString() : null
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async function GET(req, { params }) {
|
|
41
|
+
const { translate } = await resolveTranslations();
|
|
42
|
+
try {
|
|
43
|
+
const container = await createRequestContainer();
|
|
44
|
+
const auth = await getAuthFromRequest(req);
|
|
45
|
+
if (!auth || !auth.tenantId) {
|
|
46
|
+
return NextResponse.json({ error: translate("push_notifications.errors.unauthorized", "Unauthorized") }, { status: 401 });
|
|
47
|
+
}
|
|
48
|
+
const parsedParams = paramsSchema.safeParse({ id: params.id });
|
|
49
|
+
if (!parsedParams.success) {
|
|
50
|
+
return NextResponse.json({ error: translate("push_notifications.errors.invalid_id", "Invalid delivery id") }, { status: 400 });
|
|
51
|
+
}
|
|
52
|
+
const em = container.resolve("em");
|
|
53
|
+
const delivery = await em.findOne(PushNotificationDelivery, { id: parsedParams.data.id, tenantId: auth.tenantId });
|
|
54
|
+
if (!delivery) {
|
|
55
|
+
return NextResponse.json({ error: translate("push_notifications.errors.not_found", "Delivery not found") }, { status: 404 });
|
|
56
|
+
}
|
|
57
|
+
if (!isOrganizationReadAccessAllowed({
|
|
58
|
+
scope: await resolveOrganizationScopeForRequest({ container, auth, request: req }),
|
|
59
|
+
auth,
|
|
60
|
+
organizationId: delivery.organizationId ?? null
|
|
61
|
+
})) {
|
|
62
|
+
return NextResponse.json({ error: translate("push_notifications.errors.forbidden", "Access denied") }, { status: 403 });
|
|
63
|
+
}
|
|
64
|
+
const item = serializeDelivery(delivery);
|
|
65
|
+
await logCrudAccess({
|
|
66
|
+
container,
|
|
67
|
+
auth,
|
|
68
|
+
request: req,
|
|
69
|
+
items: [item],
|
|
70
|
+
idField: "id",
|
|
71
|
+
resourceKind: "push_notifications.push_notification_delivery",
|
|
72
|
+
organizationId: delivery.organizationId ?? null,
|
|
73
|
+
tenantId: auth.tenantId ?? null,
|
|
74
|
+
accessType: "read:item"
|
|
75
|
+
});
|
|
76
|
+
return NextResponse.json({ item });
|
|
77
|
+
} catch (err) {
|
|
78
|
+
if (isCrudHttpError(err)) {
|
|
79
|
+
return NextResponse.json(err.body, { status: err.status });
|
|
80
|
+
}
|
|
81
|
+
logger.error("push_notifications.deliveries.GET failed", { err });
|
|
82
|
+
return NextResponse.json({ error: translate("push_notifications.errors.server_error", "Something went wrong") }, { status: 500 });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const errorResponseSchema = z.object({ error: z.string() });
|
|
86
|
+
const detailResponseSchema = z.object({ item: deliveryDetailItemSchema });
|
|
87
|
+
const openApi = {
|
|
88
|
+
tag: "PushNotifications",
|
|
89
|
+
summary: "Read a single push delivery log row",
|
|
90
|
+
pathParams: paramsSchema,
|
|
91
|
+
methods: {
|
|
92
|
+
GET: {
|
|
93
|
+
summary: "Get a push delivery",
|
|
94
|
+
description: "Admin: fetch a single push delivery row (payload + provider response; no full push token).",
|
|
95
|
+
responses: [{ status: 200, description: "Push delivery", schema: detailResponseSchema }],
|
|
96
|
+
errors: [
|
|
97
|
+
{ status: 400, description: "Invalid id", schema: errorResponseSchema },
|
|
98
|
+
{ status: 401, description: "Unauthorized", schema: errorResponseSchema },
|
|
99
|
+
{ status: 403, description: "Missing push_notifications.view_deliveries", schema: errorResponseSchema },
|
|
100
|
+
{ status: 404, description: "Delivery not found", schema: errorResponseSchema }
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
export {
|
|
106
|
+
GET,
|
|
107
|
+
metadata,
|
|
108
|
+
openApi
|
|
109
|
+
};
|
|
110
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/push_notifications/api/deliveries/%5Bid%5D/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { logCrudAccess } from '@open-mercato/shared/lib/crud/factory'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { PushNotificationDelivery } from '../../../data/entities'\n\nconst logger = createLogger('push_notifications')\nimport { deliveryDetailItemSchema } from '../../../data/validators'\n\n// Read-only detail for a single push delivery row (admin observability).\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['push_notifications.view_deliveries'] },\n}\n\nconst paramsSchema = z.object({ id: z.string().uuid() })\n\nfunction serializeDelivery(delivery: PushNotificationDelivery) {\n return {\n id: delivery.id,\n tenant_id: delivery.tenantId,\n organization_id: delivery.organizationId ?? null,\n notification_id: delivery.notificationId ?? null,\n notification_type_id: delivery.notificationTypeId,\n user_device_id: delivery.userDeviceId,\n user_id: delivery.userId,\n provider: delivery.provider,\n token_snapshot: delivery.tokenSnapshot,\n status: delivery.status,\n attempts: delivery.attempts,\n last_error: delivery.lastError ?? null,\n payload: delivery.payload ?? null,\n provider_response: delivery.providerResponse ?? null,\n created_at: delivery.createdAt ? delivery.createdAt.toISOString() : null,\n sent_at: delivery.sentAt ? delivery.sentAt.toISOString() : null,\n next_retry_at: delivery.nextRetryAt ? delivery.nextRetryAt.toISOString() : null,\n updated_at: delivery.updatedAt ? delivery.updatedAt.toISOString() : null,\n }\n}\n\nexport async function GET(req: Request, { params }: { params: { id: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: translate('push_notifications.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n const parsedParams = paramsSchema.safeParse({ id: params.id })\n if (!parsedParams.success) {\n return NextResponse.json({ error: translate('push_notifications.errors.invalid_id', 'Invalid delivery id') }, { status: 400 })\n }\n const em = container.resolve('em') as EntityManager\n const delivery = await em.findOne(PushNotificationDelivery, { id: parsedParams.data.id, tenantId: auth.tenantId })\n if (!delivery) {\n return NextResponse.json({ error: translate('push_notifications.errors.not_found', 'Delivery not found') }, { status: 404 })\n }\n // Standard org read scoping (matches the list route + devices): an unrestricted admin reads any\n // row in the tenant, including tenant-level (NULL-org) ones; an org-restricted admin may read only\n // rows in an allowed org \u2014 a NULL-org target is denied (fail-closed).\n if (\n !isOrganizationReadAccessAllowed({\n scope: await resolveOrganizationScopeForRequest({ container, auth, request: req }),\n auth,\n organizationId: delivery.organizationId ?? null,\n })\n ) {\n return NextResponse.json({ error: translate('push_notifications.errors.forbidden', 'Access denied') }, { status: 403 })\n }\n const item = serializeDelivery(delivery)\n // Audit the read: this hand-rolled detail route bypasses makeCrudRoute (which logs list reads\n // for free), and it exposes per-user delivery detail (user/device ids, payload). Mirrors the\n // other hand-rolled core read routes (directory tenants/orgs, auth roles/users, feature_toggles).\n await logCrudAccess({\n container,\n auth,\n request: req,\n items: [item],\n idField: 'id',\n resourceKind: 'push_notifications.push_notification_delivery',\n organizationId: delivery.organizationId ?? null,\n tenantId: auth.tenantId ?? null,\n accessType: 'read:item',\n })\n return NextResponse.json({ item })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('push_notifications.deliveries.GET failed', { err })\n return NextResponse.json({ error: translate('push_notifications.errors.server_error', 'Something went wrong') }, { status: 500 })\n }\n}\n\nconst errorResponseSchema = z.object({ error: z.string() })\nconst detailResponseSchema = z.object({ item: deliveryDetailItemSchema })\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'PushNotifications',\n summary: 'Read a single push delivery log row',\n pathParams: paramsSchema,\n methods: {\n GET: {\n summary: 'Get a push delivery',\n description: 'Admin: fetch a single push delivery row (payload + provider response; no full push token).',\n responses: [{ status: 200, description: 'Push delivery', schema: detailResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid id', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 403, description: 'Missing push_notifications.view_deliveries', schema: errorResponseSchema },\n { status: 404, description: 'Delivery not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
|
|
2
|
+
import { E } from "../../../../generated/entities.ids.generated.js";
|
|
3
|
+
import { PushNotificationDelivery } from "../../data/entities.js";
|
|
4
|
+
import {
|
|
5
|
+
deliveryListSchema,
|
|
6
|
+
deliveryListFields,
|
|
7
|
+
deliveryListSortFieldMap,
|
|
8
|
+
deliveryListItemSchema
|
|
9
|
+
} from "../../data/validators.js";
|
|
10
|
+
import { createPushNotificationsCrudOpenApi, createPagedListResponseSchema } from "../openapi.js";
|
|
11
|
+
const routeMetadata = {
|
|
12
|
+
GET: { requireAuth: true, requireFeatures: ["push_notifications.view_deliveries"] }
|
|
13
|
+
};
|
|
14
|
+
const metadata = routeMetadata;
|
|
15
|
+
const crud = makeCrudRoute({
|
|
16
|
+
metadata: routeMetadata,
|
|
17
|
+
orm: {
|
|
18
|
+
entity: PushNotificationDelivery,
|
|
19
|
+
idField: "id",
|
|
20
|
+
// organization_id is nullable; the CRUD factory + query engine apply standard org scoping, exactly
|
|
21
|
+
// like devices and every other module. Unrestricted admins see every row in the tenant (including
|
|
22
|
+
// tenant-level NULL-org rows); org-restricted admins see only rows in an allowed org.
|
|
23
|
+
orgField: "organizationId",
|
|
24
|
+
tenantField: "tenantId"
|
|
25
|
+
},
|
|
26
|
+
indexer: { entityType: E.push_notifications.push_notification_delivery },
|
|
27
|
+
list: {
|
|
28
|
+
schema: deliveryListSchema,
|
|
29
|
+
entityId: E.push_notifications.push_notification_delivery,
|
|
30
|
+
fields: deliveryListFields,
|
|
31
|
+
sortFieldMap: deliveryListSortFieldMap,
|
|
32
|
+
buildFilters: async (query) => {
|
|
33
|
+
const filters = {};
|
|
34
|
+
if (query.status) filters.status = { $eq: query.status };
|
|
35
|
+
if (query.userId) filters.user_id = { $eq: query.userId };
|
|
36
|
+
const createdAt = {};
|
|
37
|
+
if (query.from) createdAt.$gte = query.from;
|
|
38
|
+
if (query.to) createdAt.$lte = query.to;
|
|
39
|
+
if (Object.keys(createdAt).length > 0) filters.created_at = createdAt;
|
|
40
|
+
return filters;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
const GET = crud.GET;
|
|
45
|
+
const openApi = createPushNotificationsCrudOpenApi({
|
|
46
|
+
resourceName: "Push delivery",
|
|
47
|
+
pluralName: "Push deliveries",
|
|
48
|
+
querySchema: deliveryListSchema,
|
|
49
|
+
listResponseSchema: createPagedListResponseSchema(deliveryListItemSchema)
|
|
50
|
+
});
|
|
51
|
+
export {
|
|
52
|
+
GET,
|
|
53
|
+
metadata,
|
|
54
|
+
openApi
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/push_notifications/api/deliveries/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { E } from '#generated/entities.ids.generated'\nimport { PushNotificationDelivery } from '../../data/entities'\nimport {\n deliveryListSchema,\n deliveryListFields,\n deliveryListSortFieldMap,\n deliveryListItemSchema,\n} from '../../data/validators'\nimport { createPushNotificationsCrudOpenApi, createPagedListResponseSchema } from '../openapi'\n\n// Read-only push delivery log (admin observability). No writes: device/token lifecycle lives in the\n// `devices` module; delivery rows are produced by the `push` strategy + send-push worker.\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['push_notifications.view_deliveries'] },\n}\n\nexport const metadata = routeMetadata\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: PushNotificationDelivery,\n idField: 'id',\n // organization_id is nullable; the CRUD factory + query engine apply standard org scoping, exactly\n // like devices and every other module. Unrestricted admins see every row in the tenant (including\n // tenant-level NULL-org rows); org-restricted admins see only rows in an allowed org.\n orgField: 'organizationId',\n tenantField: 'tenantId',\n },\n indexer: { entityType: E.push_notifications.push_notification_delivery },\n list: {\n schema: deliveryListSchema,\n entityId: E.push_notifications.push_notification_delivery,\n fields: deliveryListFields,\n sortFieldMap: deliveryListSortFieldMap,\n buildFilters: async (query) => {\n const filters: Record<string, unknown> = {}\n if (query.status) filters.status = { $eq: query.status }\n if (query.userId) filters.user_id = { $eq: query.userId }\n const createdAt: Record<string, unknown> = {}\n if (query.from) createdAt.$gte = query.from\n if (query.to) createdAt.$lte = query.to\n if (Object.keys(createdAt).length > 0) filters.created_at = createdAt\n return filters\n },\n },\n})\n\nexport const GET = crud.GET\n\nexport const openApi: OpenApiRouteDoc = createPushNotificationsCrudOpenApi({\n resourceName: 'Push delivery',\n pluralName: 'Push deliveries',\n querySchema: deliveryListSchema,\n listResponseSchema: createPagedListResponseSchema(deliveryListItemSchema),\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,qBAAqB;AAE9B,SAAS,SAAS;AAClB,SAAS,gCAAgC;AACzC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oCAAoC,qCAAqC;AAIlF,MAAM,gBAAgB;AAAA,EACpB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,oCAAoC,EAAE;AACpF;AAEO,MAAM,WAAW;AAExB,MAAM,OAAO,cAAc;AAAA,EACzB,UAAU;AAAA,EACV,KAAK;AAAA,IACH,QAAQ;AAAA,IACR,SAAS;AAAA;AAAA;AAAA;AAAA,IAIT,UAAU;AAAA,IACV,aAAa;AAAA,EACf;AAAA,EACA,SAAS,EAAE,YAAY,EAAE,mBAAmB,2BAA2B;AAAA,EACvE,MAAM;AAAA,IACJ,QAAQ;AAAA,IACR,UAAU,EAAE,mBAAmB;AAAA,IAC/B,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,cAAc,OAAO,UAAU;AAC7B,YAAM,UAAmC,CAAC;AAC1C,UAAI,MAAM,OAAQ,SAAQ,SAAS,EAAE,KAAK,MAAM,OAAO;AACvD,UAAI,MAAM,OAAQ,SAAQ,UAAU,EAAE,KAAK,MAAM,OAAO;AACxD,YAAM,YAAqC,CAAC;AAC5C,UAAI,MAAM,KAAM,WAAU,OAAO,MAAM;AACvC,UAAI,MAAM,GAAI,WAAU,OAAO,MAAM;AACrC,UAAI,OAAO,KAAK,SAAS,EAAE,SAAS,EAAG,SAAQ,aAAa;AAC5D,aAAO;AAAA,IACT;AAAA,EACF;AACF,CAAC;AAEM,MAAM,MAAM,KAAK;AAEjB,MAAM,UAA2B,mCAAmC;AAAA,EACzE,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,oBAAoB,8BAA8B,sBAAsB;AAC1E,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createCrudOpenApiFactory,
|
|
3
|
+
createPagedListResponseSchema as createSharedPagedListResponseSchema
|
|
4
|
+
} from "@open-mercato/shared/lib/openapi/crud";
|
|
5
|
+
function createPagedListResponseSchema(itemSchema) {
|
|
6
|
+
return createSharedPagedListResponseSchema(itemSchema, { paginationMetaOptional: true });
|
|
7
|
+
}
|
|
8
|
+
const buildPushNotificationsCrudOpenApi = createCrudOpenApiFactory({
|
|
9
|
+
defaultTag: "PushNotifications",
|
|
10
|
+
makeListDescription: ({ pluralLower }) => `Returns the tenant's push ${pluralLower} (admin observability).`
|
|
11
|
+
});
|
|
12
|
+
function createPushNotificationsCrudOpenApi(options) {
|
|
13
|
+
return buildPushNotificationsCrudOpenApi(options);
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
createPagedListResponseSchema,
|
|
17
|
+
createPushNotificationsCrudOpenApi
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=openapi.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/push_notifications/api/openapi.ts"],
|
|
4
|
+
"sourcesContent": ["import { type ZodTypeAny } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n createCrudOpenApiFactory,\n createPagedListResponseSchema as createSharedPagedListResponseSchema,\n type CrudOpenApiOptions,\n} from '@open-mercato/shared/lib/openapi/crud'\n\nexport function createPagedListResponseSchema(itemSchema: ZodTypeAny) {\n return createSharedPagedListResponseSchema(itemSchema, { paginationMetaOptional: true })\n}\n\n// Read-only delivery log: only the list operation is documented, so the factory's shared\n// create/ok response-schema fallbacks are left untouched (no need to pass or re-export them).\nconst buildPushNotificationsCrudOpenApi = createCrudOpenApiFactory({\n defaultTag: 'PushNotifications',\n makeListDescription: ({ pluralLower }) => `Returns the tenant's push ${pluralLower} (admin observability).`,\n})\n\nexport function createPushNotificationsCrudOpenApi(options: CrudOpenApiOptions): OpenApiRouteDoc {\n return buildPushNotificationsCrudOpenApi(options)\n}\n"],
|
|
5
|
+
"mappings": "AAEA;AAAA,EACE;AAAA,EACA,iCAAiC;AAAA,OAE5B;AAEA,SAAS,8BAA8B,YAAwB;AACpE,SAAO,oCAAoC,YAAY,EAAE,wBAAwB,KAAK,CAAC;AACzF;AAIA,MAAM,oCAAoC,yBAAyB;AAAA,EACjE,YAAY;AAAA,EACZ,qBAAqB,CAAC,EAAE,YAAY,MAAM,6BAA6B,WAAW;AACpF,CAAC;AAEM,SAAS,mCAAmC,SAA8C;AAC/F,SAAO,kCAAkC,OAAO;AAClD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|