@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,199 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { registerCommand, type CommandHandler } from '@open-mercato/shared/lib/commands'
|
|
3
|
+
import { ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'
|
|
4
|
+
import { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'
|
|
5
|
+
import { emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'
|
|
6
|
+
import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
|
|
7
|
+
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
8
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
9
|
+
import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
|
|
10
|
+
import { UserDevice } from '../data/entities'
|
|
11
|
+
import { registerDeviceCommandSchema, type RegisterDeviceCommandInput } from '../data/validators'
|
|
12
|
+
import { emitDevicesEvent } from '../events'
|
|
13
|
+
import {
|
|
14
|
+
applySnapshot,
|
|
15
|
+
deviceEvents,
|
|
16
|
+
deviceIndexer,
|
|
17
|
+
isDeviceUniqueViolation,
|
|
18
|
+
loadExistingDevice,
|
|
19
|
+
serializeDevice,
|
|
20
|
+
type DeviceSnapshot,
|
|
21
|
+
type DeviceUndoPayload,
|
|
22
|
+
} from './shared'
|
|
23
|
+
|
|
24
|
+
const registerDeviceCommand: CommandHandler<RegisterDeviceCommandInput, { id: string; deviceId: string; revived: boolean }> = {
|
|
25
|
+
id: 'devices.user_devices.register',
|
|
26
|
+
async prepare(rawInput, ctx) {
|
|
27
|
+
const parsed = registerDeviceCommandSchema.parse(rawInput)
|
|
28
|
+
// Enforce tenant scope before any DB access so prepare() can't probe across tenants.
|
|
29
|
+
ensureTenantScope(ctx, parsed.tenantId)
|
|
30
|
+
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
31
|
+
const existing = await loadExistingDevice(em, {
|
|
32
|
+
tenantId: parsed.tenantId,
|
|
33
|
+
organizationId: parsed.organizationId ?? null,
|
|
34
|
+
userId: parsed.userId,
|
|
35
|
+
deviceId: parsed.deviceId,
|
|
36
|
+
})
|
|
37
|
+
return existing ? { before: serializeDevice(existing) } : {}
|
|
38
|
+
},
|
|
39
|
+
async execute(rawInput, ctx) {
|
|
40
|
+
const parsed = registerDeviceCommandSchema.parse(rawInput)
|
|
41
|
+
ensureTenantScope(ctx, parsed.tenantId)
|
|
42
|
+
|
|
43
|
+
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
44
|
+
const existing = await loadExistingDevice(em, {
|
|
45
|
+
tenantId: parsed.tenantId,
|
|
46
|
+
organizationId: parsed.organizationId ?? null,
|
|
47
|
+
userId: parsed.userId,
|
|
48
|
+
deviceId: parsed.deviceId,
|
|
49
|
+
})
|
|
50
|
+
const now = new Date()
|
|
51
|
+
const hasPushToken = parsed.pushToken !== undefined && parsed.pushToken !== null
|
|
52
|
+
const wasActive = existing ? existing.deletedAt == null : false
|
|
53
|
+
|
|
54
|
+
let device!: UserDevice
|
|
55
|
+
try {
|
|
56
|
+
await withAtomicFlush(
|
|
57
|
+
em,
|
|
58
|
+
[
|
|
59
|
+
() => {
|
|
60
|
+
if (existing) {
|
|
61
|
+
device = existing
|
|
62
|
+
device.platform = parsed.platform
|
|
63
|
+
device.organizationId = parsed.organizationId ?? device.organizationId ?? null
|
|
64
|
+
if (parsed.clientAppVersion !== undefined) device.clientAppVersion = parsed.clientAppVersion ?? null
|
|
65
|
+
if (parsed.osVersion !== undefined) device.osVersion = parsed.osVersion ?? null
|
|
66
|
+
if (parsed.locale !== undefined) device.locale = parsed.locale ?? null
|
|
67
|
+
if (hasPushToken) {
|
|
68
|
+
device.pushToken = parsed.pushToken ?? null
|
|
69
|
+
device.pushProvider = parsed.pushProvider ?? device.pushProvider ?? null
|
|
70
|
+
device.pushTokenUpdatedAt = now
|
|
71
|
+
}
|
|
72
|
+
device.lastSeenAt = now
|
|
73
|
+
device.deletedAt = null
|
|
74
|
+
} else {
|
|
75
|
+
device = em.create(UserDevice, {
|
|
76
|
+
tenantId: parsed.tenantId,
|
|
77
|
+
organizationId: parsed.organizationId ?? null,
|
|
78
|
+
userId: parsed.userId,
|
|
79
|
+
deviceId: parsed.deviceId,
|
|
80
|
+
platform: parsed.platform,
|
|
81
|
+
clientAppVersion: parsed.clientAppVersion ?? null,
|
|
82
|
+
osVersion: parsed.osVersion ?? null,
|
|
83
|
+
locale: parsed.locale ?? null,
|
|
84
|
+
pushToken: hasPushToken ? parsed.pushToken ?? null : null,
|
|
85
|
+
pushProvider: hasPushToken ? parsed.pushProvider ?? null : null,
|
|
86
|
+
pushTokenUpdatedAt: hasPushToken ? now : null,
|
|
87
|
+
lastSeenAt: now,
|
|
88
|
+
})
|
|
89
|
+
em.persist(device)
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
{ transaction: true },
|
|
94
|
+
)
|
|
95
|
+
} catch (err) {
|
|
96
|
+
if (isDeviceUniqueViolation(err)) {
|
|
97
|
+
// Default English message is a fallback; device routes translate by `code` (see route POST
|
|
98
|
+
// handlers), matching the customers dictionaries conflict pattern.
|
|
99
|
+
throw new CrudHttpError(409, {
|
|
100
|
+
code: 'device_already_registered',
|
|
101
|
+
error: 'This device is already registered',
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
throw err
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const revived = Boolean(existing) && !wasActive
|
|
108
|
+
const de = ctx.container.resolve('dataEngine') as DataEngine
|
|
109
|
+
await emitCrudSideEffects({
|
|
110
|
+
dataEngine: de,
|
|
111
|
+
action: existing ? 'updated' : 'created',
|
|
112
|
+
entity: device,
|
|
113
|
+
identifiers: {
|
|
114
|
+
id: device.id,
|
|
115
|
+
tenantId: device.tenantId,
|
|
116
|
+
organizationId: device.organizationId ?? null,
|
|
117
|
+
},
|
|
118
|
+
indexer: deviceIndexer,
|
|
119
|
+
events: deviceEvents,
|
|
120
|
+
})
|
|
121
|
+
await emitDevicesEvent(
|
|
122
|
+
'devices.user_device.registered',
|
|
123
|
+
{
|
|
124
|
+
id: device.id,
|
|
125
|
+
tenantId: device.tenantId,
|
|
126
|
+
organizationId: device.organizationId ?? null,
|
|
127
|
+
userId: device.userId,
|
|
128
|
+
deviceId: device.deviceId,
|
|
129
|
+
platform: device.platform,
|
|
130
|
+
revived,
|
|
131
|
+
},
|
|
132
|
+
{ persistent: true },
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return { id: device.id, deviceId: device.deviceId, revived }
|
|
136
|
+
},
|
|
137
|
+
captureAfter: async (_input, result, ctx) => {
|
|
138
|
+
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
139
|
+
const device = await findOneWithDecryption(em, UserDevice, { id: result.id })
|
|
140
|
+
return device ? serializeDevice(device) : null
|
|
141
|
+
},
|
|
142
|
+
buildLog: async ({ result, snapshots }) => {
|
|
143
|
+
const before = (snapshots.before as DeviceSnapshot | undefined) ?? null
|
|
144
|
+
const after = (snapshots.after as DeviceSnapshot | undefined) ?? null
|
|
145
|
+
return {
|
|
146
|
+
actionLabel: before ? 'Update device' : 'Register device',
|
|
147
|
+
resourceKind: 'devices.user_device',
|
|
148
|
+
resourceId: result.id,
|
|
149
|
+
tenantId: after?.tenantId ?? before?.tenantId ?? null,
|
|
150
|
+
organizationId: after?.organizationId ?? before?.organizationId ?? null,
|
|
151
|
+
snapshotBefore: before,
|
|
152
|
+
snapshotAfter: after,
|
|
153
|
+
payload: {
|
|
154
|
+
undo: { before, after } satisfies DeviceUndoPayload,
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
undo: async ({ logEntry, ctx }) => {
|
|
159
|
+
const payload = extractUndoPayload<DeviceUndoPayload>(logEntry)
|
|
160
|
+
const after = payload?.after
|
|
161
|
+
if (!after) return
|
|
162
|
+
const before = payload?.before ?? null
|
|
163
|
+
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
164
|
+
const device = await findOneWithDecryption(em, UserDevice, { id: after.id })
|
|
165
|
+
if (!device) return
|
|
166
|
+
|
|
167
|
+
await withAtomicFlush(
|
|
168
|
+
em,
|
|
169
|
+
[
|
|
170
|
+
() => {
|
|
171
|
+
if (before) {
|
|
172
|
+
// Upsert/revive: restore the prior row state.
|
|
173
|
+
applySnapshot(device, before)
|
|
174
|
+
} else {
|
|
175
|
+
// Fresh registration: undo by soft-deleting the created row.
|
|
176
|
+
device.deletedAt = new Date()
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
{ transaction: true },
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
const de = ctx.container.resolve('dataEngine') as DataEngine
|
|
184
|
+
await emitCrudUndoSideEffects({
|
|
185
|
+
dataEngine: de,
|
|
186
|
+
action: before ? 'updated' : 'deleted',
|
|
187
|
+
entity: device,
|
|
188
|
+
identifiers: {
|
|
189
|
+
id: device.id,
|
|
190
|
+
tenantId: device.tenantId,
|
|
191
|
+
organizationId: device.organizationId ?? null,
|
|
192
|
+
},
|
|
193
|
+
indexer: deviceIndexer,
|
|
194
|
+
events: deviceEvents,
|
|
195
|
+
})
|
|
196
|
+
},
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
registerCommand(registerDeviceCommand)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { UniqueConstraintViolationException } from '@mikro-orm/core'
|
|
3
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
4
|
+
import type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'
|
|
5
|
+
import { E } from '#generated/entities.ids.generated'
|
|
6
|
+
import { UserDevice } from '../data/entities'
|
|
7
|
+
|
|
8
|
+
export type DeviceSnapshot = {
|
|
9
|
+
id: string
|
|
10
|
+
tenantId: string
|
|
11
|
+
organizationId: string | null
|
|
12
|
+
userId: string
|
|
13
|
+
deviceId: string
|
|
14
|
+
platform: string
|
|
15
|
+
clientAppVersion: string | null
|
|
16
|
+
osVersion: string | null
|
|
17
|
+
locale: string | null
|
|
18
|
+
// Last-8 fingerprint of the push token — NEVER the plaintext. Audit snapshots and the undo payload
|
|
19
|
+
// both persist to `action_logs` (no TTL, never purged, and `persistLog` permits a null tenantId the
|
|
20
|
+
// encryption subscriber skips), so storing the real token there would leak a device credential
|
|
21
|
+
// forever. The live encrypted column is the single source of truth; undo re-reads it (see applySnapshot).
|
|
22
|
+
pushTokenFingerprint: string | null
|
|
23
|
+
pushProvider: string | null
|
|
24
|
+
pushTokenUpdatedAt: string | null
|
|
25
|
+
lastSeenAt: string
|
|
26
|
+
deletedAt: string | null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type DeviceUndoPayload = {
|
|
30
|
+
before?: DeviceSnapshot | null
|
|
31
|
+
after?: DeviceSnapshot | null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const deviceIndexer: CrudIndexerConfig<UserDevice> = {
|
|
35
|
+
entityType: E.devices.user_device,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const deviceEvents: CrudEventsConfig<UserDevice> = {
|
|
39
|
+
module: 'devices',
|
|
40
|
+
entity: 'user_device',
|
|
41
|
+
persistent: true,
|
|
42
|
+
buildPayload: (ctx) => ({
|
|
43
|
+
id: ctx.identifiers.id,
|
|
44
|
+
tenantId: ctx.identifiers.tenantId,
|
|
45
|
+
organizationId: ctx.identifiers.organizationId,
|
|
46
|
+
}),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function toDate(value: string | null | undefined): Date | null {
|
|
50
|
+
if (!value) return null
|
|
51
|
+
const date = new Date(value)
|
|
52
|
+
return Number.isNaN(date.getTime()) ? null : date
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Persist at most the last 8 chars of the (long) provider token — never the full secret. Mirrors the
|
|
56
|
+
// delivery log's `token_snapshot` (push-fanout.ts) so audit/undo carry a stable, non-sensitive fingerprint.
|
|
57
|
+
function fingerprintPushToken(token: string | null): string | null {
|
|
58
|
+
return token ? token.slice(-8) : null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function serializeDevice(device: UserDevice): DeviceSnapshot {
|
|
62
|
+
return {
|
|
63
|
+
id: device.id,
|
|
64
|
+
tenantId: device.tenantId,
|
|
65
|
+
organizationId: device.organizationId ?? null,
|
|
66
|
+
userId: device.userId,
|
|
67
|
+
deviceId: device.deviceId,
|
|
68
|
+
platform: device.platform,
|
|
69
|
+
clientAppVersion: device.clientAppVersion ?? null,
|
|
70
|
+
osVersion: device.osVersion ?? null,
|
|
71
|
+
locale: device.locale ?? null,
|
|
72
|
+
pushTokenFingerprint: fingerprintPushToken(device.pushToken ?? null),
|
|
73
|
+
pushProvider: device.pushProvider ?? null,
|
|
74
|
+
pushTokenUpdatedAt: device.pushTokenUpdatedAt ? device.pushTokenUpdatedAt.toISOString() : null,
|
|
75
|
+
lastSeenAt: device.lastSeenAt.toISOString(),
|
|
76
|
+
deletedAt: device.deletedAt ? device.deletedAt.toISOString() : null,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function applySnapshot(device: UserDevice, snapshot: DeviceSnapshot): void {
|
|
81
|
+
device.platform = snapshot.platform as UserDevice['platform']
|
|
82
|
+
device.clientAppVersion = snapshot.clientAppVersion
|
|
83
|
+
device.osVersion = snapshot.osVersion
|
|
84
|
+
device.locale = snapshot.locale
|
|
85
|
+
// The push credential triple (token, provider, token_updated_at) is deliberately NOT restored from a
|
|
86
|
+
// snapshot. The snapshot carries only a fingerprint, and the token is a device-issued credential whose
|
|
87
|
+
// live encrypted column is authoritative — undo preserves whatever token the device currently holds.
|
|
88
|
+
// Restoring a stale token, or the redacted/undecryptable placeholder a snapshot could otherwise carry,
|
|
89
|
+
// would unrecoverably brick delivery. Reverting metadata is safe; reverting the secret is not.
|
|
90
|
+
device.lastSeenAt = toDate(snapshot.lastSeenAt) ?? new Date()
|
|
91
|
+
device.deletedAt = toDate(snapshot.deletedAt)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function loadExistingDevice(
|
|
95
|
+
em: EntityManager,
|
|
96
|
+
// organizationId is part of the device identity (a null value matches IS NULL, mirroring the
|
|
97
|
+
// coalesce(...) bucket in the unique index), so a device is looked up per (tenant, org, user, device).
|
|
98
|
+
scope: { tenantId: string; organizationId: string | null; userId: string; deviceId: string },
|
|
99
|
+
): Promise<UserDevice | null> {
|
|
100
|
+
// push_token is encrypted at rest; decrypt on read so snapshots/undo payloads keep the plaintext.
|
|
101
|
+
const dscope = { tenantId: scope.tenantId, organizationId: scope.organizationId }
|
|
102
|
+
const active = await findOneWithDecryption(em, UserDevice, { ...scope, deletedAt: null }, undefined, dscope)
|
|
103
|
+
if (active) return active
|
|
104
|
+
return findOneWithDecryption(em, UserDevice, scope, { orderBy: { createdAt: 'desc' } }, dscope)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Postgres SQLSTATE for unique_violation. MikroORM doesn't re-export pg error codes, so name it here.
|
|
108
|
+
const PG_UNIQUE_VIOLATION = '23505'
|
|
109
|
+
|
|
110
|
+
// A concurrent first-registration of the same (tenant, org, user, device_id) loses the race against the
|
|
111
|
+
// partial unique index. Surface it as a 409 conflict instead of a raw 500 — the endpoint is an
|
|
112
|
+
// idempotent upsert, so the caller can simply re-issue the request to land on the existing row.
|
|
113
|
+
export function isDeviceUniqueViolation(error: unknown): boolean {
|
|
114
|
+
if (error instanceof UniqueConstraintViolationException) return true
|
|
115
|
+
if (!error || typeof error !== 'object') return false
|
|
116
|
+
if ((error as { code?: string }).code === PG_UNIQUE_VIOLATION) return true
|
|
117
|
+
const message = (error as { message?: string }).message
|
|
118
|
+
return typeof message === 'string' && message.toLowerCase().includes('duplicate key')
|
|
119
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { registerCommand, type CommandHandler } from '@open-mercato/shared/lib/commands'
|
|
3
|
+
import { ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'
|
|
4
|
+
import { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'
|
|
5
|
+
import { emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'
|
|
6
|
+
import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
|
|
7
|
+
import { assertFound } from '@open-mercato/shared/lib/crud/errors'
|
|
8
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
9
|
+
import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
|
|
10
|
+
import { UserDevice } from '../data/entities'
|
|
11
|
+
import { updateDeviceCommandSchema, type UpdateDeviceCommandInput } from '../data/validators'
|
|
12
|
+
import {
|
|
13
|
+
applySnapshot,
|
|
14
|
+
deviceEvents,
|
|
15
|
+
deviceIndexer,
|
|
16
|
+
serializeDevice,
|
|
17
|
+
type DeviceSnapshot,
|
|
18
|
+
type DeviceUndoPayload,
|
|
19
|
+
} from './shared'
|
|
20
|
+
|
|
21
|
+
const updateDeviceCommand: CommandHandler<UpdateDeviceCommandInput, { id: string }> = {
|
|
22
|
+
id: 'devices.user_devices.update',
|
|
23
|
+
async prepare(rawInput, ctx) {
|
|
24
|
+
const parsed = updateDeviceCommandSchema.parse(rawInput)
|
|
25
|
+
// Enforce tenant scope and constrain the snapshot lookup to the caller's tenant.
|
|
26
|
+
ensureTenantScope(ctx, parsed.tenantId)
|
|
27
|
+
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
28
|
+
// No fallback decryption scope: `UserDevice` always carries its own organization_id, which
|
|
29
|
+
// `decryptEntitiesWithFallbackScope` resolves first — matching the scope-less lookup in execute().
|
|
30
|
+
const device = await findOneWithDecryption(
|
|
31
|
+
em,
|
|
32
|
+
UserDevice,
|
|
33
|
+
{ id: parsed.id, tenantId: parsed.tenantId, deletedAt: null },
|
|
34
|
+
)
|
|
35
|
+
return device ? { before: serializeDevice(device) } : {}
|
|
36
|
+
},
|
|
37
|
+
async execute(rawInput, ctx) {
|
|
38
|
+
const parsed = updateDeviceCommandSchema.parse(rawInput)
|
|
39
|
+
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
40
|
+
const device = assertFound(
|
|
41
|
+
await findOneWithDecryption(em, UserDevice, { id: parsed.id, deletedAt: null }),
|
|
42
|
+
'Device not found',
|
|
43
|
+
)
|
|
44
|
+
ensureTenantScope(ctx, device.tenantId)
|
|
45
|
+
|
|
46
|
+
const now = new Date()
|
|
47
|
+
await withAtomicFlush(
|
|
48
|
+
em,
|
|
49
|
+
[
|
|
50
|
+
() => {
|
|
51
|
+
if (Object.prototype.hasOwnProperty.call(parsed, 'clientAppVersion')) {
|
|
52
|
+
device.clientAppVersion = parsed.clientAppVersion ?? null
|
|
53
|
+
}
|
|
54
|
+
if (Object.prototype.hasOwnProperty.call(parsed, 'osVersion')) {
|
|
55
|
+
device.osVersion = parsed.osVersion ?? null
|
|
56
|
+
}
|
|
57
|
+
if (Object.prototype.hasOwnProperty.call(parsed, 'locale')) {
|
|
58
|
+
device.locale = parsed.locale ?? null
|
|
59
|
+
}
|
|
60
|
+
if (Object.prototype.hasOwnProperty.call(parsed, 'pushToken')) {
|
|
61
|
+
device.pushToken = parsed.pushToken ?? null
|
|
62
|
+
device.pushTokenUpdatedAt = now
|
|
63
|
+
}
|
|
64
|
+
if (Object.prototype.hasOwnProperty.call(parsed, 'pushProvider')) {
|
|
65
|
+
device.pushProvider = parsed.pushProvider ?? null
|
|
66
|
+
}
|
|
67
|
+
// Only a client-supplied lastSeenAt advances presence. Metadata-only edits (e.g. an admin
|
|
68
|
+
// changing push_provider) must NOT bump last_seen_at — device presence is maintained by the
|
|
69
|
+
// register heartbeat, not by edits.
|
|
70
|
+
if (Object.prototype.hasOwnProperty.call(parsed, 'lastSeenAt') && parsed.lastSeenAt) {
|
|
71
|
+
device.lastSeenAt = parsed.lastSeenAt
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
{ transaction: true },
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
const de = ctx.container.resolve('dataEngine') as DataEngine
|
|
79
|
+
await emitCrudSideEffects({
|
|
80
|
+
dataEngine: de,
|
|
81
|
+
action: 'updated',
|
|
82
|
+
entity: device,
|
|
83
|
+
identifiers: {
|
|
84
|
+
id: device.id,
|
|
85
|
+
tenantId: device.tenantId,
|
|
86
|
+
organizationId: device.organizationId ?? null,
|
|
87
|
+
},
|
|
88
|
+
indexer: deviceIndexer,
|
|
89
|
+
events: deviceEvents,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
return { id: device.id }
|
|
93
|
+
},
|
|
94
|
+
captureAfter: async (_input, result, ctx) => {
|
|
95
|
+
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
96
|
+
const device = await findOneWithDecryption(em, UserDevice, { id: result.id })
|
|
97
|
+
return device ? serializeDevice(device) : null
|
|
98
|
+
},
|
|
99
|
+
buildLog: async ({ snapshots }) => {
|
|
100
|
+
const before = (snapshots.before as DeviceSnapshot | undefined) ?? null
|
|
101
|
+
if (!before) return null
|
|
102
|
+
const after = (snapshots.after as DeviceSnapshot | undefined) ?? null
|
|
103
|
+
return {
|
|
104
|
+
actionLabel: 'Update device',
|
|
105
|
+
resourceKind: 'devices.user_device',
|
|
106
|
+
resourceId: before.id,
|
|
107
|
+
tenantId: before.tenantId,
|
|
108
|
+
organizationId: before.organizationId,
|
|
109
|
+
snapshotBefore: before,
|
|
110
|
+
snapshotAfter: after,
|
|
111
|
+
payload: {
|
|
112
|
+
undo: { before, after } satisfies DeviceUndoPayload,
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
undo: async ({ logEntry, ctx }) => {
|
|
117
|
+
const payload = extractUndoPayload<DeviceUndoPayload>(logEntry)
|
|
118
|
+
const before = payload?.before
|
|
119
|
+
if (!before) return
|
|
120
|
+
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
121
|
+
const device = await findOneWithDecryption(em, UserDevice, { id: before.id })
|
|
122
|
+
if (!device) return
|
|
123
|
+
|
|
124
|
+
await withAtomicFlush(em, [() => applySnapshot(device, before)], { transaction: true })
|
|
125
|
+
|
|
126
|
+
const de = ctx.container.resolve('dataEngine') as DataEngine
|
|
127
|
+
await emitCrudUndoSideEffects({
|
|
128
|
+
dataEngine: de,
|
|
129
|
+
action: 'updated',
|
|
130
|
+
entity: device,
|
|
131
|
+
identifiers: {
|
|
132
|
+
id: device.id,
|
|
133
|
+
tenantId: device.tenantId,
|
|
134
|
+
organizationId: device.organizationId ?? null,
|
|
135
|
+
},
|
|
136
|
+
indexer: deviceIndexer,
|
|
137
|
+
events: deviceEvents,
|
|
138
|
+
})
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
registerCommand(updateDeviceCommand)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { OptionalProps } from '@mikro-orm/core'
|
|
2
|
+
import { Entity, Index, PrimaryKey, Property } from '@mikro-orm/decorators/legacy'
|
|
3
|
+
|
|
4
|
+
export type DevicePlatform = 'ios' | 'android' | 'web'
|
|
5
|
+
|
|
6
|
+
@Entity({ tableName: 'user_devices' })
|
|
7
|
+
@Index({ name: 'user_devices_tenant_user_idx', properties: ['tenantId', 'userId'] })
|
|
8
|
+
@Index({
|
|
9
|
+
// Device identity is scoped per organization: (tenant, org, user, device). organization_id is
|
|
10
|
+
// nullable, so the index is declared NULLS NOT DISTINCT — otherwise Postgres treats NULLs as
|
|
11
|
+
// distinct and would allow duplicate null-org rows for the same (tenant, user, device).
|
|
12
|
+
name: 'user_devices_tenant_org_user_device_active_unique',
|
|
13
|
+
expression:
|
|
14
|
+
'create unique index "user_devices_tenant_org_user_device_active_unique" on "user_devices" ("tenant_id", "organization_id", "user_id", "device_id") nulls not distinct where deleted_at is null',
|
|
15
|
+
})
|
|
16
|
+
export class UserDevice {
|
|
17
|
+
[OptionalProps]?:
|
|
18
|
+
| 'organizationId'
|
|
19
|
+
| 'clientAppVersion'
|
|
20
|
+
| 'osVersion'
|
|
21
|
+
| 'locale'
|
|
22
|
+
| 'pushToken'
|
|
23
|
+
| 'pushProvider'
|
|
24
|
+
| 'pushTokenUpdatedAt'
|
|
25
|
+
| 'lastSeenAt'
|
|
26
|
+
| 'createdAt'
|
|
27
|
+
| 'updatedAt'
|
|
28
|
+
| 'deletedAt'
|
|
29
|
+
|
|
30
|
+
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
31
|
+
id!: string
|
|
32
|
+
|
|
33
|
+
@Property({ name: 'tenant_id', type: 'uuid' })
|
|
34
|
+
tenantId!: string
|
|
35
|
+
|
|
36
|
+
@Property({ name: 'organization_id', type: 'uuid', nullable: true })
|
|
37
|
+
organizationId?: string | null
|
|
38
|
+
|
|
39
|
+
@Property({ name: 'user_id', type: 'uuid' })
|
|
40
|
+
userId!: string
|
|
41
|
+
|
|
42
|
+
@Property({ name: 'device_id', type: 'text' })
|
|
43
|
+
deviceId!: string
|
|
44
|
+
|
|
45
|
+
@Property({ name: 'platform', type: 'text' })
|
|
46
|
+
platform!: DevicePlatform
|
|
47
|
+
|
|
48
|
+
@Property({ name: 'client_app_version', type: 'text', nullable: true })
|
|
49
|
+
clientAppVersion?: string | null
|
|
50
|
+
|
|
51
|
+
@Property({ name: 'os_version', type: 'text', nullable: true })
|
|
52
|
+
osVersion?: string | null
|
|
53
|
+
|
|
54
|
+
// Per-device language the client app picked (BCP-47, e.g. `en` / `en-US`). Normalized to a
|
|
55
|
+
// supported locale at push send time to translate the notification copy; null ⇒ default locale.
|
|
56
|
+
@Property({ name: 'locale', type: 'text', nullable: true })
|
|
57
|
+
locale?: string | null
|
|
58
|
+
|
|
59
|
+
@Property({ name: 'push_token', type: 'text', nullable: true })
|
|
60
|
+
pushToken?: string | null
|
|
61
|
+
|
|
62
|
+
@Property({ name: 'push_provider', type: 'text', nullable: true })
|
|
63
|
+
pushProvider?: string | null
|
|
64
|
+
|
|
65
|
+
@Property({ name: 'push_token_updated_at', type: Date, nullable: true })
|
|
66
|
+
pushTokenUpdatedAt?: Date | null
|
|
67
|
+
|
|
68
|
+
@Property({ name: 'last_seen_at', type: Date, onCreate: () => new Date() })
|
|
69
|
+
lastSeenAt: Date = new Date()
|
|
70
|
+
|
|
71
|
+
@Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
|
|
72
|
+
createdAt: Date = new Date()
|
|
73
|
+
|
|
74
|
+
@Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date() })
|
|
75
|
+
updatedAt: Date = new Date()
|
|
76
|
+
|
|
77
|
+
@Property({ name: 'deleted_at', type: Date, nullable: true })
|
|
78
|
+
deletedAt?: Date | null
|
|
79
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const devicePlatformSchema = z.enum(['ios', 'android', 'web'])
|
|
4
|
+
export type DevicePlatformInput = z.infer<typeof devicePlatformSchema>
|
|
5
|
+
|
|
6
|
+
// POST /api/devices — register/upsert. pushToken/pushProvider optional on first register.
|
|
7
|
+
export const registerDeviceSchema = z
|
|
8
|
+
.object({
|
|
9
|
+
deviceId: z.string().trim().min(1).max(255),
|
|
10
|
+
platform: devicePlatformSchema,
|
|
11
|
+
clientAppVersion: z.string().trim().max(64).nullish(),
|
|
12
|
+
osVersion: z.string().trim().max(64).nullish(),
|
|
13
|
+
locale: z.string().trim().min(2).max(35).nullish(),
|
|
14
|
+
pushToken: z.string().trim().min(1).max(4096).nullish(),
|
|
15
|
+
pushProvider: z.string().trim().min(1).max(32).nullish(),
|
|
16
|
+
})
|
|
17
|
+
.strict()
|
|
18
|
+
export type RegisterDeviceInput = z.infer<typeof registerDeviceSchema>
|
|
19
|
+
|
|
20
|
+
// POST /api/devices/admin/devices — admin registers a device on behalf of any user in the tenant.
|
|
21
|
+
// Same shape as self-register plus an explicit target userId (self-register derives it from auth).
|
|
22
|
+
export const registerDeviceAdminSchema = registerDeviceSchema.extend({
|
|
23
|
+
userId: z.string().uuid(),
|
|
24
|
+
})
|
|
25
|
+
export type RegisterDeviceAdminInput = z.infer<typeof registerDeviceAdminSchema>
|
|
26
|
+
|
|
27
|
+
// PUT /api/devices/:id — partial update.
|
|
28
|
+
// pushToken/pushProvider are tri-state: absent (leave unchanged) vs explicit null (clear).
|
|
29
|
+
// `.nullable().optional()` distinguishes `undefined` from `null`; the command relies on
|
|
30
|
+
// own-property presence to decide whether to touch the column.
|
|
31
|
+
export const updateDeviceSchema = z
|
|
32
|
+
.object({
|
|
33
|
+
clientAppVersion: z.string().trim().max(64).nullable().optional(),
|
|
34
|
+
osVersion: z.string().trim().max(64).nullable().optional(),
|
|
35
|
+
locale: z.string().trim().min(2).max(35).nullable().optional(),
|
|
36
|
+
pushToken: z.string().trim().min(1).max(4096).nullable().optional(),
|
|
37
|
+
pushProvider: z.string().trim().min(1).max(32).nullable().optional(),
|
|
38
|
+
lastSeenAt: z.coerce.date().optional(),
|
|
39
|
+
})
|
|
40
|
+
.strict()
|
|
41
|
+
export type UpdateDeviceInput = z.infer<typeof updateDeviceSchema>
|
|
42
|
+
|
|
43
|
+
// Command-side schemas append the resolved scope. Scope fields are never accepted from the client body.
|
|
44
|
+
export const registerDeviceCommandSchema = registerDeviceSchema.extend({
|
|
45
|
+
tenantId: z.string().uuid(),
|
|
46
|
+
organizationId: z.string().uuid().nullable().optional(),
|
|
47
|
+
userId: z.string().uuid(),
|
|
48
|
+
})
|
|
49
|
+
export type RegisterDeviceCommandInput = z.infer<typeof registerDeviceCommandSchema>
|
|
50
|
+
|
|
51
|
+
export const updateDeviceCommandSchema = updateDeviceSchema.extend({
|
|
52
|
+
id: z.string().uuid(),
|
|
53
|
+
tenantId: z.string().uuid(),
|
|
54
|
+
userId: z.string().uuid(),
|
|
55
|
+
organizationId: z.string().uuid().nullable().optional(),
|
|
56
|
+
})
|
|
57
|
+
export type UpdateDeviceCommandInput = z.infer<typeof updateDeviceCommandSchema>
|
|
58
|
+
|
|
59
|
+
export const deactivateDeviceCommandSchema = z.object({
|
|
60
|
+
id: z.string().uuid(),
|
|
61
|
+
tenantId: z.string().uuid(),
|
|
62
|
+
userId: z.string().uuid(),
|
|
63
|
+
organizationId: z.string().uuid().nullable().optional(),
|
|
64
|
+
})
|
|
65
|
+
export type DeactivateDeviceCommandInput = z.infer<typeof deactivateDeviceCommandSchema>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { asValue } from 'awilix'
|
|
2
|
+
import type { AppContainer } from '@open-mercato/shared/lib/di/container'
|
|
3
|
+
import { UserDevice } from './data/entities'
|
|
4
|
+
|
|
5
|
+
export function register(container: AppContainer) {
|
|
6
|
+
container.register({
|
|
7
|
+
UserDevice: asValue(UserDevice),
|
|
8
|
+
})
|
|
9
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ModuleEncryptionMap } from '@open-mercato/shared/modules/encryption'
|
|
2
|
+
|
|
3
|
+
// `push_token` is a long-lived provider secret. It is never exposed via the API (only a last-8
|
|
4
|
+
// snapshot is persisted on delivery rows) and is redacted from audit-log snapshots, so it is
|
|
5
|
+
// encrypted at rest. Reads route through `findWithDecryption`/`findOneWithDecryption` per the
|
|
6
|
+
// `packages/core/AGENTS.md` Encryption section. No blind-index column is needed: the push delivery
|
|
7
|
+
// path looks devices up by id/user, never by token value.
|
|
8
|
+
export const defaultEncryptionMaps: ModuleEncryptionMap[] = [
|
|
9
|
+
{
|
|
10
|
+
entityId: 'devices:user_device',
|
|
11
|
+
fields: [{ field: 'push_token' }],
|
|
12
|
+
},
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
export default defaultEncryptionMaps
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createModuleEvents } from '@open-mercato/shared/modules/events'
|
|
2
|
+
|
|
3
|
+
const events = [
|
|
4
|
+
// Domain events (spec contract): emitted explicitly from commands.
|
|
5
|
+
{ id: 'devices.user_device.registered', label: 'User Device Registered', entity: 'user_device', category: 'crud' },
|
|
6
|
+
{ id: 'devices.user_device.deactivated', label: 'User Device Deactivated', entity: 'user_device', category: 'crud' },
|
|
7
|
+
// CRUD lifecycle events emitted by emitCrudSideEffects (keep declared to avoid undeclared-event warnings).
|
|
8
|
+
{ id: 'devices.user_device.created', label: 'User Device Created', entity: 'user_device', category: 'crud' },
|
|
9
|
+
{ id: 'devices.user_device.updated', label: 'User Device Updated', entity: 'user_device', category: 'crud' },
|
|
10
|
+
{ id: 'devices.user_device.deleted', label: 'User Device Deleted', entity: 'user_device', category: 'crud' },
|
|
11
|
+
] as const
|
|
12
|
+
|
|
13
|
+
export const eventsConfig = createModuleEvents({ moduleId: 'devices', events })
|
|
14
|
+
export const emitDevicesEvent = eventsConfig.emit
|
|
15
|
+
export type DevicesEventId = typeof events[number]['id']
|
|
16
|
+
export default eventsConfig
|