@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,87 @@
|
|
|
1
|
+
import { sql } from "kysely";
|
|
2
|
+
import { deriveCategory } from "./derive-category.js";
|
|
3
|
+
const registry = /* @__PURE__ */ new Map();
|
|
4
|
+
let synced = false;
|
|
5
|
+
function registerNotificationTypes(types, options = {}) {
|
|
6
|
+
if (options.replace) {
|
|
7
|
+
registry.clear();
|
|
8
|
+
synced = false;
|
|
9
|
+
}
|
|
10
|
+
for (const type of types) {
|
|
11
|
+
registry.set(type.type, type);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function getNotificationType(type) {
|
|
15
|
+
return registry.get(type);
|
|
16
|
+
}
|
|
17
|
+
function getNotificationTypes() {
|
|
18
|
+
return Array.from(registry.values());
|
|
19
|
+
}
|
|
20
|
+
async function syncNotificationTypes(em, opts = {}) {
|
|
21
|
+
const definitions = getNotificationTypes();
|
|
22
|
+
if (synced && !opts.force) {
|
|
23
|
+
return { created: 0, updated: 0, deleted: 0, total: definitions.length };
|
|
24
|
+
}
|
|
25
|
+
const db = em.getKysely();
|
|
26
|
+
const existing = await db.selectFrom("notification_types").select(["id", "label_key", "description_key", "category", "silent", "non_opt_out"]).where("tenant_id", "is", null).execute();
|
|
27
|
+
const byId = new Map(existing.map((row) => [row.id, row]));
|
|
28
|
+
const labelKeyFor = (def) => def.labelKey ?? def.titleKey;
|
|
29
|
+
const descKeyFor = (def) => def.descriptionKey ?? null;
|
|
30
|
+
const categoryFor = (def) => def.category ?? deriveCategory(def.type);
|
|
31
|
+
const silentFor = (def) => def.silent === true;
|
|
32
|
+
const nonOptOutFor = (def) => def.nonOptOut === true;
|
|
33
|
+
const visibleDefinitions = definitions.filter((def) => def.hiddenFromSettings !== true);
|
|
34
|
+
const toCreate = visibleDefinitions.filter((def) => !byId.has(def.type));
|
|
35
|
+
const toUpdate = visibleDefinitions.filter((def) => {
|
|
36
|
+
const row = byId.get(def.type);
|
|
37
|
+
if (!row) return false;
|
|
38
|
+
return row.label_key !== labelKeyFor(def) || (row.description_key ?? null) !== descKeyFor(def) || (row.category ?? null) !== categoryFor(def) || row.silent !== silentFor(def) || row.non_opt_out !== nonOptOutFor(def);
|
|
39
|
+
});
|
|
40
|
+
let created = 0;
|
|
41
|
+
let updated = 0;
|
|
42
|
+
let deleted = 0;
|
|
43
|
+
if (toCreate.length) {
|
|
44
|
+
await db.insertInto("notification_types").values(
|
|
45
|
+
toCreate.map((def) => ({
|
|
46
|
+
id: def.type,
|
|
47
|
+
tenant_id: null,
|
|
48
|
+
label_key: labelKeyFor(def),
|
|
49
|
+
description_key: descKeyFor(def),
|
|
50
|
+
category: categoryFor(def),
|
|
51
|
+
silent: silentFor(def),
|
|
52
|
+
non_opt_out: nonOptOutFor(def),
|
|
53
|
+
created_at: sql`now()`,
|
|
54
|
+
updated_at: sql`now()`
|
|
55
|
+
}))
|
|
56
|
+
).onConflict((oc) => oc.column("id").doNothing()).execute();
|
|
57
|
+
created = toCreate.length;
|
|
58
|
+
}
|
|
59
|
+
for (const def of toUpdate) {
|
|
60
|
+
await db.updateTable("notification_types").set({
|
|
61
|
+
label_key: labelKeyFor(def),
|
|
62
|
+
description_key: descKeyFor(def),
|
|
63
|
+
category: categoryFor(def),
|
|
64
|
+
silent: silentFor(def),
|
|
65
|
+
non_opt_out: nonOptOutFor(def),
|
|
66
|
+
updated_at: sql`now()`
|
|
67
|
+
}).where("id", "=", def.type).where("tenant_id", "is", null).execute();
|
|
68
|
+
updated += 1;
|
|
69
|
+
}
|
|
70
|
+
if (definitions.length > 0) {
|
|
71
|
+
const validIds = new Set(visibleDefinitions.map((def) => def.type));
|
|
72
|
+
const staleIds = existing.filter((row) => !validIds.has(row.id)).map((row) => row.id);
|
|
73
|
+
if (staleIds.length) {
|
|
74
|
+
await db.deleteFrom("notification_types").where("tenant_id", "is", null).where("id", "in", staleIds).execute();
|
|
75
|
+
deleted = staleIds.length;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
synced = true;
|
|
79
|
+
return { created, updated, deleted, total: definitions.length };
|
|
80
|
+
}
|
|
81
|
+
export {
|
|
82
|
+
getNotificationType,
|
|
83
|
+
getNotificationTypes,
|
|
84
|
+
registerNotificationTypes,
|
|
85
|
+
syncNotificationTypes
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=notification-type-registry.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/notifications/lib/notification-type-registry.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, sql } from 'kysely'\nimport type { NotificationTypeDefinition } from '@open-mercato/shared/modules/notifications/types'\nimport { deriveCategory } from './derive-category'\n\nconst registry = new Map<string, NotificationTypeDefinition>()\n\nlet synced = false\n\nexport type RegisterNotificationTypesOptions = {\n replace?: boolean\n}\n\n/**\n * In-memory mirror of the code-registered notification type catalogue. Fed at\n * app bootstrap from the generated aggregate (`notificationTypes`) \u2014 mirrors the\n * `messages` registry. This stays the source of truth for code; the\n * `notification_types` table is a read-through mirror for remote clients.\n */\nexport function registerNotificationTypes(\n types: NotificationTypeDefinition[],\n options: RegisterNotificationTypesOptions = {},\n): void {\n if (options.replace) {\n registry.clear()\n synced = false\n }\n for (const type of types) {\n registry.set(type.type, type)\n }\n}\n\nexport function getNotificationType(type: string): NotificationTypeDefinition | undefined {\n return registry.get(type)\n}\n\nexport function getNotificationTypes(): NotificationTypeDefinition[] {\n return Array.from(registry.values())\n}\n\nexport type SyncNotificationTypesResult = {\n created: number\n updated: number\n deleted: number\n total: number\n}\n\n/**\n * Reconcile the in-memory catalogue into the `notification_types` table.\n * Code-registered types are system-wide, so rows are written with\n * `tenant_id IS NULL`. Idempotent: updates the mirrored columns\n * (`label_key`/`description_key`/`category`/`silent`/`non_opt_out`) only on\n * drift, and prunes system-wide rows no longer in the catalogue. Guarded by a\n * once-per-process flag on the lazy path; pass `force` to bypass it (used by the\n * explicit `notifications.type_registry.sync` subscriber).\n *\n * Writes go through kysely with `INSERT ... ON CONFLICT DO NOTHING` so a\n * concurrent first-sync (or the seedDefaults subscriber racing a `GET /types`)\n * can never raise a duplicate-PK 500 \u2014 mirrors `query_index/lib/jobs.ts`.\n */\nexport async function syncNotificationTypes(\n em: EntityManager,\n opts: { force?: boolean } = {},\n): Promise<SyncNotificationTypesResult> {\n const definitions = getNotificationTypes()\n if (synced && !opts.force) {\n return { created: 0, updated: 0, deleted: 0, total: definitions.length }\n }\n\n const db = em.getKysely<any>() as Kysely<any>\n\n // Read existing system-wide rows via kysely (not the ORM) so they stay out of the identity map and\n // the route's subsequent em.find reflects exactly what this function just wrote.\n const existing = (await db\n .selectFrom('notification_types')\n .select(['id', 'label_key', 'description_key', 'category', 'silent', 'non_opt_out'])\n .where('tenant_id', 'is', null)\n .execute()) as Array<{\n id: string\n label_key: string\n description_key: string | null\n category: string | null\n silent: boolean\n non_opt_out: boolean\n }>\n const byId = new Map(existing.map((row) => [row.id, row]))\n\n const labelKeyFor = (def: NotificationTypeDefinition) => def.labelKey ?? def.titleKey\n const descKeyFor = (def: NotificationTypeDefinition) => def.descriptionKey ?? null\n // Resolved once here so every mirrored row carries a grouping key and read paths\n // (GET /api/notifications/types) never need a fallback branch of their own.\n const categoryFor = (def: NotificationTypeDefinition) => def.category ?? deriveCategory(def.type)\n const silentFor = (def: NotificationTypeDefinition) => def.silent === true\n const nonOptOutFor = (def: NotificationTypeDefinition) => def.nonOptOut === true\n\n // Internal/admin-only types (`hiddenFromSettings`) are never exposed to the client catalogue: they\n // are excluded from create/update, and \u2014 because they drop out of `validIds` below \u2014 any stale row\n // for a type that was flipped to hidden gets pruned. They remain in the in-memory registry for\n // delivery logic. GET /api/notifications/types therefore never lists them.\n const visibleDefinitions = definitions.filter((def) => def.hiddenFromSettings !== true)\n\n const toCreate = visibleDefinitions.filter((def) => !byId.has(def.type))\n const toUpdate = visibleDefinitions.filter((def) => {\n const row = byId.get(def.type)\n if (!row) return false\n return (\n row.label_key !== labelKeyFor(def) ||\n (row.description_key ?? null) !== descKeyFor(def) ||\n (row.category ?? null) !== categoryFor(def) ||\n row.silent !== silentFor(def) ||\n row.non_opt_out !== nonOptOutFor(def)\n )\n })\n\n let created = 0\n let updated = 0\n let deleted = 0\n\n if (toCreate.length) {\n await db\n .insertInto('notification_types')\n .values(\n toCreate.map((def) => ({\n id: def.type,\n tenant_id: null,\n label_key: labelKeyFor(def),\n description_key: descKeyFor(def),\n category: categoryFor(def),\n silent: silentFor(def),\n non_opt_out: nonOptOutFor(def),\n created_at: sql`now()`,\n updated_at: sql`now()`,\n })),\n )\n .onConflict((oc: any) => oc.column('id').doNothing())\n .execute()\n created = toCreate.length\n }\n\n // Bump updated_at only on real drift, so repeated syncs don't churn timestamps.\n for (const def of toUpdate) {\n await db\n .updateTable('notification_types')\n .set({\n label_key: labelKeyFor(def),\n description_key: descKeyFor(def),\n category: categoryFor(def),\n silent: silentFor(def),\n non_opt_out: nonOptOutFor(def),\n updated_at: sql`now()`,\n })\n .where('id', '=', def.type)\n .where('tenant_id', 'is', null)\n .execute()\n updated += 1\n }\n\n // Prune system-wide rows no longer in the catalogue (a removed/renamed module's type stops showing\n // in the preferences matrix). Guarded by a non-empty catalogue so a mis-bootstrapped process with an\n // empty registry can never wipe the table. Known limitation, deliberately not addressed here: the\n // guard is a count, not a completeness check, so a process holding a PARTIAL catalogue still prunes\n // the missing modules' rows \u2014 same for multiple apps with different module sets sharing one DB.\n // Both are bounded: `synced` limits it to one sync per process, the rows come back on the next\n // full-catalogue sync, and nothing cascades (overrides and preferences reference the type by plain\n // string id with no FK, so they survive and re-attach). Closing it properly means gating on the\n // enabled-module set that fed the registry, or moving the prune off this lazy read path entirely.\n if (definitions.length > 0) {\n const validIds = new Set(visibleDefinitions.map((def) => def.type))\n const staleIds = existing.filter((row) => !validIds.has(row.id)).map((row) => row.id)\n if (staleIds.length) {\n await db\n .deleteFrom('notification_types')\n .where('tenant_id', 'is', null)\n .where('id', 'in', staleIds)\n .execute()\n deleted = staleIds.length\n }\n }\n\n synced = true\n return { created, updated, deleted, total: definitions.length }\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAsB,WAAW;AAEjC,SAAS,sBAAsB;AAE/B,MAAM,WAAW,oBAAI,IAAwC;AAE7D,IAAI,SAAS;AAYN,SAAS,0BACd,OACA,UAA4C,CAAC,GACvC;AACN,MAAI,QAAQ,SAAS;AACnB,aAAS,MAAM;AACf,aAAS;AAAA,EACX;AACA,aAAW,QAAQ,OAAO;AACxB,aAAS,IAAI,KAAK,MAAM,IAAI;AAAA,EAC9B;AACF;AAEO,SAAS,oBAAoB,MAAsD;AACxF,SAAO,SAAS,IAAI,IAAI;AAC1B;AAEO,SAAS,uBAAqD;AACnE,SAAO,MAAM,KAAK,SAAS,OAAO,CAAC;AACrC;AAsBA,eAAsB,sBACpB,IACA,OAA4B,CAAC,GACS;AACtC,QAAM,cAAc,qBAAqB;AACzC,MAAI,UAAU,CAAC,KAAK,OAAO;AACzB,WAAO,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,YAAY,OAAO;AAAA,EACzE;AAEA,QAAM,KAAK,GAAG,UAAe;AAI7B,QAAM,WAAY,MAAM,GACrB,WAAW,oBAAoB,EAC/B,OAAO,CAAC,MAAM,aAAa,mBAAmB,YAAY,UAAU,aAAa,CAAC,EAClF,MAAM,aAAa,MAAM,IAAI,EAC7B,QAAQ;AAQX,QAAM,OAAO,IAAI,IAAI,SAAS,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC;AAEzD,QAAM,cAAc,CAAC,QAAoC,IAAI,YAAY,IAAI;AAC7E,QAAM,aAAa,CAAC,QAAoC,IAAI,kBAAkB;AAG9E,QAAM,cAAc,CAAC,QAAoC,IAAI,YAAY,eAAe,IAAI,IAAI;AAChG,QAAM,YAAY,CAAC,QAAoC,IAAI,WAAW;AACtE,QAAM,eAAe,CAAC,QAAoC,IAAI,cAAc;AAM5E,QAAM,qBAAqB,YAAY,OAAO,CAAC,QAAQ,IAAI,uBAAuB,IAAI;AAEtF,QAAM,WAAW,mBAAmB,OAAO,CAAC,QAAQ,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC;AACvE,QAAM,WAAW,mBAAmB,OAAO,CAAC,QAAQ;AAClD,UAAM,MAAM,KAAK,IAAI,IAAI,IAAI;AAC7B,QAAI,CAAC,IAAK,QAAO;AACjB,WACE,IAAI,cAAc,YAAY,GAAG,MAChC,IAAI,mBAAmB,UAAU,WAAW,GAAG,MAC/C,IAAI,YAAY,UAAU,YAAY,GAAG,KAC1C,IAAI,WAAW,UAAU,GAAG,KAC5B,IAAI,gBAAgB,aAAa,GAAG;AAAA,EAExC,CAAC;AAED,MAAI,UAAU;AACd,MAAI,UAAU;AACd,MAAI,UAAU;AAEd,MAAI,SAAS,QAAQ;AACnB,UAAM,GACH,WAAW,oBAAoB,EAC/B;AAAA,MACC,SAAS,IAAI,CAAC,SAAS;AAAA,QACrB,IAAI,IAAI;AAAA,QACR,WAAW;AAAA,QACX,WAAW,YAAY,GAAG;AAAA,QAC1B,iBAAiB,WAAW,GAAG;AAAA,QAC/B,UAAU,YAAY,GAAG;AAAA,QACzB,QAAQ,UAAU,GAAG;AAAA,QACrB,aAAa,aAAa,GAAG;AAAA,QAC7B,YAAY;AAAA,QACZ,YAAY;AAAA,MACd,EAAE;AAAA,IACJ,EACC,WAAW,CAAC,OAAY,GAAG,OAAO,IAAI,EAAE,UAAU,CAAC,EACnD,QAAQ;AACX,cAAU,SAAS;AAAA,EACrB;AAGA,aAAW,OAAO,UAAU;AAC1B,UAAM,GACH,YAAY,oBAAoB,EAChC,IAAI;AAAA,MACH,WAAW,YAAY,GAAG;AAAA,MAC1B,iBAAiB,WAAW,GAAG;AAAA,MAC/B,UAAU,YAAY,GAAG;AAAA,MACzB,QAAQ,UAAU,GAAG;AAAA,MACrB,aAAa,aAAa,GAAG;AAAA,MAC7B,YAAY;AAAA,IACd,CAAC,EACA,MAAM,MAAM,KAAK,IAAI,IAAI,EACzB,MAAM,aAAa,MAAM,IAAI,EAC7B,QAAQ;AACX,eAAW;AAAA,EACb;AAWA,MAAI,YAAY,SAAS,GAAG;AAC1B,UAAM,WAAW,IAAI,IAAI,mBAAmB,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC;AAClE,UAAM,WAAW,SAAS,OAAO,CAAC,QAAQ,CAAC,SAAS,IAAI,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,QAAQ,IAAI,EAAE;AACpF,QAAI,SAAS,QAAQ;AACnB,YAAM,GACH,WAAW,oBAAoB,EAC/B,MAAM,aAAa,MAAM,IAAI,EAC7B,MAAM,MAAM,MAAM,QAAQ,EAC1B,QAAQ;AACX,gBAAU,SAAS;AAAA,IACrB;AAAA,EACF;AAEA,WAAS;AACT,SAAO,EAAE,SAAS,SAAS,SAAS,OAAO,YAAY,OAAO;AAChE;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { loadDictionary } from "@open-mercato/shared/lib/i18n/server";
|
|
2
|
+
import { createFallbackTranslator } from "@open-mercato/shared/lib/i18n/translate";
|
|
3
|
+
import { defaultLocale } from "@open-mercato/shared/lib/i18n/config";
|
|
4
|
+
async function resolveNotificationCopy(source, locale = defaultLocale) {
|
|
5
|
+
const dictionary = await loadDictionary(locale);
|
|
6
|
+
const t = createFallbackTranslator(dictionary);
|
|
7
|
+
const title = source.titleKey ? t(source.titleKey, source.title ?? source.titleKey, source.titleVariables ?? void 0) : source.title;
|
|
8
|
+
const body = source.bodyKey ? t(source.bodyKey, source.body ?? source.bodyKey ?? "", source.bodyVariables ?? void 0) : source.body ?? null;
|
|
9
|
+
return { title, body, t };
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
resolveNotificationCopy
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=notificationCopy.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/notifications/lib/notificationCopy.ts"],
|
|
4
|
+
"sourcesContent": ["import { loadDictionary } from '@open-mercato/shared/lib/i18n/server'\nimport { createFallbackTranslator } from '@open-mercato/shared/lib/i18n/translate'\nimport { defaultLocale, type Locale } from '@open-mercato/shared/lib/i18n/config'\n\n/**\n * Localizable notification copy: i18n keys + variables, with already-resolved (or raw) strings as\n * the fallback when a key is absent or has no translation. Structurally satisfied by a `Notification`\n * row (in-app/email delivery) and built ad hoc by the push fan-out (per-device delivery).\n */\nexport type NotificationCopySource = {\n titleKey?: string | null\n bodyKey?: string | null\n titleVariables?: Record<string, string> | null\n bodyVariables?: Record<string, string> | null\n title: string\n body?: string | null\n}\n\n/**\n * Resolve a notification's title/body in a given locale. Dictionaries are memoized by\n * {@link loadDictionary}, so repeated calls per locale are cheap. Also returns the translator so\n * callers can localize sibling strings (email chrome, action labels) without reloading the dictionary.\n */\nexport async function resolveNotificationCopy(source: NotificationCopySource, locale: Locale = defaultLocale) {\n const dictionary = await loadDictionary(locale)\n const t = createFallbackTranslator(dictionary)\n const title = source.titleKey\n ? t(source.titleKey, source.title ?? source.titleKey, source.titleVariables ?? undefined)\n : source.title\n const body = source.bodyKey\n ? t(source.bodyKey, source.body ?? source.bodyKey ?? '', source.bodyVariables ?? undefined)\n : source.body ?? null\n return { title, body, t }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,sBAAsB;AAC/B,SAAS,gCAAgC;AACzC,SAAS,qBAAkC;AAqB3C,eAAsB,wBAAwB,QAAgC,SAAiB,eAAe;AAC5G,QAAM,aAAa,MAAM,eAAe,MAAM;AAC9C,QAAM,IAAI,yBAAyB,UAAU;AAC7C,QAAM,QAAQ,OAAO,WACjB,EAAE,OAAO,UAAU,OAAO,SAAS,OAAO,UAAU,OAAO,kBAAkB,MAAS,IACtF,OAAO;AACX,QAAM,OAAO,OAAO,UAChB,EAAE,OAAO,SAAS,OAAO,QAAQ,OAAO,WAAW,IAAI,OAAO,iBAAiB,MAAS,IACxF,OAAO,QAAQ;AACnB,SAAO,EAAE,OAAO,MAAM,EAAE;AAC1B;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Notification } from "../data/entities.js";
|
|
2
2
|
import { NOTIFICATION_EVENTS } from "./events.js";
|
|
3
3
|
import { assertSafeNotificationHref, sanitizeNotificationActions } from "./safeHref.js";
|
|
4
|
-
function buildNotificationEntity(em, input, recipientUserId, ctx) {
|
|
4
|
+
function buildNotificationEntity(em, input, recipientUserId, ctx, channels = null) {
|
|
5
5
|
const actions = sanitizeNotificationActions(input.actions);
|
|
6
6
|
const linkHref = assertSafeNotificationHref(input.linkHref);
|
|
7
7
|
return em.create(Notification, {
|
|
@@ -26,6 +26,9 @@ function buildNotificationEntity(em, input, recipientUserId, ctx) {
|
|
|
26
26
|
sourceEntityId: input.sourceEntityId,
|
|
27
27
|
linkHref,
|
|
28
28
|
groupKey: input.groupKey,
|
|
29
|
+
data: input.data ?? null,
|
|
30
|
+
pushOptions: input.pushOptions ?? null,
|
|
31
|
+
channels,
|
|
29
32
|
expiresAt: input.expiresAt ? new Date(input.expiresAt) : null,
|
|
30
33
|
tenantId: ctx.tenantId,
|
|
31
34
|
organizationId: ctx.organizationId
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/notifications/lib/notificationFactory.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/core'\nimport { Notification } from '../data/entities'\nimport type { CreateNotificationInput } from '../data/validators'\nimport { NOTIFICATION_EVENTS } from './events'\nimport { assertSafeNotificationHref, sanitizeNotificationActions } from './safeHref'\n\nexport type NotificationContentInput = Omit<CreateNotificationInput, 'recipientUserId'>\n\nexport type NotificationTenantContext = {\n tenantId: string\n organizationId?: string | null\n}\n\nexport function buildNotificationEntity(\n em: EntityManager,\n input: NotificationContentInput,\n recipientUserId: string,\n ctx: NotificationTenantContext\n): Notification {\n const actions = sanitizeNotificationActions(input.actions)\n const linkHref = assertSafeNotificationHref(input.linkHref)\n\n return em.create(Notification, {\n recipientUserId,\n type: input.type,\n // i18n-first: store keys and variables for translation at display time\n titleKey: input.titleKey,\n bodyKey: input.bodyKey,\n titleVariables: input.titleVariables,\n bodyVariables: input.bodyVariables,\n // Fallback text (required for backward compatibility)\n title: input.title || input.titleKey || '',\n body: input.body,\n icon: input.icon,\n severity: input.severity ?? 'info',\n actionData: actions\n ? {\n actions,\n primaryActionId: input.primaryActionId,\n }\n : null,\n sourceModule: input.sourceModule,\n sourceEntityType: input.sourceEntityType,\n sourceEntityId: input.sourceEntityId,\n linkHref,\n groupKey: input.groupKey,\n expiresAt: input.expiresAt ? new Date(input.expiresAt) : null,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n}\n\nexport async function emitNotificationCreated(\n eventBus: { emit: (event: string, payload: unknown) => Promise<void> },\n notification: Notification,\n ctx: NotificationTenantContext\n): Promise<void> {\n await eventBus.emit(NOTIFICATION_EVENTS.CREATED, {\n notificationId: notification.id,\n recipientUserId: notification.recipientUserId,\n type: notification.type,\n title: notification.title,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n}\n\nexport async function emitNotificationCreatedBatch(\n eventBus: { emit: (event: string, payload: unknown) => Promise<void> },\n notifications: Notification[],\n ctx: NotificationTenantContext\n): Promise<void> {\n for (const notification of notifications) {\n await emitNotificationCreated(eventBus, notification, ctx)\n }\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,oBAAoB;AAE7B,SAAS,2BAA2B;AACpC,SAAS,4BAA4B,mCAAmC;AASjE,SAAS,wBACd,IACA,OACA,iBACA,
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/core'\nimport { Notification } from '../data/entities'\nimport type { CreateNotificationInput } from '../data/validators'\nimport { NOTIFICATION_EVENTS } from './events'\nimport { assertSafeNotificationHref, sanitizeNotificationActions } from './safeHref'\n\nexport type NotificationContentInput = Omit<CreateNotificationInput, 'recipientUserId'>\n\nexport type NotificationTenantContext = {\n tenantId: string\n organizationId?: string | null\n}\n\nexport function buildNotificationEntity(\n em: EntityManager,\n input: NotificationContentInput,\n recipientUserId: string,\n ctx: NotificationTenantContext,\n channels: string[] | null = null\n): Notification {\n const actions = sanitizeNotificationActions(input.actions)\n const linkHref = assertSafeNotificationHref(input.linkHref)\n\n return em.create(Notification, {\n recipientUserId,\n type: input.type,\n // i18n-first: store keys and variables for translation at display time\n titleKey: input.titleKey,\n bodyKey: input.bodyKey,\n titleVariables: input.titleVariables,\n bodyVariables: input.bodyVariables,\n // Fallback text (required for backward compatibility)\n title: input.title || input.titleKey || '',\n body: input.body,\n icon: input.icon,\n severity: input.severity ?? 'info',\n actionData: actions\n ? {\n actions,\n primaryActionId: input.primaryActionId,\n }\n : null,\n sourceModule: input.sourceModule,\n sourceEntityType: input.sourceEntityType,\n sourceEntityId: input.sourceEntityId,\n linkHref,\n groupKey: input.groupKey,\n data: input.data ?? null,\n pushOptions: input.pushOptions ?? null,\n channels,\n expiresAt: input.expiresAt ? new Date(input.expiresAt) : null,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n}\n\nexport async function emitNotificationCreated(\n eventBus: { emit: (event: string, payload: unknown) => Promise<void> },\n notification: Notification,\n ctx: NotificationTenantContext\n): Promise<void> {\n await eventBus.emit(NOTIFICATION_EVENTS.CREATED, {\n notificationId: notification.id,\n recipientUserId: notification.recipientUserId,\n type: notification.type,\n title: notification.title,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n}\n\nexport async function emitNotificationCreatedBatch(\n eventBus: { emit: (event: string, payload: unknown) => Promise<void> },\n notifications: Notification[],\n ctx: NotificationTenantContext\n): Promise<void> {\n for (const notification of notifications) {\n await emitNotificationCreated(eventBus, notification, ctx)\n }\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,oBAAoB;AAE7B,SAAS,2BAA2B;AACpC,SAAS,4BAA4B,mCAAmC;AASjE,SAAS,wBACd,IACA,OACA,iBACA,KACA,WAA4B,MACd;AACd,QAAM,UAAU,4BAA4B,MAAM,OAAO;AACzD,QAAM,WAAW,2BAA2B,MAAM,QAAQ;AAE1D,SAAO,GAAG,OAAO,cAAc;AAAA,IAC7B;AAAA,IACA,MAAM,MAAM;AAAA;AAAA,IAEZ,UAAU,MAAM;AAAA,IAChB,SAAS,MAAM;AAAA,IACf,gBAAgB,MAAM;AAAA,IACtB,eAAe,MAAM;AAAA;AAAA,IAErB,OAAO,MAAM,SAAS,MAAM,YAAY;AAAA,IACxC,MAAM,MAAM;AAAA,IACZ,MAAM,MAAM;AAAA,IACZ,UAAU,MAAM,YAAY;AAAA,IAC5B,YAAY,UACR;AAAA,MACE;AAAA,MACA,iBAAiB,MAAM;AAAA,IACzB,IACA;AAAA,IACJ,cAAc,MAAM;AAAA,IACpB,kBAAkB,MAAM;AAAA,IACxB,gBAAgB,MAAM;AAAA,IACtB;AAAA,IACA,UAAU,MAAM;AAAA,IAChB,MAAM,MAAM,QAAQ;AAAA,IACpB,aAAa,MAAM,eAAe;AAAA,IAClC;AAAA,IACA,WAAW,MAAM,YAAY,IAAI,KAAK,MAAM,SAAS,IAAI;AAAA,IACzD,UAAU,IAAI;AAAA,IACd,gBAAgB,IAAI;AAAA,EACtB,CAAC;AACH;AAEA,eAAsB,wBACpB,UACA,cACA,KACe;AACf,QAAM,SAAS,KAAK,oBAAoB,SAAS;AAAA,IAC/C,gBAAgB,aAAa;AAAA,IAC7B,iBAAiB,aAAa;AAAA,IAC9B,MAAM,aAAa;AAAA,IACnB,OAAO,aAAa;AAAA,IACpB,UAAU,IAAI;AAAA,IACd,gBAAgB,IAAI;AAAA,EACtB,CAAC;AACH;AAEA,eAAsB,6BACpB,UACA,eACA,KACe;AACf,aAAW,gBAAgB,eAAe;AACxC,UAAM,wBAAwB,UAAU,cAAc,GAAG;AAAA,EAC3D;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -31,6 +31,8 @@ function toNotificationDto(notification) {
|
|
|
31
31
|
sourceEntityType: notification.sourceEntityType,
|
|
32
32
|
sourceEntityId: notification.sourceEntityId,
|
|
33
33
|
linkHref: notification.linkHref,
|
|
34
|
+
data: notification.data ?? null,
|
|
35
|
+
channels: notification.channels ?? null,
|
|
34
36
|
createdAt: createdAt.toISOString(),
|
|
35
37
|
readAt: notification.readAt?.toISOString() ?? null,
|
|
36
38
|
actionTaken: notification.actionTaken
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/notifications/lib/notificationMapper.ts"],
|
|
4
|
-
"sourcesContent": ["import type { NotificationDto } from '@open-mercato/shared/modules/notifications/types'\nimport { Notification } from '../data/entities'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('notifications').child({ component: 'mapper' })\n\nexport function toNotificationDto(notification: Notification): NotificationDto {\n const createdAt = notification.createdAt instanceof Date\n ? notification.createdAt\n : (() => {\n if (process.env.NODE_ENV !== 'test') {\n logger.warn('Invalid createdAt on notification entity, falling back to current time', { id: notification.id, createdAt: notification.createdAt })\n }\n return new Date()\n })()\n return {\n id: notification.id,\n type: notification.type,\n title: notification.title,\n body: notification.body,\n titleKey: notification.titleKey,\n bodyKey: notification.bodyKey,\n titleVariables: notification.titleVariables,\n bodyVariables: notification.bodyVariables,\n icon: notification.icon,\n severity: notification.severity,\n status: notification.status,\n actions: notification.actionData?.actions?.map((action) => ({\n id: action.id,\n label: action.label,\n labelKey: action.labelKey,\n variant: action.variant,\n icon: action.icon,\n })) ?? [],\n primaryActionId: notification.actionData?.primaryActionId,\n sourceModule: notification.sourceModule,\n sourceEntityType: notification.sourceEntityType,\n sourceEntityId: notification.sourceEntityId,\n linkHref: notification.linkHref,\n createdAt: createdAt.toISOString(),\n readAt: notification.readAt?.toISOString() ?? null,\n actionTaken: notification.actionTaken,\n }\n}\n"],
|
|
5
|
-
"mappings": "AAEA,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,eAAe,EAAE,MAAM,EAAE,WAAW,SAAS,CAAC;AAEnE,SAAS,kBAAkB,cAA6C;AAC7E,QAAM,YAAY,aAAa,qBAAqB,OAChD,aAAa,aACZ,MAAM;AACP,QAAI,QAAQ,IAAI,aAAa,QAAQ;AACnC,aAAO,KAAK,0EAA0E,EAAE,IAAI,aAAa,IAAI,WAAW,aAAa,UAAU,CAAC;AAAA,IAClJ;AACA,WAAO,oBAAI,KAAK;AAAA,EAClB,GAAG;AACL,SAAO;AAAA,IACL,IAAI,aAAa;AAAA,IACjB,MAAM,aAAa;AAAA,IACnB,OAAO,aAAa;AAAA,IACpB,MAAM,aAAa;AAAA,IACnB,UAAU,aAAa;AAAA,IACvB,SAAS,aAAa;AAAA,IACtB,gBAAgB,aAAa;AAAA,IAC7B,eAAe,aAAa;AAAA,IAC5B,MAAM,aAAa;AAAA,IACnB,UAAU,aAAa;AAAA,IACvB,QAAQ,aAAa;AAAA,IACrB,SAAS,aAAa,YAAY,SAAS,IAAI,CAAC,YAAY;AAAA,MAC1D,IAAI,OAAO;AAAA,MACX,OAAO,OAAO;AAAA,MACd,UAAU,OAAO;AAAA,MACjB,SAAS,OAAO;AAAA,MAChB,MAAM,OAAO;AAAA,IACf,EAAE,KAAK,CAAC;AAAA,IACR,iBAAiB,aAAa,YAAY;AAAA,IAC1C,cAAc,aAAa;AAAA,IAC3B,kBAAkB,aAAa;AAAA,IAC/B,gBAAgB,aAAa;AAAA,IAC7B,UAAU,aAAa;AAAA,IACvB,WAAW,UAAU,YAAY;AAAA,IACjC,QAAQ,aAAa,QAAQ,YAAY,KAAK;AAAA,IAC9C,aAAa,aAAa;AAAA,EAC5B;AACF;",
|
|
4
|
+
"sourcesContent": ["import type { NotificationDto } from '@open-mercato/shared/modules/notifications/types'\nimport { Notification } from '../data/entities'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('notifications').child({ component: 'mapper' })\n\nexport function toNotificationDto(notification: Notification): NotificationDto {\n const createdAt = notification.createdAt instanceof Date\n ? notification.createdAt\n : (() => {\n if (process.env.NODE_ENV !== 'test') {\n logger.warn('Invalid createdAt on notification entity, falling back to current time', { id: notification.id, createdAt: notification.createdAt })\n }\n return new Date()\n })()\n return {\n id: notification.id,\n type: notification.type,\n title: notification.title,\n body: notification.body,\n titleKey: notification.titleKey,\n bodyKey: notification.bodyKey,\n titleVariables: notification.titleVariables,\n bodyVariables: notification.bodyVariables,\n icon: notification.icon,\n severity: notification.severity,\n status: notification.status,\n actions: notification.actionData?.actions?.map((action) => ({\n id: action.id,\n label: action.label,\n labelKey: action.labelKey,\n variant: action.variant,\n icon: action.icon,\n })) ?? [],\n primaryActionId: notification.actionData?.primaryActionId,\n sourceModule: notification.sourceModule,\n sourceEntityType: notification.sourceEntityType,\n sourceEntityId: notification.sourceEntityId,\n linkHref: notification.linkHref,\n data: notification.data ?? null,\n channels: notification.channels ?? null,\n createdAt: createdAt.toISOString(),\n readAt: notification.readAt?.toISOString() ?? null,\n actionTaken: notification.actionTaken,\n }\n}\n"],
|
|
5
|
+
"mappings": "AAEA,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,eAAe,EAAE,MAAM,EAAE,WAAW,SAAS,CAAC;AAEnE,SAAS,kBAAkB,cAA6C;AAC7E,QAAM,YAAY,aAAa,qBAAqB,OAChD,aAAa,aACZ,MAAM;AACP,QAAI,QAAQ,IAAI,aAAa,QAAQ;AACnC,aAAO,KAAK,0EAA0E,EAAE,IAAI,aAAa,IAAI,WAAW,aAAa,UAAU,CAAC;AAAA,IAClJ;AACA,WAAO,oBAAI,KAAK;AAAA,EAClB,GAAG;AACL,SAAO;AAAA,IACL,IAAI,aAAa;AAAA,IACjB,MAAM,aAAa;AAAA,IACnB,OAAO,aAAa;AAAA,IACpB,MAAM,aAAa;AAAA,IACnB,UAAU,aAAa;AAAA,IACvB,SAAS,aAAa;AAAA,IACtB,gBAAgB,aAAa;AAAA,IAC7B,eAAe,aAAa;AAAA,IAC5B,MAAM,aAAa;AAAA,IACnB,UAAU,aAAa;AAAA,IACvB,QAAQ,aAAa;AAAA,IACrB,SAAS,aAAa,YAAY,SAAS,IAAI,CAAC,YAAY;AAAA,MAC1D,IAAI,OAAO;AAAA,MACX,OAAO,OAAO;AAAA,MACd,UAAU,OAAO;AAAA,MACjB,SAAS,OAAO;AAAA,MAChB,MAAM,OAAO;AAAA,IACf,EAAE,KAAK,CAAC;AAAA,IACR,iBAAiB,aAAa,YAAY;AAAA,IAC1C,cAAc,aAAa;AAAA,IAC3B,kBAAkB,aAAa;AAAA,IAC/B,gBAAgB,aAAa;AAAA,IAC7B,UAAU,aAAa;AAAA,IACvB,MAAM,aAAa,QAAQ;AAAA,IAC3B,UAAU,aAAa,YAAY;AAAA,IACnC,WAAW,UAAU,YAAY;AAAA,IACjC,QAAQ,aAAa,QAAQ,YAAY,KAAK;AAAA,IAC9C,aAAa,aAAa;AAAA,EAC5B;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
|
|
2
|
+
import { NotificationPreference } from "../data/entities.js";
|
|
3
|
+
import { getNotificationType } from "./notification-type-registry.js";
|
|
4
|
+
import { getNotificationTypeOverrides } from "./typeOverrides.js";
|
|
5
|
+
function createNotificationPreferenceService(deps) {
|
|
6
|
+
const { em: rootEm } = deps;
|
|
7
|
+
return {
|
|
8
|
+
async isChannelEnabled(scope, typeId, channel) {
|
|
9
|
+
const row = await rootEm.findOne(NotificationPreference, {
|
|
10
|
+
tenantId: scope.tenantId,
|
|
11
|
+
userId: scope.userId,
|
|
12
|
+
notificationTypeId: typeId,
|
|
13
|
+
channel
|
|
14
|
+
});
|
|
15
|
+
return row ? row.enabled : true;
|
|
16
|
+
},
|
|
17
|
+
async listForUser(scope) {
|
|
18
|
+
return rootEm.find(
|
|
19
|
+
NotificationPreference,
|
|
20
|
+
{ tenantId: scope.tenantId, userId: scope.userId },
|
|
21
|
+
{ orderBy: { notificationTypeId: "asc", channel: "asc" } }
|
|
22
|
+
);
|
|
23
|
+
},
|
|
24
|
+
async setPreferences(scope, items) {
|
|
25
|
+
const storedOverrides = items.length ? await getNotificationTypeOverrides(rootEm.fork(), scope.tenantId, items.map((item) => item.typeId)) : /* @__PURE__ */ new Map();
|
|
26
|
+
const optOutFiltered = items.filter((item) => {
|
|
27
|
+
if (item.enabled === true) return true;
|
|
28
|
+
const nonOptOut = storedOverrides.get(item.typeId)?.nonOptOut ?? getNotificationType(item.typeId)?.nonOptOut;
|
|
29
|
+
return nonOptOut !== true;
|
|
30
|
+
});
|
|
31
|
+
const writable = optOutFiltered.filter((item) => {
|
|
32
|
+
const eligible = storedOverrides.get(item.typeId)?.channels ?? getNotificationType(item.typeId)?.channels;
|
|
33
|
+
return !eligible || eligible.includes(item.channel);
|
|
34
|
+
});
|
|
35
|
+
if (writable.length === 0) return 0;
|
|
36
|
+
const em = rootEm.fork();
|
|
37
|
+
const existing = await em.find(NotificationPreference, {
|
|
38
|
+
tenantId: scope.tenantId,
|
|
39
|
+
userId: scope.userId
|
|
40
|
+
});
|
|
41
|
+
const byKey = new Map(
|
|
42
|
+
existing.map((row) => [`${row.notificationTypeId}::${row.channel}`, row])
|
|
43
|
+
);
|
|
44
|
+
let changed = 0;
|
|
45
|
+
await withAtomicFlush(
|
|
46
|
+
em,
|
|
47
|
+
[
|
|
48
|
+
() => {
|
|
49
|
+
for (const item of writable) {
|
|
50
|
+
const row = byKey.get(`${item.typeId}::${item.channel}`);
|
|
51
|
+
if (row) {
|
|
52
|
+
if (row.enabled === item.enabled) continue;
|
|
53
|
+
row.enabled = item.enabled;
|
|
54
|
+
changed += 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const next = em.create(NotificationPreference, {
|
|
58
|
+
tenantId: scope.tenantId,
|
|
59
|
+
userId: scope.userId,
|
|
60
|
+
notificationTypeId: item.typeId,
|
|
61
|
+
channel: item.channel,
|
|
62
|
+
enabled: item.enabled
|
|
63
|
+
});
|
|
64
|
+
em.persist(next);
|
|
65
|
+
changed += 1;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
{ transaction: true, label: "notifications.setPreferences" }
|
|
70
|
+
);
|
|
71
|
+
return changed;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function resolveNotificationPreferenceService(container) {
|
|
76
|
+
const em = container.resolve("em");
|
|
77
|
+
return createNotificationPreferenceService({ em });
|
|
78
|
+
}
|
|
79
|
+
export {
|
|
80
|
+
createNotificationPreferenceService,
|
|
81
|
+
resolveNotificationPreferenceService
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=notificationPreferenceService.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/notifications/lib/notificationPreferenceService.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { NotificationPreference } from '../data/entities'\nimport { getNotificationType } from './notification-type-registry'\nimport { getNotificationTypeOverrides } from './typeOverrides'\n\n/**\n * Tenant + user scope for preference operations. Tenant scoping is mandatory,\n * so it is part of every call (the spec's bare `userId` signatures are widened\n * here to stay tenant-safe).\n */\nexport interface NotificationPreferenceScope {\n tenantId: string\n userId: string\n}\n\nexport interface NotificationPreferenceInput {\n typeId: string\n channel: string\n enabled: boolean\n}\n\nexport interface NotificationPreferenceService {\n /**\n * Whether a channel is enabled for a user + type. Defaults to `true` when no\n * row exists (lazy-seed, default-on) \u2014 does not write.\n */\n isChannelEnabled(scope: NotificationPreferenceScope, typeId: string, channel: string): Promise<boolean>\n /**\n * Bulk find-or-upsert of preference rows for the scoped user. Returns the number of rows actually\n * changed (created, or whose `enabled` flipped) so callers can skip emitting events on no-op writes.\n */\n setPreferences(scope: NotificationPreferenceScope, items: NotificationPreferenceInput[]): Promise<number>\n /** All stored preference rows for the scoped user (absence \u21D2 enabled). */\n listForUser(scope: NotificationPreferenceScope): Promise<NotificationPreference[]>\n}\n\nexport interface NotificationPreferenceServiceDeps {\n em: EntityManager\n}\n\nexport function createNotificationPreferenceService(\n deps: NotificationPreferenceServiceDeps,\n): NotificationPreferenceService {\n const { em: rootEm } = deps\n\n return {\n async isChannelEnabled(scope, typeId, channel) {\n const row = await rootEm.findOne(NotificationPreference, {\n tenantId: scope.tenantId,\n userId: scope.userId,\n notificationTypeId: typeId,\n channel,\n })\n return row ? row.enabled : true\n },\n\n async listForUser(scope) {\n return rootEm.find(\n NotificationPreference,\n { tenantId: scope.tenantId, userId: scope.userId },\n { orderBy: { notificationTypeId: 'asc', channel: 'asc' } },\n )\n },\n\n async setPreferences(scope, items) {\n const storedOverrides = items.length\n ? await getNotificationTypeOverrides(rootEm.fork(), scope.tenantId, items.map((item) => item.typeId))\n : new Map()\n // Effectively-nonOptOut types (operator override ?? code flag) ignore stored preferences at\n // delivery time; refuse to persist an opt-out row for them so the stored state can never\n // contradict enforcement. An `enabled: true` write matches the forced-on state and is\n // allowed through (a preferences UI can still confirm it).\n const optOutFiltered = items.filter((item) => {\n if (item.enabled === true) return true\n const nonOptOut =\n storedOverrides.get(item.typeId)?.nonOptOut ?? getNotificationType(item.typeId)?.nonOptOut\n return nonOptOut !== true\n })\n // Channels outside the type's effective eligibility (operator override on\n // `notification_types.channels`, else the code-declared `type.channels`) are locked:\n // delivery rejects them before preferences and the UI renders the cell off, so a stored\n // row would only lie. Drop those writes server-side (the UI lock is not a guarantee).\n const writable = optOutFiltered.filter((item) => {\n const eligible = storedOverrides.get(item.typeId)?.channels ?? getNotificationType(item.typeId)?.channels\n return !eligible || eligible.includes(item.channel)\n })\n if (writable.length === 0) return 0\n const em = rootEm.fork()\n const existing = await em.find(NotificationPreference, {\n tenantId: scope.tenantId,\n userId: scope.userId,\n })\n const byKey = new Map(\n existing.map((row) => [`${row.notificationTypeId}::${row.channel}`, row]),\n )\n\n let changed = 0\n await withAtomicFlush(\n em,\n [\n () => {\n for (const item of writable) {\n const row = byKey.get(`${item.typeId}::${item.channel}`)\n if (row) {\n if (row.enabled === item.enabled) continue\n row.enabled = item.enabled\n changed += 1\n continue\n }\n const next = em.create(NotificationPreference, {\n tenantId: scope.tenantId,\n userId: scope.userId,\n notificationTypeId: item.typeId,\n channel: item.channel,\n enabled: item.enabled,\n })\n em.persist(next)\n changed += 1\n }\n },\n ],\n { transaction: true, label: 'notifications.setPreferences' },\n )\n return changed\n },\n }\n}\n\nexport function resolveNotificationPreferenceService(container: {\n resolve: (name: string) => unknown\n}): NotificationPreferenceService {\n // Construct from the request-scoped `em`, mirroring `resolveNotificationService`. (The request\n // container does not expose the module's scoped service bindings, so resolving `em` directly is the\n // module convention \u2014 see notificationService.ts.)\n const em = container.resolve('em') as EntityManager\n return createNotificationPreferenceService({ em })\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,uBAAuB;AAChC,SAAS,8BAA8B;AACvC,SAAS,2BAA2B;AACpC,SAAS,oCAAoC;AAqCtC,SAAS,oCACd,MAC+B;AAC/B,QAAM,EAAE,IAAI,OAAO,IAAI;AAEvB,SAAO;AAAA,IACL,MAAM,iBAAiB,OAAO,QAAQ,SAAS;AAC7C,YAAM,MAAM,MAAM,OAAO,QAAQ,wBAAwB;AAAA,QACvD,UAAU,MAAM;AAAA,QAChB,QAAQ,MAAM;AAAA,QACd,oBAAoB;AAAA,QACpB;AAAA,MACF,CAAC;AACD,aAAO,MAAM,IAAI,UAAU;AAAA,IAC7B;AAAA,IAEA,MAAM,YAAY,OAAO;AACvB,aAAO,OAAO;AAAA,QACZ;AAAA,QACA,EAAE,UAAU,MAAM,UAAU,QAAQ,MAAM,OAAO;AAAA,QACjD,EAAE,SAAS,EAAE,oBAAoB,OAAO,SAAS,MAAM,EAAE;AAAA,MAC3D;AAAA,IACF;AAAA,IAEA,MAAM,eAAe,OAAO,OAAO;AACjC,YAAM,kBAAkB,MAAM,SAC1B,MAAM,6BAA6B,OAAO,KAAK,GAAG,MAAM,UAAU,MAAM,IAAI,CAAC,SAAS,KAAK,MAAM,CAAC,IAClG,oBAAI,IAAI;AAKZ,YAAM,iBAAiB,MAAM,OAAO,CAAC,SAAS;AAC5C,YAAI,KAAK,YAAY,KAAM,QAAO;AAClC,cAAM,YACJ,gBAAgB,IAAI,KAAK,MAAM,GAAG,aAAa,oBAAoB,KAAK,MAAM,GAAG;AACnF,eAAO,cAAc;AAAA,MACvB,CAAC;AAKD,YAAM,WAAW,eAAe,OAAO,CAAC,SAAS;AAC/C,cAAM,WAAW,gBAAgB,IAAI,KAAK,MAAM,GAAG,YAAY,oBAAoB,KAAK,MAAM,GAAG;AACjG,eAAO,CAAC,YAAY,SAAS,SAAS,KAAK,OAAO;AAAA,MACpD,CAAC;AACD,UAAI,SAAS,WAAW,EAAG,QAAO;AAClC,YAAM,KAAK,OAAO,KAAK;AACvB,YAAM,WAAW,MAAM,GAAG,KAAK,wBAAwB;AAAA,QACrD,UAAU,MAAM;AAAA,QAChB,QAAQ,MAAM;AAAA,MAChB,CAAC;AACD,YAAM,QAAQ,IAAI;AAAA,QAChB,SAAS,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,kBAAkB,KAAK,IAAI,OAAO,IAAI,GAAG,CAAC;AAAA,MAC1E;AAEA,UAAI,UAAU;AACd,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,UACE,MAAM;AACJ,uBAAW,QAAQ,UAAU;AAC3B,oBAAM,MAAM,MAAM,IAAI,GAAG,KAAK,MAAM,KAAK,KAAK,OAAO,EAAE;AACvD,kBAAI,KAAK;AACP,oBAAI,IAAI,YAAY,KAAK,QAAS;AAClC,oBAAI,UAAU,KAAK;AACnB,2BAAW;AACX;AAAA,cACF;AACA,oBAAM,OAAO,GAAG,OAAO,wBAAwB;AAAA,gBAC7C,UAAU,MAAM;AAAA,gBAChB,QAAQ,MAAM;AAAA,gBACd,oBAAoB,KAAK;AAAA,gBACzB,SAAS,KAAK;AAAA,gBACd,SAAS,KAAK;AAAA,cAChB,CAAC;AACD,iBAAG,QAAQ,IAAI;AACf,yBAAW;AAAA,YACb;AAAA,UACF;AAAA,QACF;AAAA,QACA,EAAE,aAAa,MAAM,OAAO,+BAA+B;AAAA,MAC7D;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEO,SAAS,qCAAqC,WAEnB;AAIhC,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,SAAO,oCAAoC,EAAE,GAAG,CAAC;AACnD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -18,6 +18,14 @@ import {
|
|
|
18
18
|
} from "./notificationRecipients.js";
|
|
19
19
|
import { assertSafeNotificationHref, sanitizeNotificationActions } from "./safeHref.js";
|
|
20
20
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
21
|
+
import { getNotificationType } from "./notification-type-registry.js";
|
|
22
|
+
import { getNotificationTypeOverrides } from "./typeOverrides.js";
|
|
23
|
+
import { getNotificationDeliveryStrategies } from "./deliveryStrategies.js";
|
|
24
|
+
import { resolveEffectiveChannels } from "./shouldDeliver.js";
|
|
25
|
+
import {
|
|
26
|
+
createNotificationPreferenceService
|
|
27
|
+
} from "./notificationPreferenceService.js";
|
|
28
|
+
import { inAppVisibleFilter, inAppVisibleSql, isInAppVisible } from "./notificationVisibility.js";
|
|
21
29
|
const logger = createLogger("notifications").child({ component: "service" });
|
|
22
30
|
function debug(message, ...details) {
|
|
23
31
|
logger.debug(message, details.length ? { details } : void 0);
|
|
@@ -55,7 +63,7 @@ async function assertNotificationRecipientsInScope(em, recipientUserIds, ctx) {
|
|
|
55
63
|
throw new CrudHttpError(404, { error: "Notification recipient not found" });
|
|
56
64
|
}
|
|
57
65
|
}
|
|
58
|
-
function applyNotificationContent(notification, input, recipientUserId, ctx) {
|
|
66
|
+
function applyNotificationContent(notification, input, recipientUserId, ctx, channels) {
|
|
59
67
|
const actions = sanitizeNotificationActions(input.actions);
|
|
60
68
|
const linkHref = assertSafeNotificationHref(input.linkHref);
|
|
61
69
|
notification.recipientUserId = recipientUserId;
|
|
@@ -77,6 +85,7 @@ function applyNotificationContent(notification, input, recipientUserId, ctx) {
|
|
|
77
85
|
notification.sourceEntityId = input.sourceEntityId;
|
|
78
86
|
notification.linkHref = linkHref;
|
|
79
87
|
notification.groupKey = input.groupKey;
|
|
88
|
+
notification.channels = channels;
|
|
80
89
|
notification.expiresAt = input.expiresAt ? new Date(input.expiresAt) : null;
|
|
81
90
|
notification.tenantId = ctx.tenantId;
|
|
82
91
|
notification.organizationId = normalizeOrgScope(ctx.organizationId);
|
|
@@ -108,14 +117,17 @@ async function findScopedNotificationOrThrow(em, notificationId, ctx) {
|
|
|
108
117
|
}
|
|
109
118
|
return notification;
|
|
110
119
|
}
|
|
111
|
-
async function emitNotificationSseEvents(eventBus, notifications, ctx
|
|
120
|
+
async function emitNotificationSseEvents(eventBus, notifications, ctx) {
|
|
121
|
+
const visible = notifications.filter((notification) => isInAppVisible(notification.channels));
|
|
122
|
+
if (visible.length === 0) return;
|
|
123
|
+
const visibleRecipientUserIds = Array.from(new Set(visible.map((n) => n.recipientUserId)));
|
|
112
124
|
await eventBus.emit(NOTIFICATION_SSE_EVENTS.BATCH_CREATED, {
|
|
113
125
|
tenantId: ctx.tenantId,
|
|
114
126
|
organizationId: normalizeOrgScope(ctx.organizationId),
|
|
115
|
-
recipientUserIds,
|
|
116
|
-
count:
|
|
127
|
+
recipientUserIds: visibleRecipientUserIds,
|
|
128
|
+
count: visible.length
|
|
117
129
|
});
|
|
118
|
-
for (const notification of
|
|
130
|
+
for (const notification of visible) {
|
|
119
131
|
await eventBus.emit(NOTIFICATION_SSE_EVENTS.CREATED, {
|
|
120
132
|
tenantId: notification.tenantId,
|
|
121
133
|
organizationId: notification.organizationId ?? null,
|
|
@@ -124,7 +136,7 @@ async function emitNotificationSseEvents(eventBus, notifications, ctx, recipient
|
|
|
124
136
|
});
|
|
125
137
|
}
|
|
126
138
|
}
|
|
127
|
-
async function createOrRefreshNotification(em, input, recipientUserId, ctx) {
|
|
139
|
+
async function createOrRefreshNotification(em, input, recipientUserId, ctx, channels) {
|
|
128
140
|
if (input.groupKey && input.groupKey.trim().length > 0) {
|
|
129
141
|
const orgScope = normalizeOrgScope(ctx.organizationId) ?? "global";
|
|
130
142
|
const lockKey = `notifications:${ctx.tenantId}:${orgScope}:${recipientUserId}:${input.type}:${input.groupKey}`;
|
|
@@ -144,50 +156,82 @@ async function createOrRefreshNotification(em, input, recipientUserId, ctx) {
|
|
|
144
156
|
orderBy: { createdAt: "desc" }
|
|
145
157
|
});
|
|
146
158
|
if (existing) {
|
|
147
|
-
applyNotificationContent(existing, input, recipientUserId, ctx);
|
|
159
|
+
applyNotificationContent(existing, input, recipientUserId, ctx, channels);
|
|
148
160
|
return existing;
|
|
149
161
|
}
|
|
150
162
|
}
|
|
151
|
-
return buildNotificationEntity(em, input, recipientUserId, ctx);
|
|
163
|
+
return buildNotificationEntity(em, input, recipientUserId, ctx, channels);
|
|
152
164
|
}
|
|
153
165
|
function createNotificationService(deps) {
|
|
154
166
|
const { em: rootEm, eventBus, commandBus, container } = deps;
|
|
167
|
+
const resolveChannelsFor = async (content, recipientUserId, scopeCtx, preferences = createNotificationPreferenceService({ em: rootEm.fork() }), typeOverrides) => {
|
|
168
|
+
const registeredChannels = getNotificationDeliveryStrategies().map((strategy) => strategy.id);
|
|
169
|
+
if (registeredChannels.length === 0) return null;
|
|
170
|
+
const targetChannels = content.channels && content.channels.length > 0 ? content.channels : null;
|
|
171
|
+
const overrides = typeOverrides === void 0 ? (await getNotificationTypeOverrides(rootEm.fork(), scopeCtx.tenantId, [content.type])).get(content.type) ?? null : typeOverrides;
|
|
172
|
+
return resolveEffectiveChannels({
|
|
173
|
+
typeId: content.type,
|
|
174
|
+
type: getNotificationType(content.type),
|
|
175
|
+
scope: { tenantId: scopeCtx.tenantId, userId: recipientUserId },
|
|
176
|
+
targetChannels,
|
|
177
|
+
registeredChannels,
|
|
178
|
+
preferences,
|
|
179
|
+
channelsOverride: overrides?.channels ?? null,
|
|
180
|
+
nonOptOutOverride: overrides?.nonOptOut ?? null
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
const resolveChannelsForRecipients = async (content, recipientUserIds, scopeCtx) => {
|
|
184
|
+
const preferences = createNotificationPreferenceService({ em: rootEm.fork() });
|
|
185
|
+
const typeOverrides = (await getNotificationTypeOverrides(rootEm.fork(), scopeCtx.tenantId, [content.type])).get(content.type) ?? null;
|
|
186
|
+
const resolved = [];
|
|
187
|
+
for (const recipientUserId of recipientUserIds) {
|
|
188
|
+
resolved.push({
|
|
189
|
+
recipientUserId,
|
|
190
|
+
channels: await resolveChannelsFor(content, recipientUserId, scopeCtx, preferences, typeOverrides)
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return resolved;
|
|
194
|
+
};
|
|
155
195
|
return {
|
|
156
196
|
async create(input, ctx) {
|
|
157
197
|
const { recipientUserId, ...content } = input;
|
|
198
|
+
const channels = await resolveChannelsFor(content, recipientUserId, ctx);
|
|
158
199
|
const writeEm = rootEm.fork();
|
|
159
200
|
const notification = await writeEm.transactional(async (tx) => {
|
|
160
201
|
await assertNotificationRecipientsInScope(tx, [recipientUserId], ctx);
|
|
161
|
-
const entity = await createOrRefreshNotification(tx, content, recipientUserId, ctx);
|
|
202
|
+
const entity = await createOrRefreshNotification(tx, content, recipientUserId, ctx, channels);
|
|
162
203
|
await tx.flush();
|
|
163
204
|
return entity;
|
|
164
205
|
});
|
|
165
206
|
await invalidateNotificationCache(container, ctx, "created");
|
|
166
207
|
await emitNotificationCreated(eventBus, notification, ctx);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
208
|
+
if (isInAppVisible(notification.channels)) {
|
|
209
|
+
await eventBus.emit(NOTIFICATION_SSE_EVENTS.CREATED, {
|
|
210
|
+
tenantId: notification.tenantId,
|
|
211
|
+
organizationId: notification.organizationId ?? null,
|
|
212
|
+
recipientUserId: notification.recipientUserId,
|
|
213
|
+
notification: toNotificationDto(notification)
|
|
214
|
+
});
|
|
215
|
+
}
|
|
173
216
|
return notification;
|
|
174
217
|
},
|
|
175
218
|
async createBatch(input, ctx) {
|
|
176
219
|
const recipientUserIds = Array.from(new Set(input.recipientUserIds));
|
|
177
220
|
const { recipientUserIds: _recipientUserIds, ...content } = input;
|
|
178
221
|
const notifications = [];
|
|
222
|
+
const resolved = await resolveChannelsForRecipients(content, recipientUserIds, ctx);
|
|
179
223
|
const writeEm = rootEm.fork();
|
|
180
224
|
await writeEm.transactional(async (tx) => {
|
|
181
225
|
await assertNotificationRecipientsInScope(tx, recipientUserIds, ctx);
|
|
182
|
-
for (const recipientUserId of
|
|
183
|
-
const notification = await createOrRefreshNotification(tx, content, recipientUserId, ctx);
|
|
226
|
+
for (const { recipientUserId, channels } of resolved) {
|
|
227
|
+
const notification = await createOrRefreshNotification(tx, content, recipientUserId, ctx, channels);
|
|
184
228
|
notifications.push(notification);
|
|
185
229
|
}
|
|
186
230
|
await tx.flush();
|
|
187
231
|
});
|
|
188
232
|
await invalidateNotificationCache(container, ctx, "created");
|
|
189
233
|
await emitNotificationCreatedBatch(eventBus, notifications, ctx);
|
|
190
|
-
await emitNotificationSseEvents(eventBus, notifications, ctx
|
|
234
|
+
await emitNotificationSseEvents(eventBus, notifications, ctx);
|
|
191
235
|
return notifications;
|
|
192
236
|
},
|
|
193
237
|
async createForRole(input, ctx) {
|
|
@@ -200,17 +244,18 @@ function createNotificationService(deps) {
|
|
|
200
244
|
const { roleId: _roleId, ...content } = input;
|
|
201
245
|
const notifications = [];
|
|
202
246
|
const uniqueRecipientUserIds = Array.from(new Set(recipientUserIds));
|
|
247
|
+
const resolved = await resolveChannelsForRecipients(content, uniqueRecipientUserIds, ctx);
|
|
203
248
|
const writeEm = rootEm.fork();
|
|
204
249
|
await writeEm.transactional(async (tx) => {
|
|
205
|
-
for (const recipientUserId of
|
|
206
|
-
const notification = await createOrRefreshNotification(tx, content, recipientUserId, ctx);
|
|
250
|
+
for (const { recipientUserId, channels } of resolved) {
|
|
251
|
+
const notification = await createOrRefreshNotification(tx, content, recipientUserId, ctx, channels);
|
|
207
252
|
notifications.push(notification);
|
|
208
253
|
}
|
|
209
254
|
await tx.flush();
|
|
210
255
|
});
|
|
211
256
|
await invalidateNotificationCache(container, ctx, "created");
|
|
212
257
|
await emitNotificationCreatedBatch(eventBus, notifications, ctx);
|
|
213
|
-
await emitNotificationSseEvents(eventBus, notifications, ctx
|
|
258
|
+
await emitNotificationSseEvents(eventBus, notifications, ctx);
|
|
214
259
|
return notifications;
|
|
215
260
|
},
|
|
216
261
|
async createForFeature(input, ctx) {
|
|
@@ -289,17 +334,18 @@ function createNotificationService(deps) {
|
|
|
289
334
|
} = input;
|
|
290
335
|
const notifications = [];
|
|
291
336
|
const uniqueRecipientUserIds = Array.from(new Set(authorizedRecipientUserIds));
|
|
337
|
+
const resolved = await resolveChannelsForRecipients(content, uniqueRecipientUserIds, ctx);
|
|
292
338
|
const writeEm = rootEm.fork();
|
|
293
339
|
await writeEm.transactional(async (tx) => {
|
|
294
|
-
for (const recipientUserId of
|
|
295
|
-
const notification = await createOrRefreshNotification(tx, content, recipientUserId, ctx);
|
|
340
|
+
for (const { recipientUserId, channels } of resolved) {
|
|
341
|
+
const notification = await createOrRefreshNotification(tx, content, recipientUserId, ctx, channels);
|
|
296
342
|
notifications.push(notification);
|
|
297
343
|
}
|
|
298
344
|
await tx.flush();
|
|
299
345
|
});
|
|
300
346
|
await invalidateNotificationCache(container, ctx, "created");
|
|
301
347
|
await emitNotificationCreatedBatch(eventBus, notifications, ctx);
|
|
302
|
-
await emitNotificationSseEvents(eventBus, notifications, ctx
|
|
348
|
+
await emitNotificationSseEvents(eventBus, notifications, ctx);
|
|
303
349
|
return notifications;
|
|
304
350
|
},
|
|
305
351
|
async markAsRead(notificationId, ctx) {
|
|
@@ -322,7 +368,7 @@ function createNotificationService(deps) {
|
|
|
322
368
|
const em = rootEm.fork();
|
|
323
369
|
const db = getDb(em);
|
|
324
370
|
const applyScope = (q) => {
|
|
325
|
-
let chain = q.where("recipient_user_id", "=", ctx.userId).where("tenant_id", "=", ctx.tenantId).where("status", "=", "unread");
|
|
371
|
+
let chain = q.where("recipient_user_id", "=", ctx.userId).where("tenant_id", "=", ctx.tenantId).where("status", "=", "unread").where(inAppVisibleSql());
|
|
326
372
|
if (ctx.organizationId) {
|
|
327
373
|
chain = chain.where("organization_id", "=", ctx.organizationId);
|
|
328
374
|
}
|
|
@@ -496,7 +542,10 @@ function createNotificationService(deps) {
|
|
|
496
542
|
recipientUserId: ctx.userId,
|
|
497
543
|
tenantId: ctx.tenantId,
|
|
498
544
|
status: "unread",
|
|
499
|
-
|
|
545
|
+
$and: [
|
|
546
|
+
buildNotificationReadScopeWhere(ctx),
|
|
547
|
+
inAppVisibleFilter()
|
|
548
|
+
]
|
|
500
549
|
});
|
|
501
550
|
},
|
|
502
551
|
async getPollData(ctx, since) {
|
|
@@ -504,7 +553,10 @@ function createNotificationService(deps) {
|
|
|
504
553
|
const filters = {
|
|
505
554
|
recipientUserId: ctx.userId,
|
|
506
555
|
tenantId: ctx.tenantId,
|
|
507
|
-
|
|
556
|
+
$and: [
|
|
557
|
+
buildNotificationReadScopeWhere(ctx),
|
|
558
|
+
inAppVisibleFilter()
|
|
559
|
+
]
|
|
508
560
|
};
|
|
509
561
|
if (since) {
|
|
510
562
|
filters.createdAt = { $gt: new Date(since) };
|
|
@@ -518,7 +570,10 @@ function createNotificationService(deps) {
|
|
|
518
570
|
recipientUserId: ctx.userId,
|
|
519
571
|
tenantId: ctx.tenantId,
|
|
520
572
|
status: "unread",
|
|
521
|
-
|
|
573
|
+
$and: [
|
|
574
|
+
buildNotificationReadScopeWhere(ctx),
|
|
575
|
+
inAppVisibleFilter()
|
|
576
|
+
]
|
|
522
577
|
})
|
|
523
578
|
]);
|
|
524
579
|
const recent = notifications.map(toNotificationDto);
|