@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
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { useRouter } from "next/navigation";
|
|
6
|
+
import { Plus, X } from "lucide-react";
|
|
7
|
+
import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
8
|
+
import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
|
|
9
|
+
import { apiCall, apiCallOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
|
|
10
|
+
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
11
|
+
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@open-mercato/ui/primitives/select";
|
|
12
|
+
import { Input } from "@open-mercato/ui/primitives/input";
|
|
13
|
+
import { IconButton } from "@open-mercato/ui/primitives/icon-button";
|
|
14
|
+
import { Button } from "@open-mercato/ui/primitives/button";
|
|
15
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
16
|
+
const DELIVERIES_HREF = "/backend/push_notifications";
|
|
17
|
+
const FORM_ID = "push-custom-send-form";
|
|
18
|
+
const ALL_DEVICES = "__all__";
|
|
19
|
+
function DeviceField({ value, setValue, setFormValue, values, onState }) {
|
|
20
|
+
const t = useT();
|
|
21
|
+
const userId = typeof values?.userId === "string" ? values.userId : "";
|
|
22
|
+
const mode = typeof values?.mode === "string" ? values.mode : "visible";
|
|
23
|
+
const selected = typeof value === "string" && value ? value : "";
|
|
24
|
+
const [devices, setDevices] = React.useState([]);
|
|
25
|
+
const [loading, setLoading] = React.useState(false);
|
|
26
|
+
React.useEffect(() => {
|
|
27
|
+
onState?.({ userId, count: devices.length, loading });
|
|
28
|
+
}, [userId, devices.length, loading, onState]);
|
|
29
|
+
React.useEffect(() => {
|
|
30
|
+
if (!userId) {
|
|
31
|
+
setDevices([]);
|
|
32
|
+
if (selected) setValue("");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
let cancelled = false;
|
|
36
|
+
setLoading(true);
|
|
37
|
+
const params = new URLSearchParams({ userId, pageSize: "50" });
|
|
38
|
+
apiCall(
|
|
39
|
+
`/api/devices/admin/devices?${params.toString()}`,
|
|
40
|
+
{ headers: { "x-om-forbidden-redirect": "0" } },
|
|
41
|
+
{ fallback: null }
|
|
42
|
+
).catch(() => null).then((call) => {
|
|
43
|
+
if (cancelled) return;
|
|
44
|
+
const items = (call && call.ok ? call.result?.items : []) ?? [];
|
|
45
|
+
const opts = items.filter((d) => !!d && typeof d.id === "string" && !!d.push_provider).map((d) => ({ id: d.id, label: `${d.device_id} \xB7 ${d.platform}${d.push_provider ? ` \xB7 ${d.push_provider}` : ""}`, platform: d.platform }));
|
|
46
|
+
setDevices(opts);
|
|
47
|
+
if (selected && !opts.some((o) => o.id === selected)) setValue("");
|
|
48
|
+
}).finally(() => {
|
|
49
|
+
if (!cancelled) setLoading(false);
|
|
50
|
+
});
|
|
51
|
+
return () => {
|
|
52
|
+
cancelled = true;
|
|
53
|
+
};
|
|
54
|
+
}, [userId]);
|
|
55
|
+
const selectedPlatform = React.useMemo(() => devices.find((d) => d.id === selected)?.platform, [devices, selected]);
|
|
56
|
+
React.useEffect(() => {
|
|
57
|
+
const show = mode !== "silent" && selectedPlatform !== "ios";
|
|
58
|
+
if (values?.showChannelId !== show) setFormValue?.("showChannelId", show);
|
|
59
|
+
}, [mode, selectedPlatform, values?.showChannelId, setFormValue]);
|
|
60
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
61
|
+
/* @__PURE__ */ jsxs(Select, { value: selected || ALL_DEVICES, onValueChange: (v) => setValue(v === ALL_DEVICES ? "" : v), disabled: !userId, children: [
|
|
62
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: `${t("push_notifications.send.deviceAll")} (${devices.length})` }) }),
|
|
63
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
64
|
+
/* @__PURE__ */ jsx(SelectItem, { value: ALL_DEVICES, children: `${t("push_notifications.send.deviceAll")} (${devices.length})` }),
|
|
65
|
+
devices.map((d) => /* @__PURE__ */ jsx(SelectItem, { value: d.id, children: d.label }, d.id))
|
|
66
|
+
] })
|
|
67
|
+
] }),
|
|
68
|
+
/* @__PURE__ */ jsx("p", { className: `text-xs ${userId && !loading && devices.length === 0 ? "text-status-warning-text" : "text-muted-foreground"}`, children: !userId ? t("push_notifications.send.deviceSelectUserFirst") : loading ? t("common.loading", "Loading\u2026") : devices.length === 0 ? t("push_notifications.send.deviceNone") : t("push_notifications.send.deviceHint") })
|
|
69
|
+
] });
|
|
70
|
+
}
|
|
71
|
+
function DataField({ value, setValue }) {
|
|
72
|
+
const t = useT();
|
|
73
|
+
const pairs = Array.isArray(value) ? value : [];
|
|
74
|
+
const update = (next) => setValue(next);
|
|
75
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
76
|
+
pairs.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("push_notifications.send.dataEmpty") }) : null,
|
|
77
|
+
pairs.map((pair, index) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
78
|
+
/* @__PURE__ */ jsx(
|
|
79
|
+
Input,
|
|
80
|
+
{
|
|
81
|
+
placeholder: t("push_notifications.send.dataKey"),
|
|
82
|
+
value: pair.key,
|
|
83
|
+
onChange: (e) => update(pairs.map((p, i) => i === index ? { ...p, key: e.target.value } : p))
|
|
84
|
+
}
|
|
85
|
+
),
|
|
86
|
+
/* @__PURE__ */ jsx(
|
|
87
|
+
Input,
|
|
88
|
+
{
|
|
89
|
+
placeholder: t("push_notifications.send.dataValue"),
|
|
90
|
+
value: pair.value,
|
|
91
|
+
onChange: (e) => update(pairs.map((p, i) => i === index ? { ...p, value: e.target.value } : p))
|
|
92
|
+
}
|
|
93
|
+
),
|
|
94
|
+
/* @__PURE__ */ jsx(
|
|
95
|
+
IconButton,
|
|
96
|
+
{
|
|
97
|
+
type: "button",
|
|
98
|
+
variant: "ghost",
|
|
99
|
+
"aria-label": t("push_notifications.send.dataRemove"),
|
|
100
|
+
onClick: () => update(pairs.filter((_, i) => i !== index)),
|
|
101
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
] }, index)),
|
|
105
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => update([...pairs, { key: "", value: "" }]), children: [
|
|
106
|
+
/* @__PURE__ */ jsx(Plus, { className: "h-4 w-4 mr-1" }),
|
|
107
|
+
t("push_notifications.send.dataAdd")
|
|
108
|
+
] })
|
|
109
|
+
] });
|
|
110
|
+
}
|
|
111
|
+
function PushCustomSendPage() {
|
|
112
|
+
const router = useRouter();
|
|
113
|
+
const t = useT();
|
|
114
|
+
const [deviceState, setDeviceState] = React.useState({ userId: "", count: 0, loading: false });
|
|
115
|
+
const [submitting, setSubmitting] = React.useState(false);
|
|
116
|
+
const reportDeviceState = React.useCallback((next) => {
|
|
117
|
+
setDeviceState((prev) => prev.userId === next.userId && prev.count === next.count && prev.loading === next.loading ? prev : next);
|
|
118
|
+
}, []);
|
|
119
|
+
const loadUserOptions = React.useCallback(async (query) => {
|
|
120
|
+
const params = new URLSearchParams();
|
|
121
|
+
params.set("page", "1");
|
|
122
|
+
params.set("pageSize", "20");
|
|
123
|
+
if (query && query.trim().length > 0) params.set("search", query.trim());
|
|
124
|
+
const call = await apiCall(
|
|
125
|
+
`/api/auth/users?${params.toString()}`,
|
|
126
|
+
{ headers: { "x-om-forbidden-redirect": "0" } },
|
|
127
|
+
{ fallback: null }
|
|
128
|
+
).catch(() => null);
|
|
129
|
+
if (!call || !call.ok) return [];
|
|
130
|
+
return (call.result?.items ?? []).flatMap((item) => {
|
|
131
|
+
if (!item || typeof item.id !== "string" || !item.id.trim()) return [];
|
|
132
|
+
const name = typeof item.name === "string" && item.name.trim() ? item.name.trim() : null;
|
|
133
|
+
const email = typeof item.email === "string" && item.email.trim() ? item.email.trim() : null;
|
|
134
|
+
const label = name && email ? `${name} \u2014 ${email}` : email ?? name ?? item.id;
|
|
135
|
+
return [{ value: item.id, label }];
|
|
136
|
+
});
|
|
137
|
+
}, []);
|
|
138
|
+
const visibleOnly = { field: "mode", equals: "visible" };
|
|
139
|
+
const fields = React.useMemo(() => [
|
|
140
|
+
{ id: "userId", label: t("push_notifications.send.userId"), type: "combobox", required: true, description: t("push_notifications.send.userIdHint"), loadOptions: loadUserOptions },
|
|
141
|
+
{ id: "deviceId", label: t("push_notifications.send.device"), type: "custom", component: (props) => /* @__PURE__ */ jsx(DeviceField, { ...props, onState: reportDeviceState }) },
|
|
142
|
+
{
|
|
143
|
+
id: "mode",
|
|
144
|
+
label: t("push_notifications.send.mode"),
|
|
145
|
+
type: "select",
|
|
146
|
+
required: true,
|
|
147
|
+
description: t("push_notifications.send.modeHint"),
|
|
148
|
+
options: [
|
|
149
|
+
{ value: "visible", label: t("push_notifications.send.modeVisible") },
|
|
150
|
+
{ value: "silent", label: t("push_notifications.send.modeSilent") }
|
|
151
|
+
]
|
|
152
|
+
},
|
|
153
|
+
{ id: "title", label: t("push_notifications.send.title"), type: "text", required: true, visibleWhen: visibleOnly },
|
|
154
|
+
{ id: "body", label: t("push_notifications.send.body"), type: "textarea", visibleWhen: visibleOnly },
|
|
155
|
+
{ id: "data", label: t("push_notifications.send.data"), type: "custom", description: t("push_notifications.send.dataHint"), component: (props) => /* @__PURE__ */ jsx(DataField, { ...props }) },
|
|
156
|
+
{ id: "sound", label: t("push_notifications.send.sound"), type: "text", visibleWhen: visibleOnly },
|
|
157
|
+
{ id: "badge", label: t("push_notifications.send.badge"), type: "number", visibleWhen: visibleOnly },
|
|
158
|
+
{ id: "image", label: t("push_notifications.send.image"), type: "text", visibleWhen: visibleOnly },
|
|
159
|
+
{
|
|
160
|
+
id: "priority",
|
|
161
|
+
label: t("push_notifications.send.priority"),
|
|
162
|
+
type: "select",
|
|
163
|
+
options: [
|
|
164
|
+
{ value: "", label: t("push_notifications.send.priorityDefault") },
|
|
165
|
+
{ value: "high", label: t("push_notifications.send.priorityHigh") },
|
|
166
|
+
{ value: "normal", label: t("push_notifications.send.priorityNormal") }
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
{ id: "channelId", label: t("push_notifications.send.channelId"), type: "text", visibleWhen: { field: "showChannelId", equals: true } }
|
|
170
|
+
], [t, loadUserOptions, reportDeviceState]);
|
|
171
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsxs(PageBody, { children: [
|
|
172
|
+
/* @__PURE__ */ jsx(
|
|
173
|
+
CrudForm,
|
|
174
|
+
{
|
|
175
|
+
title: t("push_notifications.send.pageTitle"),
|
|
176
|
+
backHref: DELIVERIES_HREF,
|
|
177
|
+
formId: FORM_ID,
|
|
178
|
+
hideFooterActions: true,
|
|
179
|
+
fields,
|
|
180
|
+
initialValues: { userId: "", deviceId: "", mode: "visible", title: "", body: "", data: [], sound: "", badge: "", image: "", priority: "", channelId: "", showChannelId: true },
|
|
181
|
+
submitLabel: t("push_notifications.send.submit"),
|
|
182
|
+
onSubmit: async (values) => {
|
|
183
|
+
setSubmitting(true);
|
|
184
|
+
try {
|
|
185
|
+
const silent = values.mode === "silent";
|
|
186
|
+
const body = (values.body ?? "").trim();
|
|
187
|
+
const dataMap = {};
|
|
188
|
+
for (const pair of Array.isArray(values.data) ? values.data : []) {
|
|
189
|
+
const key = (pair?.key ?? "").trim();
|
|
190
|
+
if (key) dataMap[key] = `${pair?.value ?? ""}`;
|
|
191
|
+
}
|
|
192
|
+
const hasData = Object.keys(dataMap).length > 0;
|
|
193
|
+
const pushOptions = {};
|
|
194
|
+
if (!silent) {
|
|
195
|
+
if (typeof values.sound === "string" && values.sound.trim()) pushOptions.sound = values.sound.trim();
|
|
196
|
+
const badgeNum = typeof values.badge === "number" ? values.badge : Number.parseInt(String(values.badge ?? ""), 10);
|
|
197
|
+
if (Number.isFinite(badgeNum) && badgeNum >= 0) pushOptions.badge = badgeNum;
|
|
198
|
+
if (typeof values.image === "string" && values.image.trim()) pushOptions.image = values.image.trim();
|
|
199
|
+
if (typeof values.channelId === "string" && values.channelId.trim()) pushOptions.channelId = values.channelId.trim();
|
|
200
|
+
}
|
|
201
|
+
if (values.priority === "high" || values.priority === "normal") pushOptions.priority = values.priority;
|
|
202
|
+
const hasOptions = Object.keys(pushOptions).length > 0;
|
|
203
|
+
const title = silent ? (values.title ?? "").trim() || "Silent push" : (values.title ?? "").trim();
|
|
204
|
+
await apiCallOrThrow("/api/push_notifications/custom-send", {
|
|
205
|
+
method: "POST",
|
|
206
|
+
headers: { "content-type": "application/json" },
|
|
207
|
+
body: JSON.stringify({
|
|
208
|
+
recipientUserId: (values.userId ?? "").trim(),
|
|
209
|
+
deviceId: (values.deviceId ?? "").trim() || void 0,
|
|
210
|
+
title,
|
|
211
|
+
body: !silent && body.length > 0 ? body : void 0,
|
|
212
|
+
silent,
|
|
213
|
+
data: hasData ? dataMap : void 0,
|
|
214
|
+
pushOptions: hasOptions ? pushOptions : void 0
|
|
215
|
+
})
|
|
216
|
+
});
|
|
217
|
+
flash(t("push_notifications.send.success"), "success");
|
|
218
|
+
router.push(DELIVERIES_HREF);
|
|
219
|
+
} finally {
|
|
220
|
+
setSubmitting(false);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
),
|
|
225
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center justify-between gap-3", children: [
|
|
226
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs", children: !deviceState.userId ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("push_notifications.send.deviceSelectUserFirst") }) : deviceState.count === 0 && !deviceState.loading ? /* @__PURE__ */ jsx("span", { className: "text-status-warning-text", children: t("push_notifications.send.deviceNone") }) : null }),
|
|
227
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
228
|
+
/* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", children: /* @__PURE__ */ jsx(Link, { href: DELIVERIES_HREF, children: t("common.cancel", "Cancel") }) }),
|
|
229
|
+
/* @__PURE__ */ jsx(
|
|
230
|
+
Button,
|
|
231
|
+
{
|
|
232
|
+
type: "submit",
|
|
233
|
+
form: FORM_ID,
|
|
234
|
+
disabled: !deviceState.userId || deviceState.count === 0 || deviceState.loading || submitting,
|
|
235
|
+
children: t("push_notifications.send.submit")
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
] })
|
|
239
|
+
] })
|
|
240
|
+
] }) });
|
|
241
|
+
}
|
|
242
|
+
export {
|
|
243
|
+
PushCustomSendPage as default
|
|
244
|
+
};
|
|
245
|
+
//# sourceMappingURL=page.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/push_notifications/backend/push_notifications/send/page.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { Plus, X } from 'lucide-react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFieldOption, type CrudCustomFieldRenderProps } from '@open-mercato/ui/backend/CrudForm'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@open-mercato/ui/primitives/select'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype KeyValue = { key: string; value: string }\n\ntype FormValues = {\n userId: string\n deviceId: string\n mode: string\n title: string\n body: string\n data: KeyValue[]\n sound: string\n badge: number | string\n image: string\n priority: string\n channelId: string\n // form-only helper (never sent): drives channelId visibility (hidden in silent / for an iOS target)\n showChannelId: boolean\n}\n\nconst DELIVERIES_HREF = '/backend/push_notifications'\nconst FORM_ID = 'push-custom-send-form'\n// Radix Select forbids an empty-string item value, so \"all devices\" uses a sentinel that we map\n// back to '' (the form value that means \"fan out to every push-capable device\").\nconst ALL_DEVICES = '__all__'\n\ntype DeviceOption = { id: string; label: string; platform: string }\n// Reported up to the page so the (custom) submit button can disable when there is nothing to send to.\ntype DeviceState = { userId: string; count: number; loading: boolean }\n\n// Dependent picker: loads the *selected recipient's* push-capable devices (admin devices API,\n// filtered by userId) so an admin can target one device or all of them. Reads the live form values,\n// so it re-loads whenever the recipient changes and clears a stale selection. It also derives\n// `showChannelId` (Android channel id is Android-only + visible-only) so the channelId field can hide\n// itself for a selected iOS device or in silent mode. Degrades to \"All devices\" when the admin lacks\n// devices.admin or the recipient has no push-capable device.\nfunction DeviceField({ value, setValue, setFormValue, values, onState }: CrudCustomFieldRenderProps & { onState?: (s: DeviceState) => void }) {\n const t = useT()\n const userId = typeof values?.userId === 'string' ? values.userId : ''\n const mode = typeof values?.mode === 'string' ? values.mode : 'visible'\n const selected = typeof value === 'string' && value ? value : ''\n const [devices, setDevices] = React.useState<DeviceOption[]>([])\n const [loading, setLoading] = React.useState(false)\n\n // Report the recipient + push-capable device count up so the page can disable \"Send\" when there is\n // nowhere to deliver (no recipient, or the recipient has zero push-capable devices).\n React.useEffect(() => {\n onState?.({ userId, count: devices.length, loading })\n }, [userId, devices.length, loading, onState])\n\n React.useEffect(() => {\n if (!userId) {\n setDevices([])\n if (selected) setValue('')\n return\n }\n let cancelled = false\n setLoading(true)\n const params = new URLSearchParams({ userId, pageSize: '50' })\n apiCall<{ items?: Array<{ id: string; device_id: string; platform: string; push_provider?: string | null }> }>(\n `/api/devices/admin/devices?${params.toString()}`,\n { headers: { 'x-om-forbidden-redirect': '0' } },\n { fallback: null },\n )\n .catch(() => null)\n .then((call) => {\n if (cancelled) return\n const items = (call && call.ok ? call.result?.items : []) ?? []\n const opts = items\n .filter((d): d is { id: string; device_id: string; platform: string; push_provider?: string | null } =>\n !!d && typeof d.id === 'string' && !!d.push_provider)\n .map((d) => ({ id: d.id, label: `${d.device_id} \u00B7 ${d.platform}${d.push_provider ? ` \u00B7 ${d.push_provider}` : ''}`, platform: d.platform }))\n setDevices(opts)\n if (selected && !opts.some((o) => o.id === selected)) setValue('')\n })\n .finally(() => { if (!cancelled) setLoading(false) })\n return () => { cancelled = true }\n // Reload only when the recipient changes; `selected`/`setValue` are handled inside.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [userId])\n\n // channelId is Android-only and irrelevant to silent pushes \u2192 hide it for a selected iOS device or\n // in silent mode. Only write when the derived value actually changes (avoids a render loop).\n const selectedPlatform = React.useMemo(() => devices.find((d) => d.id === selected)?.platform, [devices, selected])\n React.useEffect(() => {\n const show = mode !== 'silent' && selectedPlatform !== 'ios'\n if (values?.showChannelId !== show) setFormValue?.('showChannelId', show)\n }, [mode, selectedPlatform, values?.showChannelId, setFormValue])\n\n return (\n <div className=\"space-y-1\">\n <Select value={selected || ALL_DEVICES} onValueChange={(v) => setValue(v === ALL_DEVICES ? '' : v)} disabled={!userId}>\n <SelectTrigger>\n <SelectValue placeholder={`${t('push_notifications.send.deviceAll')} (${devices.length})`} />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL_DEVICES}>{`${t('push_notifications.send.deviceAll')} (${devices.length})`}</SelectItem>\n {devices.map((d) => (\n <SelectItem key={d.id} value={d.id}>{d.label}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className={`text-xs ${userId && !loading && devices.length === 0 ? 'text-status-warning-text' : 'text-muted-foreground'}`}>\n {!userId\n ? t('push_notifications.send.deviceSelectUserFirst')\n : loading\n ? t('common.loading', 'Loading\u2026')\n : devices.length === 0\n ? t('push_notifications.send.deviceNone')\n : t('push_notifications.send.deviceHint')}\n </p>\n </div>\n )\n}\n\n// Arbitrary key/value payload delivered inside the push `data` map (the app reads it). Shown for both\n// visible and silent sends \u2014 for silent it is the entire payload.\nfunction DataField({ value, setValue }: CrudCustomFieldRenderProps) {\n const t = useT()\n const pairs: KeyValue[] = Array.isArray(value) ? (value as KeyValue[]) : []\n const update = (next: KeyValue[]) => setValue(next)\n return (\n <div className=\"space-y-2\">\n {pairs.length === 0 ? (\n <p className=\"text-xs text-muted-foreground\">{t('push_notifications.send.dataEmpty')}</p>\n ) : null}\n {pairs.map((pair, index) => (\n <div key={index} className=\"flex items-center gap-2\">\n <Input\n placeholder={t('push_notifications.send.dataKey')}\n value={pair.key}\n onChange={(e) => update(pairs.map((p, i) => (i === index ? { ...p, key: e.target.value } : p)))}\n />\n <Input\n placeholder={t('push_notifications.send.dataValue')}\n value={pair.value}\n onChange={(e) => update(pairs.map((p, i) => (i === index ? { ...p, value: e.target.value } : p)))}\n />\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n aria-label={t('push_notifications.send.dataRemove')}\n onClick={() => update(pairs.filter((_, i) => i !== index))}\n >\n <X className=\"h-4 w-4\" />\n </IconButton>\n </div>\n ))}\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => update([...pairs, { key: '', value: '' }])}>\n <Plus className=\"h-4 w-4 mr-1\" />\n {t('push_notifications.send.dataAdd')}\n </Button>\n </div>\n )\n}\n\nexport default function PushCustomSendPage() {\n const router = useRouter()\n const t = useT()\n const [deviceState, setDeviceState] = React.useState<DeviceState>({ userId: '', count: 0, loading: false })\n const [submitting, setSubmitting] = React.useState(false)\n\n // Stable reporter for the device picker \u2192 bails when unchanged so it can't loop the render.\n const reportDeviceState = React.useCallback((next: DeviceState) => {\n setDeviceState((prev) =>\n prev.userId === next.userId && prev.count === next.count && prev.loading === next.loading ? prev : next)\n }, [])\n\n // Recipient search by name/email via /api/auth/users (mirrors the devices list + admin\n // notification-preferences picker). Return results only \u2014 the combobox retains them internally, so\n // pushing them into state that `fields` depends on would re-render the whole form per keystroke.\n const loadUserOptions = React.useCallback(async (query?: string): Promise<CrudFieldOption[]> => {\n const params = new URLSearchParams()\n params.set('page', '1')\n params.set('pageSize', '20')\n if (query && query.trim().length > 0) params.set('search', query.trim())\n const call = await apiCall<{ items?: { id: string; name?: string | null; email?: string | null }[] }>(\n `/api/auth/users?${params.toString()}`,\n { headers: { 'x-om-forbidden-redirect': '0' } },\n { fallback: null },\n ).catch(() => null)\n if (!call || !call.ok) return []\n return (call.result?.items ?? []).flatMap((item): CrudFieldOption[] => {\n if (!item || typeof item.id !== 'string' || !item.id.trim()) return []\n const name = typeof item.name === 'string' && item.name.trim() ? item.name.trim() : null\n const email = typeof item.email === 'string' && item.email.trim() ? item.email.trim() : null\n const label = name && email ? `${name} \u2014 ${email}` : email ?? name ?? item.id\n return [{ value: item.id, label }]\n })\n }, [])\n\n const visibleOnly = { field: 'mode', equals: 'visible' } as const\n\n const fields = React.useMemo<CrudField[]>(() => [\n { id: 'userId', label: t('push_notifications.send.userId'), type: 'combobox', required: true, description: t('push_notifications.send.userIdHint'), loadOptions: loadUserOptions },\n { id: 'deviceId', label: t('push_notifications.send.device'), type: 'custom', component: (props) => <DeviceField {...props} onState={reportDeviceState} /> },\n {\n id: 'mode',\n label: t('push_notifications.send.mode'),\n type: 'select',\n required: true,\n description: t('push_notifications.send.modeHint'),\n options: [\n { value: 'visible', label: t('push_notifications.send.modeVisible') },\n { value: 'silent', label: t('push_notifications.send.modeSilent') },\n ],\n },\n { id: 'title', label: t('push_notifications.send.title'), type: 'text', required: true, visibleWhen: visibleOnly },\n { id: 'body', label: t('push_notifications.send.body'), type: 'textarea', visibleWhen: visibleOnly },\n { id: 'data', label: t('push_notifications.send.data'), type: 'custom', description: t('push_notifications.send.dataHint'), component: (props) => <DataField {...props} /> },\n { id: 'sound', label: t('push_notifications.send.sound'), type: 'text', visibleWhen: visibleOnly },\n { id: 'badge', label: t('push_notifications.send.badge'), type: 'number', visibleWhen: visibleOnly },\n { id: 'image', label: t('push_notifications.send.image'), type: 'text', visibleWhen: visibleOnly },\n {\n id: 'priority',\n label: t('push_notifications.send.priority'),\n type: 'select',\n options: [\n { value: '', label: t('push_notifications.send.priorityDefault') },\n { value: 'high', label: t('push_notifications.send.priorityHigh') },\n { value: 'normal', label: t('push_notifications.send.priorityNormal') },\n ],\n },\n { id: 'channelId', label: t('push_notifications.send.channelId'), type: 'text', visibleWhen: { field: 'showChannelId', equals: true } },\n ], [t, loadUserOptions, reportDeviceState])\n\n // NOTE: intentionally a flat field list (no `groups`). CrudForm only applies `visibleWhen` in the\n // ungrouped render path \u2014 grouped fields ignore it \u2014 and this form relies on mode/platform-driven\n // field visibility (silent hides title/body/options; iOS hides channelId).\n\n return (\n <Page>\n <PageBody>\n <CrudForm<FormValues>\n title={t('push_notifications.send.pageTitle')}\n backHref={DELIVERIES_HREF}\n formId={FORM_ID}\n hideFooterActions\n fields={fields}\n initialValues={{ userId: '', deviceId: '', mode: 'visible', title: '', body: '', data: [], sound: '', badge: '', image: '', priority: '', channelId: '', showChannelId: true }}\n submitLabel={t('push_notifications.send.submit')}\n onSubmit={async (values) => {\n setSubmitting(true)\n try {\n const silent = values.mode === 'silent'\n const body = (values.body ?? '').trim()\n\n const dataMap: Record<string, string> = {}\n for (const pair of Array.isArray(values.data) ? values.data : []) {\n const key = (pair?.key ?? '').trim()\n if (key) dataMap[key] = `${pair?.value ?? ''}`\n }\n const hasData = Object.keys(dataMap).length > 0\n\n // Options that only affect a visible banner are dropped for silent.\n const pushOptions: Record<string, unknown> = {}\n if (!silent) {\n if (typeof values.sound === 'string' && values.sound.trim()) pushOptions.sound = values.sound.trim()\n const badgeNum = typeof values.badge === 'number' ? values.badge : Number.parseInt(String(values.badge ?? ''), 10)\n if (Number.isFinite(badgeNum) && badgeNum >= 0) pushOptions.badge = badgeNum\n if (typeof values.image === 'string' && values.image.trim()) pushOptions.image = values.image.trim()\n if (typeof values.channelId === 'string' && values.channelId.trim()) pushOptions.channelId = values.channelId.trim()\n }\n if (values.priority === 'high' || values.priority === 'normal') pushOptions.priority = values.priority\n const hasOptions = Object.keys(pushOptions).length > 0\n\n // Silent hides the title field; the API still needs one, so use a stable label for the log.\n const title = silent ? (values.title ?? '').trim() || 'Silent push' : (values.title ?? '').trim()\n\n await apiCallOrThrow('/api/push_notifications/custom-send', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n recipientUserId: (values.userId ?? '').trim(),\n deviceId: (values.deviceId ?? '').trim() || undefined,\n title,\n body: !silent && body.length > 0 ? body : undefined,\n silent,\n data: hasData ? dataMap : undefined,\n pushOptions: hasOptions ? pushOptions : undefined,\n }),\n })\n flash(t('push_notifications.send.success'), 'success')\n router.push(DELIVERIES_HREF)\n } finally {\n setSubmitting(false)\n }\n }}\n />\n <div className=\"mt-4 flex items-center justify-between gap-3\">\n <div className=\"text-xs\">\n {!deviceState.userId ? (\n <span className=\"text-muted-foreground\">{t('push_notifications.send.deviceSelectUserFirst')}</span>\n ) : deviceState.count === 0 && !deviceState.loading ? (\n <span className=\"text-status-warning-text\">{t('push_notifications.send.deviceNone')}</span>\n ) : null}\n </div>\n <div className=\"flex gap-2\">\n <Button asChild variant=\"outline\">\n <Link href={DELIVERIES_HREF}>{t('common.cancel', 'Cancel')}</Link>\n </Button>\n <Button\n type=\"submit\"\n form={FORM_ID}\n disabled={!deviceState.userId || deviceState.count === 0 || deviceState.loading || submitting}\n >\n {t('push_notifications.send.submit')}\n </Button>\n </div>\n </div>\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
const megaphoneIcon = React.createElement(
|
|
3
|
+
"svg",
|
|
4
|
+
{ width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" },
|
|
5
|
+
React.createElement("path", { d: "m3 11 18-5v12L3 14v-3z" }),
|
|
6
|
+
React.createElement("path", { d: "M11.6 16.8a3 3 0 1 1-5.8-1.6" })
|
|
7
|
+
);
|
|
8
|
+
const metadata = {
|
|
9
|
+
requireAuth: true,
|
|
10
|
+
requireFeatures: ["push_notifications.send_custom"],
|
|
11
|
+
pageTitle: "Send Push",
|
|
12
|
+
pageTitleKey: "push_notifications.send.pageTitle",
|
|
13
|
+
pageGroup: "External systems",
|
|
14
|
+
pageGroupKey: "backend.nav.externalSystems",
|
|
15
|
+
pageOrder: 55,
|
|
16
|
+
icon: megaphoneIcon,
|
|
17
|
+
pageContext: "settings",
|
|
18
|
+
breadcrumb: [
|
|
19
|
+
{ label: "Push Deliveries", labelKey: "push_notifications.deliveries.pageTitle", href: "/backend/push_notifications" },
|
|
20
|
+
{ label: "Send Push", labelKey: "push_notifications.send.pageTitle" }
|
|
21
|
+
]
|
|
22
|
+
};
|
|
23
|
+
export {
|
|
24
|
+
metadata
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=page.meta.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/push_notifications/backend/push_notifications/send/page.meta.ts"],
|
|
4
|
+
"sourcesContent": ["import React from 'react'\n\nconst megaphoneIcon = React.createElement(\n 'svg',\n { width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' },\n React.createElement('path', { d: 'm3 11 18-5v12L3 14v-3z' }),\n React.createElement('path', { d: 'M11.6 16.8a3 3 0 1 1-5.8-1.6' }),\n)\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['push_notifications.send_custom'],\n pageTitle: 'Send Push',\n pageTitleKey: 'push_notifications.send.pageTitle',\n pageGroup: 'External systems',\n pageGroupKey: 'backend.nav.externalSystems',\n pageOrder: 55,\n icon: megaphoneIcon,\n pageContext: 'settings' as const,\n breadcrumb: [\n { label: 'Push Deliveries', labelKey: 'push_notifications.deliveries.pageTitle', href: '/backend/push_notifications' },\n { label: 'Send Push', labelKey: 'push_notifications.send.pageTitle' },\n ],\n} as const\n"],
|
|
5
|
+
"mappings": "AAAA,OAAO,WAAW;AAElB,MAAM,gBAAgB,MAAM;AAAA,EAC1B;AAAA,EACA,EAAE,OAAO,IAAI,QAAQ,IAAI,SAAS,aAAa,MAAM,QAAQ,QAAQ,gBAAgB,aAAa,GAAG,eAAe,SAAS,gBAAgB,QAAQ;AAAA,EACrJ,MAAM,cAAc,QAAQ,EAAE,GAAG,yBAAyB,CAAC;AAAA,EAC3D,MAAM,cAAc,QAAQ,EAAE,GAAG,+BAA+B,CAAC;AACnE;AAEO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,gCAAgC;AAAA,EAClD,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,MAAM;AAAA,EACN,aAAa;AAAA,EACb,YAAY;AAAA,IACV,EAAE,OAAO,mBAAmB,UAAU,2CAA2C,MAAM,8BAA8B;AAAA,IACrH,EAAE,OAAO,aAAa,UAAU,oCAAoC;AAAA,EACtE;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result) __defProp(target, key, result);
|
|
9
|
+
return result;
|
|
10
|
+
};
|
|
11
|
+
import { OptionalProps } from "@mikro-orm/core";
|
|
12
|
+
import { Entity, Index, PrimaryKey, Property } from "@mikro-orm/decorators/legacy";
|
|
13
|
+
OptionalProps;
|
|
14
|
+
let PushNotificationDelivery = class {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.silent = false;
|
|
17
|
+
this.status = "pending";
|
|
18
|
+
this.attempts = 0;
|
|
19
|
+
this.createdAt = /* @__PURE__ */ new Date();
|
|
20
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
__decorateClass([
|
|
24
|
+
PrimaryKey({ type: "uuid", defaultRaw: "gen_random_uuid()" })
|
|
25
|
+
], PushNotificationDelivery.prototype, "id", 2);
|
|
26
|
+
__decorateClass([
|
|
27
|
+
Property({ name: "tenant_id", type: "uuid" })
|
|
28
|
+
], PushNotificationDelivery.prototype, "tenantId", 2);
|
|
29
|
+
__decorateClass([
|
|
30
|
+
Property({ name: "organization_id", type: "uuid", nullable: true })
|
|
31
|
+
], PushNotificationDelivery.prototype, "organizationId", 2);
|
|
32
|
+
__decorateClass([
|
|
33
|
+
Property({ name: "notification_id", type: "uuid", nullable: true })
|
|
34
|
+
], PushNotificationDelivery.prototype, "notificationId", 2);
|
|
35
|
+
__decorateClass([
|
|
36
|
+
Property({ name: "notification_type_id", type: "text" })
|
|
37
|
+
], PushNotificationDelivery.prototype, "notificationTypeId", 2);
|
|
38
|
+
__decorateClass([
|
|
39
|
+
Property({ name: "user_device_id", type: "uuid" })
|
|
40
|
+
], PushNotificationDelivery.prototype, "userDeviceId", 2);
|
|
41
|
+
__decorateClass([
|
|
42
|
+
Property({ name: "user_id", type: "uuid" })
|
|
43
|
+
], PushNotificationDelivery.prototype, "userId", 2);
|
|
44
|
+
__decorateClass([
|
|
45
|
+
Property({ name: "provider", type: "text" })
|
|
46
|
+
], PushNotificationDelivery.prototype, "provider", 2);
|
|
47
|
+
__decorateClass([
|
|
48
|
+
Property({ name: "token_snapshot", type: "text" })
|
|
49
|
+
], PushNotificationDelivery.prototype, "tokenSnapshot", 2);
|
|
50
|
+
__decorateClass([
|
|
51
|
+
Property({ name: "silent", type: "boolean", default: false })
|
|
52
|
+
], PushNotificationDelivery.prototype, "silent", 2);
|
|
53
|
+
__decorateClass([
|
|
54
|
+
Property({ name: "status", type: "text", default: "pending" })
|
|
55
|
+
], PushNotificationDelivery.prototype, "status", 2);
|
|
56
|
+
__decorateClass([
|
|
57
|
+
Property({ name: "attempts", type: "int", default: 0 })
|
|
58
|
+
], PushNotificationDelivery.prototype, "attempts", 2);
|
|
59
|
+
__decorateClass([
|
|
60
|
+
Property({ name: "last_error", type: "text", nullable: true })
|
|
61
|
+
], PushNotificationDelivery.prototype, "lastError", 2);
|
|
62
|
+
__decorateClass([
|
|
63
|
+
Property({ name: "payload", type: "json" })
|
|
64
|
+
], PushNotificationDelivery.prototype, "payload", 2);
|
|
65
|
+
__decorateClass([
|
|
66
|
+
Property({ name: "provider_response", type: "json", nullable: true })
|
|
67
|
+
], PushNotificationDelivery.prototype, "providerResponse", 2);
|
|
68
|
+
__decorateClass([
|
|
69
|
+
Property({ name: "created_at", type: Date, onCreate: () => /* @__PURE__ */ new Date() })
|
|
70
|
+
], PushNotificationDelivery.prototype, "createdAt", 2);
|
|
71
|
+
__decorateClass([
|
|
72
|
+
Property({ name: "sent_at", type: Date, nullable: true })
|
|
73
|
+
], PushNotificationDelivery.prototype, "sentAt", 2);
|
|
74
|
+
__decorateClass([
|
|
75
|
+
Property({ name: "next_retry_at", type: Date, nullable: true })
|
|
76
|
+
], PushNotificationDelivery.prototype, "nextRetryAt", 2);
|
|
77
|
+
__decorateClass([
|
|
78
|
+
Property({ name: "updated_at", type: Date, onCreate: () => /* @__PURE__ */ new Date(), onUpdate: () => /* @__PURE__ */ new Date() })
|
|
79
|
+
], PushNotificationDelivery.prototype, "updatedAt", 2);
|
|
80
|
+
PushNotificationDelivery = __decorateClass([
|
|
81
|
+
Entity({ tableName: "push_notification_deliveries" }),
|
|
82
|
+
Index({ name: "push_notification_deliveries_tenant_status_idx", properties: ["tenantId", "status", "createdAt"] }),
|
|
83
|
+
Index({ name: "push_notification_deliveries_notification_idx", properties: ["notificationId"] }),
|
|
84
|
+
Index({
|
|
85
|
+
name: "push_notification_deliveries_notif_device_unique",
|
|
86
|
+
expression: 'create unique index "push_notification_deliveries_notif_device_unique" on "push_notification_deliveries" ("notification_id", "user_device_id") where "notification_id" is not null'
|
|
87
|
+
})
|
|
88
|
+
], PushNotificationDelivery);
|
|
89
|
+
export {
|
|
90
|
+
PushNotificationDelivery
|
|
91
|
+
};
|
|
92
|
+
//# sourceMappingURL=entities.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/push_notifications/data/entities.ts"],
|
|
4
|
+
"sourcesContent": ["import { OptionalProps } from '@mikro-orm/core'\nimport { Entity, Index, PrimaryKey, Property } from '@mikro-orm/decorators/legacy'\n\nexport type PushDeliveryStatus = 'pending' | 'sending' | 'sent' | 'failed' | 'skipped' | 'expired'\n\n/**\n * Append-only delivery log for a single push notification attempt against one device.\n *\n * One row per (notification, device). The strategy inserts rows `pending`; the `send-push` worker\n * atomically claims a row (`pending` \u2192 `sending`, so a redelivered job is processed once) and then\n * transitions it to `sent`/`failed`/`skipped`, or `expired` once retries are exhausted. This is a\n * background-job/log row, so it is intentionally EXEMPT from optimistic locking\n * (status transitions are server-driven, never concurrently user-edited) and is\n * NOT added to the curated `optimistic-lock-editable-entities` list.\n *\n * `push_token` is a secret: the full token is never persisted here \u2014 only\n * `token_snapshot` (last 8 chars, for debugging across token rotation) plus the\n * `provider` snapshot so the audit trail survives token rotation and device deletion.\n */\n@Entity({ tableName: 'push_notification_deliveries' })\n@Index({ name: 'push_notification_deliveries_tenant_status_idx', properties: ['tenantId', 'status', 'createdAt'] })\n@Index({ name: 'push_notification_deliveries_notification_idx', properties: ['notificationId'] })\n// Idempotency guard for the fan-out: the `push` strategy runs inside the at-least-once persistent\n// `notifications:deliver` subscriber, so a redelivered event would otherwise insert a second set of\n// rows for the same (notification, device) \u2192 a duplicate push. A partial unique index (only where\n// notification_id is set \u2014 direct/silent pushes without a notification are out of scope) lets the\n// strategy insert-on-conflict-do-nothing, making a re-run a no-op.\n@Index({\n name: 'push_notification_deliveries_notif_device_unique',\n expression:\n 'create unique index \"push_notification_deliveries_notif_device_unique\" on \"push_notification_deliveries\" (\"notification_id\", \"user_device_id\") where \"notification_id\" is not null',\n})\nexport class PushNotificationDelivery {\n [OptionalProps]?:\n | 'organizationId'\n | 'notificationId'\n | 'status'\n | 'attempts'\n | 'lastError'\n | 'providerResponse'\n | 'silent'\n | 'createdAt'\n | 'sentAt'\n | 'nextRetryAt'\n | 'updatedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n // Soft FK -> notifications.notifications (nullable: a push can be dispatched without an in-app row).\n @Property({ name: 'notification_id', type: 'uuid', nullable: true })\n notificationId?: string | null\n\n @Property({ name: 'notification_type_id', type: 'text' })\n notificationTypeId!: string\n\n // Soft FK -> devices.user_devices (link declared in data/extensions.ts).\n @Property({ name: 'user_device_id', type: 'uuid' })\n userDeviceId!: string\n\n @Property({ name: 'user_id', type: 'uuid' })\n userId!: string\n\n // Snapshot of the push CommunicationChannel providerKey used at send time.\n @Property({ name: 'provider', type: 'text' })\n provider!: string\n\n // Last 8 chars of the device push token only \u2014 never the full secret.\n @Property({ name: 'token_snapshot', type: 'text' })\n tokenSnapshot!: string\n\n // Snapshot of whether this was a silent / content-available wake-up (derived from the\n // notification type's `silent` flag at fan-out time) so the log distinguishes silent deliveries.\n @Property({ name: 'silent', type: 'boolean', default: false })\n silent: boolean = false\n\n @Property({ name: 'status', type: 'text', default: 'pending' })\n status: PushDeliveryStatus = 'pending'\n\n @Property({ name: 'attempts', type: 'int', default: 0 })\n attempts: number = 0\n\n @Property({ name: 'last_error', type: 'text', nullable: true })\n lastError?: string | null\n\n @Property({ name: 'payload', type: 'json' })\n payload!: Record<string, unknown>\n\n @Property({ name: 'provider_response', type: 'json', nullable: true })\n providerResponse?: Record<string, unknown> | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'sent_at', type: Date, nullable: true })\n sentAt?: Date | null\n\n // Set when a retryable failure re-enqueues the job (observability for the admin delivery log);\n // cleared once the delivery reaches a terminal state.\n @Property({ name: 'next_retry_at', type: Date, nullable: true })\n nextRetryAt?: Date | null\n\n @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;AAAA,SAAS,qBAAqB;AAC9B,SAAS,QAAQ,OAAO,YAAY,gBAAgB;AAgCjD;AADI,IAAM,2BAAN,MAA+B;AAAA,EAA/B;AAgDL,kBAAkB;AAGlB,kBAA6B;AAG7B,oBAAmB;AAYnB,qBAAkB,oBAAI,KAAK;AAW3B,qBAAkB,oBAAI,KAAK;AAAA;AAC7B;AA/DE;AAAA,EADC,WAAW,EAAE,MAAM,QAAQ,YAAY,oBAAoB,CAAC;AAAA,GAdlD,yBAeX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,aAAa,MAAM,OAAO,CAAC;AAAA,GAjBlC,yBAkBX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,mBAAmB,MAAM,QAAQ,UAAU,KAAK,CAAC;AAAA,GApBxD,yBAqBX;AAIA;AAAA,EADC,SAAS,EAAE,MAAM,mBAAmB,MAAM,QAAQ,UAAU,KAAK,CAAC;AAAA,GAxBxD,yBAyBX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,wBAAwB,MAAM,OAAO,CAAC;AAAA,GA3B7C,yBA4BX;AAIA;AAAA,EADC,SAAS,EAAE,MAAM,kBAAkB,MAAM,OAAO,CAAC;AAAA,GA/BvC,yBAgCX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,WAAW,MAAM,OAAO,CAAC;AAAA,GAlChC,yBAmCX;AAIA;AAAA,EADC,SAAS,EAAE,MAAM,YAAY,MAAM,OAAO,CAAC;AAAA,GAtCjC,yBAuCX;AAIA;AAAA,EADC,SAAS,EAAE,MAAM,kBAAkB,MAAM,OAAO,CAAC;AAAA,GA1CvC,yBA2CX;AAKA;AAAA,EADC,SAAS,EAAE,MAAM,UAAU,MAAM,WAAW,SAAS,MAAM,CAAC;AAAA,GA/ClD,yBAgDX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,UAAU,MAAM,QAAQ,SAAS,UAAU,CAAC;AAAA,GAlDnD,yBAmDX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,YAAY,MAAM,OAAO,SAAS,EAAE,CAAC;AAAA,GArD5C,yBAsDX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,QAAQ,UAAU,KAAK,CAAC;AAAA,GAxDnD,yBAyDX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,WAAW,MAAM,OAAO,CAAC;AAAA,GA3DhC,yBA4DX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,qBAAqB,MAAM,QAAQ,UAAU,KAAK,CAAC;AAAA,GA9D1D,yBA+DX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,MAAM,UAAU,MAAM,oBAAI,KAAK,EAAE,CAAC;AAAA,GAjE7D,yBAkEX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,WAAW,MAAM,MAAM,UAAU,KAAK,CAAC;AAAA,GApE9C,yBAqEX;AAKA;AAAA,EADC,SAAS,EAAE,MAAM,iBAAiB,MAAM,MAAM,UAAU,KAAK,CAAC;AAAA,GAzEpD,yBA0EX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,MAAM,UAAU,MAAM,oBAAI,KAAK,GAAG,UAAU,MAAM,oBAAI,KAAK,EAAE,CAAC;AAAA,GA5EzF,yBA6EX;AA7EW,2BAAN;AAAA,EAbN,OAAO,EAAE,WAAW,+BAA+B,CAAC;AAAA,EACpD,MAAM,EAAE,MAAM,kDAAkD,YAAY,CAAC,YAAY,UAAU,WAAW,EAAE,CAAC;AAAA,EACjH,MAAM,EAAE,MAAM,iDAAiD,YAAY,CAAC,gBAAgB,EAAE,CAAC;AAAA,EAM/F,MAAM;AAAA,IACL,MAAM;AAAA,IACN,YACE;AAAA,EACJ,CAAC;AAAA,GACY;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const entityExtensions = [
|
|
2
|
+
{
|
|
3
|
+
base: "push_notifications:push_notification_delivery",
|
|
4
|
+
extension: "devices:user_device",
|
|
5
|
+
join: { baseKey: "user_device_id", extensionKey: "id" },
|
|
6
|
+
cardinality: "many-to-one",
|
|
7
|
+
description: "Links a push delivery row to the target user device it was sent to"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
base: "push_notifications:push_notification_delivery",
|
|
11
|
+
extension: "notifications:notification",
|
|
12
|
+
join: { baseKey: "notification_id", extensionKey: "id" },
|
|
13
|
+
cardinality: "many-to-one",
|
|
14
|
+
description: "Links a push delivery row to the in-app notification that triggered it"
|
|
15
|
+
}
|
|
16
|
+
];
|
|
17
|
+
const extensions = entityExtensions;
|
|
18
|
+
var extensions_default = entityExtensions;
|
|
19
|
+
export {
|
|
20
|
+
extensions_default as default,
|
|
21
|
+
extensions
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=extensions.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/push_notifications/data/extensions.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityExtension } from '@open-mercato/shared/modules/entities'\n\n/**\n * Cross-module entity links declared by the push_notifications module.\n *\n * Per root AGENTS.md, modules do NOT form direct ORM relationships across\n * boundaries. `PushNotificationDelivery` carries plain UUID columns that\n * reference rows owned by other modules; the links are declared here so the\n * data engine + UI tooling can traverse them.\n */\nconst entityExtensions: EntityExtension[] = [\n {\n base: 'push_notifications:push_notification_delivery',\n extension: 'devices:user_device',\n join: { baseKey: 'user_device_id', extensionKey: 'id' },\n cardinality: 'many-to-one',\n description: 'Links a push delivery row to the target user device it was sent to',\n },\n {\n base: 'push_notifications:push_notification_delivery',\n extension: 'notifications:notification',\n join: { baseKey: 'notification_id', extensionKey: 'id' },\n cardinality: 'many-to-one',\n description: 'Links a push delivery row to the in-app notification that triggered it',\n },\n]\n\nexport const extensions = entityExtensions\nexport default entityExtensions\n"],
|
|
5
|
+
"mappings": "AAUA,MAAM,mBAAsC;AAAA,EAC1C;AAAA,IACE,MAAM;AAAA,IACN,WAAW;AAAA,IACX,MAAM,EAAE,SAAS,kBAAkB,cAAc,KAAK;AAAA,IACtD,aAAa;AAAA,IACb,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,WAAW;AAAA,IACX,MAAM,EAAE,SAAS,mBAAmB,cAAc,KAAK;AAAA,IACvD,aAAa;AAAA,IACb,aAAa;AAAA,EACf;AACF;AAEO,MAAM,aAAa;AAC1B,IAAO,qBAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { pushOptionsSchema } from "@open-mercato/core/modules/notifications/data/validators";
|
|
3
|
+
const PUSH_DELIVERY_STATUSES = ["pending", "sending", "sent", "failed", "skipped", "expired"];
|
|
4
|
+
const rangeDateFilter = z.union([z.string().datetime({ offset: true }), z.string().date()]);
|
|
5
|
+
const customSendSchema = z.object({
|
|
6
|
+
recipientUserId: z.string().uuid(),
|
|
7
|
+
// Optional: target one of the recipient's devices; omit to send to all their devices.
|
|
8
|
+
deviceId: z.string().uuid().optional(),
|
|
9
|
+
title: z.string().trim().min(1).max(500),
|
|
10
|
+
body: z.string().trim().max(2e3).nullish(),
|
|
11
|
+
data: z.record(z.string(), z.string()).optional(),
|
|
12
|
+
pushOptions: pushOptionsSchema.optional(),
|
|
13
|
+
// When true, deliver a silent data-only wake-up instead of a visible banner.
|
|
14
|
+
silent: z.boolean().optional()
|
|
15
|
+
}).strict();
|
|
16
|
+
const CUSTOM_SEND_NO_DEVICES_WARNING = "no_matching_devices_in_scope";
|
|
17
|
+
const customSendResponseSchema = z.object({
|
|
18
|
+
enqueued: z.number(),
|
|
19
|
+
warning: z.literal(CUSTOM_SEND_NO_DEVICES_WARNING).optional(),
|
|
20
|
+
message: z.string().optional()
|
|
21
|
+
});
|
|
22
|
+
const deliveryListSchema = z.object({
|
|
23
|
+
page: z.coerce.number().min(1).default(1),
|
|
24
|
+
pageSize: z.coerce.number().min(1).max(100).default(50),
|
|
25
|
+
status: z.enum(PUSH_DELIVERY_STATUSES).optional(),
|
|
26
|
+
userId: z.string().uuid().optional(),
|
|
27
|
+
from: rangeDateFilter.optional(),
|
|
28
|
+
to: rangeDateFilter.optional(),
|
|
29
|
+
sortField: z.string().optional(),
|
|
30
|
+
sortDir: z.enum(["asc", "desc"]).optional()
|
|
31
|
+
}).passthrough();
|
|
32
|
+
const deliveryListFields = [
|
|
33
|
+
"id",
|
|
34
|
+
"tenant_id",
|
|
35
|
+
"organization_id",
|
|
36
|
+
"notification_id",
|
|
37
|
+
"notification_type_id",
|
|
38
|
+
"user_device_id",
|
|
39
|
+
"user_id",
|
|
40
|
+
"provider",
|
|
41
|
+
"token_snapshot",
|
|
42
|
+
"status",
|
|
43
|
+
"attempts",
|
|
44
|
+
"last_error",
|
|
45
|
+
"created_at",
|
|
46
|
+
"sent_at",
|
|
47
|
+
"next_retry_at",
|
|
48
|
+
"updated_at"
|
|
49
|
+
];
|
|
50
|
+
const deliveryListSortFieldMap = {
|
|
51
|
+
createdAt: "created_at",
|
|
52
|
+
sentAt: "sent_at",
|
|
53
|
+
updatedAt: "updated_at",
|
|
54
|
+
status: "status"
|
|
55
|
+
};
|
|
56
|
+
const deliveryListItemSchema = z.object({
|
|
57
|
+
id: z.string().uuid(),
|
|
58
|
+
tenant_id: z.string().uuid(),
|
|
59
|
+
organization_id: z.string().uuid().nullable().optional(),
|
|
60
|
+
notification_id: z.string().uuid().nullable().optional(),
|
|
61
|
+
notification_type_id: z.string(),
|
|
62
|
+
user_device_id: z.string().uuid(),
|
|
63
|
+
user_id: z.string().uuid(),
|
|
64
|
+
provider: z.string(),
|
|
65
|
+
token_snapshot: z.string(),
|
|
66
|
+
status: z.enum(PUSH_DELIVERY_STATUSES),
|
|
67
|
+
attempts: z.number(),
|
|
68
|
+
last_error: z.string().nullable().optional(),
|
|
69
|
+
created_at: z.string().nullable().optional(),
|
|
70
|
+
sent_at: z.string().nullable().optional(),
|
|
71
|
+
next_retry_at: z.string().nullable().optional(),
|
|
72
|
+
updated_at: z.string().nullable().optional()
|
|
73
|
+
});
|
|
74
|
+
const deliveryDetailItemSchema = deliveryListItemSchema.extend({
|
|
75
|
+
payload: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
76
|
+
provider_response: z.record(z.string(), z.unknown()).nullable().optional()
|
|
77
|
+
});
|
|
78
|
+
export {
|
|
79
|
+
CUSTOM_SEND_NO_DEVICES_WARNING,
|
|
80
|
+
PUSH_DELIVERY_STATUSES,
|
|
81
|
+
customSendResponseSchema,
|
|
82
|
+
customSendSchema,
|
|
83
|
+
deliveryDetailItemSchema,
|
|
84
|
+
deliveryListFields,
|
|
85
|
+
deliveryListItemSchema,
|
|
86
|
+
deliveryListSchema,
|
|
87
|
+
deliveryListSortFieldMap
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=validators.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/push_notifications/data/validators.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { pushOptionsSchema } from '@open-mercato/core/modules/notifications/data/validators'\n\nexport const PUSH_DELIVERY_STATUSES = ['pending', 'sending', 'sent', 'failed', 'skipped', 'expired'] as const\n\n// A `created_at` range bound: an ISO-8601 date (`2026-07-01`) or datetime (`2026-07-01T00:00:00Z`).\n// Validated here so a malformed value fails with a 400 instead of reaching the query engine / Postgres.\nconst rangeDateFilter = z.union([z.string().datetime({ offset: true }), z.string().date()])\n\n// POST /api/push_notifications/custom-send \u2014 admin composes a one-off visible push to a single user.\n// title/body are literal free text (not i18n keys). data/pushOptions reuse the notifications contract.\nexport const customSendSchema = z\n .object({\n recipientUserId: z.string().uuid(),\n // Optional: target one of the recipient's devices; omit to send to all their devices.\n deviceId: z.string().uuid().optional(),\n title: z.string().trim().min(1).max(500),\n body: z.string().trim().max(2000).nullish(),\n data: z.record(z.string(), z.string()).optional(),\n pushOptions: pushOptionsSchema.optional(),\n // When true, deliver a silent data-only wake-up instead of a visible banner.\n silent: z.boolean().optional(),\n })\n .strict()\nexport type CustomSendInput = z.infer<typeof customSendSchema>\n\n// Machine-readable warning surfaced when a well-formed send resolved no deliverable device in scope\n// (no push channel configured, no in-scope devices, or none whose provider matches an active channel).\n// The caller gets `enqueued: 0` plus this code + a human `message` instead of a misleading silent success.\nexport const CUSTOM_SEND_NO_DEVICES_WARNING = 'no_matching_devices_in_scope'\n\nexport const customSendResponseSchema = z.object({\n enqueued: z.number(),\n warning: z.literal(CUSTOM_SEND_NO_DEVICES_WARNING).optional(),\n message: z.string().optional(),\n})\n\n// Read-only delivery-log list contract (admin observability). No full push token is ever exposed \u2014\n// only `token_snapshot` (last 8 chars) and the `provider` snapshot.\nexport const deliveryListSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n status: z.enum(PUSH_DELIVERY_STATUSES).optional(),\n userId: z.string().uuid().optional(),\n from: rangeDateFilter.optional(),\n to: rangeDateFilter.optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n })\n .passthrough()\n\nexport const deliveryListFields: string[] = [\n 'id',\n 'tenant_id',\n 'organization_id',\n 'notification_id',\n 'notification_type_id',\n 'user_device_id',\n 'user_id',\n 'provider',\n 'token_snapshot',\n 'status',\n 'attempts',\n 'last_error',\n 'created_at',\n 'sent_at',\n 'next_retry_at',\n 'updated_at',\n]\n\nexport const deliveryListSortFieldMap: Record<string, string> = {\n createdAt: 'created_at',\n sentAt: 'sent_at',\n updatedAt: 'updated_at',\n status: 'status',\n}\n\nexport const deliveryListItemSchema = z.object({\n id: z.string().uuid(),\n tenant_id: z.string().uuid(),\n organization_id: z.string().uuid().nullable().optional(),\n notification_id: z.string().uuid().nullable().optional(),\n notification_type_id: z.string(),\n user_device_id: z.string().uuid(),\n user_id: z.string().uuid(),\n provider: z.string(),\n token_snapshot: z.string(),\n status: z.enum(PUSH_DELIVERY_STATUSES),\n attempts: z.number(),\n last_error: z.string().nullable().optional(),\n created_at: z.string().nullable().optional(),\n sent_at: z.string().nullable().optional(),\n next_retry_at: z.string().nullable().optional(),\n updated_at: z.string().nullable().optional(),\n})\n\n// Detail adds the JSON payload + provider response (still no full token).\nexport const deliveryDetailItemSchema = deliveryListItemSchema.extend({\n payload: z.record(z.string(), z.unknown()).nullable().optional(),\n provider_response: z.record(z.string(), z.unknown()).nullable().optional(),\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { asValue } from "awilix";
|
|
2
|
+
import { PushNotificationDelivery } from "./data/entities.js";
|
|
3
|
+
import { ensurePushStubAdapterRegistered } from "./lib/push-stub-adapter.js";
|
|
4
|
+
import { pushNotificationService } from "./lib/send-custom-push.js";
|
|
5
|
+
function register(container) {
|
|
6
|
+
ensurePushStubAdapterRegistered();
|
|
7
|
+
container.register({
|
|
8
|
+
PushNotificationDelivery: asValue(PushNotificationDelivery),
|
|
9
|
+
// Stateless service backing the admin custom-send push (decoupled via DI). The caller passes
|
|
10
|
+
// its own scoped `resolve` in the call args.
|
|
11
|
+
pushNotificationService: asValue(pushNotificationService)
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
register
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=di.js.map
|