@open-mercato/core 0.6.8-develop.6984.1.f0cf23f0f6 → 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,101 @@
|
|
|
1
|
+
# Devices Module — Agent Guidelines
|
|
2
|
+
|
|
3
|
+
Generic device registry keyed on `(tenant, org, user, device)` plus app/OS metadata and
|
|
4
|
+
push-token storage. Channel-agnostic — it owns device identity and the token *field*, but **no push
|
|
5
|
+
delivery logic** (sender, providers, delivery rows, workers live in the `push_notifications` module).
|
|
6
|
+
|
|
7
|
+
> **Spec note (resolves a wording inconsistency):** Spec §"Module 1" line 78 says "push-token storage
|
|
8
|
+
> does not live here," but the authoritative sections override that — the `UserDevice` entity lists
|
|
9
|
+
> `push_token`/`push_provider`/`push_token_updated_at` (Data Models: *"UserDevice carries push-token
|
|
10
|
+
> fields directly"*), and `push_notifications` ships **no** token API, relying on `PUT /api/devices/:id`
|
|
11
|
+
> to set/clear tokens. "Channel-agnostic" means no delivery machinery, not "no token column."
|
|
12
|
+
|
|
13
|
+
## Always
|
|
14
|
+
|
|
15
|
+
- Scope every query by `tenant_id`. `organization_id` is **nullable**, but scoping follows the standard
|
|
16
|
+
org-scoped pattern like every other module: the list routes keep `orm.orgField: 'organizationId'` and
|
|
17
|
+
let the factory auto-filter by the caller's org scope. The query engine's org scope is null-aware —
|
|
18
|
+
callers with no org restriction (or whose scope includes null) also see null-org rows; a caller pinned
|
|
19
|
+
to a specific org does not.
|
|
20
|
+
- Route all writes through the device commands (`commands/register.ts`, `commands/update.ts`,
|
|
21
|
+
`commands/deactivate.ts` → `devices.user_devices.register`, `devices.user_devices.update`,
|
|
22
|
+
`devices.user_devices.deactivate`; shared helpers in `commands/shared.ts`, registered via `commands/index.ts`)
|
|
23
|
+
— they carry undo snapshots, query-index side effects, and domain events.
|
|
24
|
+
- Treat `push_token` as a secret: **never** include it in list/response field sets. Only
|
|
25
|
+
`push_provider` and `push_token_updated_at` are exposed.
|
|
26
|
+
- Honor the `pushToken` tri-state on `PUT`: absent key = leave unchanged; explicit `null` = clear
|
|
27
|
+
(revoked OS permission) and bump `push_token_updated_at`. The command uses own-property presence.
|
|
28
|
+
- Keep the list route's CRUD cache tag aligned with the command's `resourceKind`. The list reads
|
|
29
|
+
through `makeCrudRoute` (org-nullable, custom POST/PUT/DELETE + command bus), so it passes
|
|
30
|
+
`events: { module: 'devices', entity: 'user_device' }` purely to set the cache resource tag to
|
|
31
|
+
`devices.user_device` — matching the `resourceKind` the commands invalidate. Without it the factory
|
|
32
|
+
falls back to the ORM entity name and writes never bust the list cache (stale list under
|
|
33
|
+
`ENABLE_CRUD_API_CACHE`). No events are emitted on GET.
|
|
34
|
+
|
|
35
|
+
## Never
|
|
36
|
+
|
|
37
|
+
- Never add push delivery, provider adapters, FCM/APNs SDKs, or send workers here — those belong to the `push_notifications` module.
|
|
38
|
+
- Never expose another user's devices unless the caller has `devices.admin` (wildcard-aware RBAC check).
|
|
39
|
+
- Never route `PUT`/`DELETE :id` through `makeCrudRoute` — the factory resolves the id from the body/
|
|
40
|
+
query, not the path param. The `[id]` route is a thin guard + command-bus wrapper by design.
|
|
41
|
+
|
|
42
|
+
## Self-serve vs admin split
|
|
43
|
+
|
|
44
|
+
Endpoints are split the same way other modules split them (e.g. `customer_accounts/api/admin`,
|
|
45
|
+
`staff/api/.../self`): the base path is **self-serve** (always scoped to the acting user) and a
|
|
46
|
+
separate `api/admin/devices` tree holds the **cross-user** operations gated by `devices.admin`.
|
|
47
|
+
|
|
48
|
+
- **Self-serve** (`devices.view` / `devices.manage`):
|
|
49
|
+
- `POST /api/devices` — register/upsert the **caller's own** device. Idempotent on `(tenant, org,
|
|
50
|
+
user, device_id)`; a soft-deleted row is **revived** (`deleted_at = null`); response `{ id, deviceId,
|
|
51
|
+
revived }`. Uniqueness of active rows enforced by the partial unique index
|
|
52
|
+
`user_devices_tenant_org_user_device_active_unique ... nulls not distinct where deleted_at is null`
|
|
53
|
+
(declared `NULLS NOT DISTINCT` so null-org rows still dedupe per tenant/user/device).
|
|
54
|
+
- `GET /api/devices` — the caller's own devices, scoped to the active organization. It does **not**
|
|
55
|
+
honor `?userId`.
|
|
56
|
+
- `PUT` / `DELETE /api/devices/:id` — **owner only** (403 otherwise) and scoped to the active org
|
|
57
|
+
(a device outside the current org reads as 404), mirroring the list + the user-owned-resource
|
|
58
|
+
convention. Update advances `last_seen_at` only when the client explicitly sends `lastSeenAt`
|
|
59
|
+
(metadata-only edits do not touch presence).
|
|
60
|
+
- **Admin** (`devices.admin`) under `api/admin/devices`:
|
|
61
|
+
- `GET /api/devices/admin/devices` — tenant-wide list; optional `?userId=` / `?platform=`.
|
|
62
|
+
- `POST /api/devices/admin/devices` — register on behalf of any user (`userId` in body,
|
|
63
|
+
`registerDeviceAdminSchema`). `actorUserId` (the admin) is recorded for the mutation guard/audit.
|
|
64
|
+
- `GET` / `PUT` / `DELETE /api/devices/admin/devices/:id` — read/update/deactivate any device.
|
|
65
|
+
|
|
66
|
+
Shared write boilerplate (guard → command bus → undo header) lives in `api/deviceOps.ts`
|
|
67
|
+
(`executeRegister`/`executeUpdate`/`executeDeactivate`); the list schema/fields/openapi item live in
|
|
68
|
+
`api/deviceList.ts`. Both list routes pass `events: { module:'devices', entity:'user_device' }` so any
|
|
69
|
+
write busts both caches (see cache-tag note above).
|
|
70
|
+
|
|
71
|
+
- Never route `PUT`/`DELETE :id` through `makeCrudRoute` — the factory resolves the id from the body/
|
|
72
|
+
query, not the path param. The `[id]` routes are thin guard + command-bus wrappers by design.
|
|
73
|
+
|
|
74
|
+
## Backend pages (`/backend/devices`, gated by `devices.admin`, settings → Auth)
|
|
75
|
+
|
|
76
|
+
- `backend/devices/page.tsx` — cross-user list via `GET /api/devices/admin/devices`, with a Register
|
|
77
|
+
button and per-row Edit (`/backend/devices/:id`) + Deactivate (`DELETE …/admin/devices/:id`).
|
|
78
|
+
- `backend/devices/create/page.tsx` — `CrudForm` → `POST /api/devices/admin/devices` (admin registers
|
|
79
|
+
for a user; `userId` is a required field).
|
|
80
|
+
- `backend/devices/[id]/page.tsx` — `CrudForm` edit (app/OS version, push provider) via
|
|
81
|
+
`PUT /api/devices/admin/devices/:id`, loaded from the admin `GET :id`. Never renders `push_token`.
|
|
82
|
+
|
|
83
|
+
## Events
|
|
84
|
+
|
|
85
|
+
- `devices.user_device.registered` — emitted on register/revive.
|
|
86
|
+
- `devices.user_device.deactivated` — emitted on soft-delete.
|
|
87
|
+
- `devices.user_device.created|updated|deleted` — CRUD lifecycle events from `emitCrudSideEffects`.
|
|
88
|
+
|
|
89
|
+
## ACL
|
|
90
|
+
|
|
91
|
+
`devices.view`, `devices.manage` (self-serve), `devices.admin` (cross-user). Defaults in `setup.ts`:
|
|
92
|
+
`superadmin`/`admin` get `devices.*`; `employee` gets `view` + `manage`. Run
|
|
93
|
+
`yarn mercato auth sync-role-acls` after changing `acl.ts`/`setup.ts` to backfill existing tenants.
|
|
94
|
+
|
|
95
|
+
## Validation Commands
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
yarn generate
|
|
99
|
+
yarn db:generate # schema-diff probe — partial unique index is hand-authored in the migration
|
|
100
|
+
yarn workspace @open-mercato/core build
|
|
101
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const features = [
|
|
2
|
+
{ id: 'devices.view', title: 'View own devices', module: 'devices' },
|
|
3
|
+
{ id: 'devices.manage', title: 'Manage own devices', module: 'devices' },
|
|
4
|
+
{ id: 'devices.admin', title: 'Manage devices across users', module: 'devices' },
|
|
5
|
+
]
|
|
6
|
+
|
|
7
|
+
export default features
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
4
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
5
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
6
|
+
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
7
|
+
import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
8
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
9
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
10
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
11
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
12
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
13
|
+
import { UserDevice } from '../../data/entities'
|
|
14
|
+
import { updateDeviceSchema } from '../../data/validators'
|
|
15
|
+
import { resolveDeviceActorUserId } from '../auth'
|
|
16
|
+
import { executeUpdate, executeDeactivate, type DeviceMutationContext } from '../deviceOps'
|
|
17
|
+
|
|
18
|
+
const logger = createLogger('devices')
|
|
19
|
+
|
|
20
|
+
// Self-serve: the caller may only mutate their OWN device. Cross-user device administration lives in
|
|
21
|
+
// api/admin/devices/[id] (gated by devices.admin).
|
|
22
|
+
export const metadata = {
|
|
23
|
+
PUT: { requireAuth: true, requireFeatures: ['devices.manage'] },
|
|
24
|
+
DELETE: { requireAuth: true, requireFeatures: ['devices.manage'] },
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const paramsSchema = z.object({ id: z.string().uuid() })
|
|
28
|
+
|
|
29
|
+
export async function PUT(req: Request, { params }: { params: { id: string } }) {
|
|
30
|
+
const { translate } = await resolveTranslations()
|
|
31
|
+
try {
|
|
32
|
+
const container = await createRequestContainer()
|
|
33
|
+
const auth = await getAuthFromRequest(req)
|
|
34
|
+
const actorUserId = resolveDeviceActorUserId(auth)
|
|
35
|
+
if (!auth || !auth.tenantId || !actorUserId) {
|
|
36
|
+
return NextResponse.json({ error: translate('devices.errors.unauthorized', 'Unauthorized') }, { status: 401 })
|
|
37
|
+
}
|
|
38
|
+
const parsedParams = paramsSchema.safeParse({ id: params.id })
|
|
39
|
+
if (!parsedParams.success) {
|
|
40
|
+
return NextResponse.json({ error: translate('devices.errors.invalid_id', 'Invalid device id') }, { status: 400 })
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const body = updateDeviceSchema.parse(await readJsonSafe(req, {}))
|
|
44
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
45
|
+
// Self devices are scoped to the caller's active organization, mirroring the list route and the
|
|
46
|
+
// user-owned-resource convention (e.g. progress jobs): a device outside the current org context
|
|
47
|
+
// reads as not-found. When there is no active org, scope by tenant only.
|
|
48
|
+
const currentOrganizationId = scope?.selectedId ?? auth.orgId ?? null
|
|
49
|
+
const em = container.resolve('em') as EntityManager
|
|
50
|
+
const device = await findOneWithDecryption(
|
|
51
|
+
em,
|
|
52
|
+
UserDevice,
|
|
53
|
+
{
|
|
54
|
+
id: parsedParams.data.id,
|
|
55
|
+
tenantId: auth.tenantId,
|
|
56
|
+
...(currentOrganizationId ? { organizationId: currentOrganizationId } : {}),
|
|
57
|
+
deletedAt: null,
|
|
58
|
+
},
|
|
59
|
+
undefined,
|
|
60
|
+
{ tenantId: auth.tenantId, organizationId: currentOrganizationId },
|
|
61
|
+
)
|
|
62
|
+
if (!device) {
|
|
63
|
+
return NextResponse.json({ error: translate('devices.errors.not_found', 'Device not found') }, { status: 404 })
|
|
64
|
+
}
|
|
65
|
+
if (device.userId !== actorUserId) {
|
|
66
|
+
return NextResponse.json({ error: translate('devices.errors.forbidden', 'Access denied') }, { status: 403 })
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const mctx: DeviceMutationContext = {
|
|
70
|
+
container,
|
|
71
|
+
auth,
|
|
72
|
+
scope,
|
|
73
|
+
organizationId: device.organizationId ?? null,
|
|
74
|
+
actorUserId,
|
|
75
|
+
request: req,
|
|
76
|
+
}
|
|
77
|
+
return await executeUpdate(mctx, device, body)
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if (err instanceof z.ZodError) {
|
|
80
|
+
return NextResponse.json(
|
|
81
|
+
{ error: translate('devices.errors.invalid_payload', 'Invalid device payload'), details: err.flatten() },
|
|
82
|
+
{ status: 400 },
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
if (isCrudHttpError(err)) {
|
|
86
|
+
return NextResponse.json(err.body, { status: err.status })
|
|
87
|
+
}
|
|
88
|
+
logger.error('devices.PUT failed', { err })
|
|
89
|
+
return NextResponse.json({ error: translate('devices.errors.update_failed', 'Failed to update device') }, { status: 500 })
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function DELETE(req: Request, { params }: { params: { id: string } }) {
|
|
94
|
+
const { translate } = await resolveTranslations()
|
|
95
|
+
try {
|
|
96
|
+
const container = await createRequestContainer()
|
|
97
|
+
const auth = await getAuthFromRequest(req)
|
|
98
|
+
const actorUserId = resolveDeviceActorUserId(auth)
|
|
99
|
+
if (!auth || !auth.tenantId || !actorUserId) {
|
|
100
|
+
return NextResponse.json({ error: translate('devices.errors.unauthorized', 'Unauthorized') }, { status: 401 })
|
|
101
|
+
}
|
|
102
|
+
const parsedParams = paramsSchema.safeParse({ id: params.id })
|
|
103
|
+
if (!parsedParams.success) {
|
|
104
|
+
return NextResponse.json({ error: translate('devices.errors.invalid_id', 'Invalid device id') }, { status: 400 })
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
108
|
+
// Same active-org scoping as PUT/the list route: a device outside the current org reads as not-found.
|
|
109
|
+
const currentOrganizationId = scope?.selectedId ?? auth.orgId ?? null
|
|
110
|
+
const em = container.resolve('em') as EntityManager
|
|
111
|
+
const device = await findOneWithDecryption(
|
|
112
|
+
em,
|
|
113
|
+
UserDevice,
|
|
114
|
+
{
|
|
115
|
+
id: parsedParams.data.id,
|
|
116
|
+
tenantId: auth.tenantId,
|
|
117
|
+
...(currentOrganizationId ? { organizationId: currentOrganizationId } : {}),
|
|
118
|
+
deletedAt: null,
|
|
119
|
+
},
|
|
120
|
+
undefined,
|
|
121
|
+
{ tenantId: auth.tenantId, organizationId: currentOrganizationId },
|
|
122
|
+
)
|
|
123
|
+
if (!device) {
|
|
124
|
+
return NextResponse.json({ error: translate('devices.errors.not_found', 'Device not found') }, { status: 404 })
|
|
125
|
+
}
|
|
126
|
+
if (device.userId !== actorUserId) {
|
|
127
|
+
return NextResponse.json({ error: translate('devices.errors.forbidden', 'Access denied') }, { status: 403 })
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const mctx: DeviceMutationContext = {
|
|
131
|
+
container,
|
|
132
|
+
auth,
|
|
133
|
+
scope,
|
|
134
|
+
organizationId: device.organizationId ?? null,
|
|
135
|
+
actorUserId,
|
|
136
|
+
request: req,
|
|
137
|
+
}
|
|
138
|
+
return await executeDeactivate(mctx, device)
|
|
139
|
+
} catch (err) {
|
|
140
|
+
if (isCrudHttpError(err)) {
|
|
141
|
+
return NextResponse.json(err.body, { status: err.status })
|
|
142
|
+
}
|
|
143
|
+
logger.error('devices.DELETE failed', { err })
|
|
144
|
+
return NextResponse.json({ error: translate('devices.errors.delete_failed', 'Failed to delete device') }, { status: 500 })
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const okResponseSchema = z.object({ ok: z.literal(true), id: z.string().uuid().optional() })
|
|
149
|
+
const errorResponseSchema = z.object({ error: z.string() })
|
|
150
|
+
|
|
151
|
+
export const openApi: OpenApiRouteDoc = {
|
|
152
|
+
tag: 'Devices',
|
|
153
|
+
summary: 'Update or deactivate one of your registered devices',
|
|
154
|
+
methods: {
|
|
155
|
+
PUT: {
|
|
156
|
+
summary: 'Update your device',
|
|
157
|
+
description:
|
|
158
|
+
'Updates last-seen, app/OS metadata, and push token for a device owned by the current user. Set pushToken to null to clear a revoked OS permission.',
|
|
159
|
+
requestBody: { schema: updateDeviceSchema },
|
|
160
|
+
responses: [{ status: 200, description: 'Device updated', schema: okResponseSchema }],
|
|
161
|
+
errors: [
|
|
162
|
+
{ status: 400, description: 'Invalid payload or id', schema: errorResponseSchema },
|
|
163
|
+
{ status: 401, description: 'Unauthorized', schema: errorResponseSchema },
|
|
164
|
+
{ status: 403, description: 'Not the device owner', schema: errorResponseSchema },
|
|
165
|
+
{ status: 404, description: 'Device not found', schema: errorResponseSchema },
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
DELETE: {
|
|
169
|
+
summary: 'Deactivate your device',
|
|
170
|
+
description: 'Soft-deletes a device owned by the current user.',
|
|
171
|
+
responses: [{ status: 200, description: 'Device deactivated', schema: okResponseSchema }],
|
|
172
|
+
errors: [
|
|
173
|
+
{ status: 400, description: 'Invalid id', schema: errorResponseSchema },
|
|
174
|
+
{ status: 401, description: 'Unauthorized', schema: errorResponseSchema },
|
|
175
|
+
{ status: 403, description: 'Not the device owner', schema: errorResponseSchema },
|
|
176
|
+
{ status: 404, description: 'Device not found', schema: errorResponseSchema },
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
4
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
5
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
6
|
+
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
7
|
+
import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
8
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
9
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
10
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
11
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
12
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
13
|
+
import { UserDevice } from '../../../../data/entities'
|
|
14
|
+
import { updateDeviceSchema } from '../../../../data/validators'
|
|
15
|
+
import { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'
|
|
16
|
+
import { resolveDeviceActorUserId } from '../../../auth'
|
|
17
|
+
import { executeUpdate, executeDeactivate, type DeviceMutationContext } from '../../../deviceOps'
|
|
18
|
+
|
|
19
|
+
const logger = createLogger('devices')
|
|
20
|
+
|
|
21
|
+
// Admin: read/update/deactivate ANY device in the tenant. Gated by devices.admin (no owner check).
|
|
22
|
+
export const metadata = {
|
|
23
|
+
GET: { requireAuth: true, requireFeatures: ['devices.admin'] },
|
|
24
|
+
PUT: { requireAuth: true, requireFeatures: ['devices.admin'] },
|
|
25
|
+
DELETE: { requireAuth: true, requireFeatures: ['devices.admin'] },
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const paramsSchema = z.object({ id: z.string().uuid() })
|
|
29
|
+
|
|
30
|
+
async function loadDevice(
|
|
31
|
+
container: Awaited<ReturnType<typeof createRequestContainer>>,
|
|
32
|
+
id: string,
|
|
33
|
+
tenantId: string,
|
|
34
|
+
): Promise<UserDevice | null> {
|
|
35
|
+
const em = container.resolve('em') as EntityManager
|
|
36
|
+
// push_token is encrypted at rest; the org is not known until the row loads, so pass tenant only —
|
|
37
|
+
// the helper prefers each record's own tenant/org and treats this scope as a fallback.
|
|
38
|
+
return findOneWithDecryption(em, UserDevice, { id, tenantId, deletedAt: null }, undefined, { tenantId })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// push_token is a secret and is never returned.
|
|
42
|
+
function serializeDevice(device: UserDevice) {
|
|
43
|
+
return {
|
|
44
|
+
id: device.id,
|
|
45
|
+
user_id: device.userId,
|
|
46
|
+
device_id: device.deviceId,
|
|
47
|
+
platform: device.platform,
|
|
48
|
+
client_app_version: device.clientAppVersion ?? null,
|
|
49
|
+
os_version: device.osVersion ?? null,
|
|
50
|
+
push_provider: device.pushProvider ?? null,
|
|
51
|
+
push_token_updated_at: device.pushTokenUpdatedAt ? device.pushTokenUpdatedAt.toISOString() : null,
|
|
52
|
+
last_seen_at: device.lastSeenAt ? device.lastSeenAt.toISOString() : null,
|
|
53
|
+
created_at: device.createdAt ? device.createdAt.toISOString() : null,
|
|
54
|
+
updated_at: device.updatedAt ? device.updatedAt.toISOString() : null,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function GET(req: Request, { params }: { params: { id: string } }) {
|
|
59
|
+
const { translate } = await resolveTranslations()
|
|
60
|
+
try {
|
|
61
|
+
const container = await createRequestContainer()
|
|
62
|
+
const auth = await getAuthFromRequest(req)
|
|
63
|
+
const actorUserId = resolveDeviceActorUserId(auth)
|
|
64
|
+
if (!auth || !auth.tenantId || !actorUserId) {
|
|
65
|
+
return NextResponse.json({ error: translate('devices.errors.unauthorized', 'Unauthorized') }, { status: 401 })
|
|
66
|
+
}
|
|
67
|
+
const parsedParams = paramsSchema.safeParse({ id: params.id })
|
|
68
|
+
if (!parsedParams.success) {
|
|
69
|
+
return NextResponse.json({ error: translate('devices.errors.invalid_id', 'Invalid device id') }, { status: 400 })
|
|
70
|
+
}
|
|
71
|
+
const device = await loadDevice(container, parsedParams.data.id, auth.tenantId)
|
|
72
|
+
if (!device) {
|
|
73
|
+
return NextResponse.json({ error: translate('devices.errors.not_found', 'Device not found') }, { status: 404 })
|
|
74
|
+
}
|
|
75
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
76
|
+
if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: device.organizationId ?? null })) {
|
|
77
|
+
return NextResponse.json({ error: translate('devices.errors.forbidden', 'Access denied') }, { status: 403 })
|
|
78
|
+
}
|
|
79
|
+
return NextResponse.json({ item: serializeDevice(device) })
|
|
80
|
+
} catch (err) {
|
|
81
|
+
if (isCrudHttpError(err)) {
|
|
82
|
+
return NextResponse.json(err.body, { status: err.status })
|
|
83
|
+
}
|
|
84
|
+
logger.error('devices.admin.GET failed', { err })
|
|
85
|
+
return NextResponse.json({ error: translate('devices.errors.load_failed', 'Failed to load device') }, { status: 500 })
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function PUT(req: Request, { params }: { params: { id: string } }) {
|
|
90
|
+
const { translate } = await resolveTranslations()
|
|
91
|
+
try {
|
|
92
|
+
const container = await createRequestContainer()
|
|
93
|
+
const auth = await getAuthFromRequest(req)
|
|
94
|
+
const actorUserId = resolveDeviceActorUserId(auth)
|
|
95
|
+
if (!auth || !auth.tenantId || !actorUserId) {
|
|
96
|
+
return NextResponse.json({ error: translate('devices.errors.unauthorized', 'Unauthorized') }, { status: 401 })
|
|
97
|
+
}
|
|
98
|
+
const parsedParams = paramsSchema.safeParse({ id: params.id })
|
|
99
|
+
if (!parsedParams.success) {
|
|
100
|
+
return NextResponse.json({ error: translate('devices.errors.invalid_id', 'Invalid device id') }, { status: 400 })
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const body = updateDeviceSchema.parse(await readJsonSafe(req, {}))
|
|
104
|
+
const device = await loadDevice(container, parsedParams.data.id, auth.tenantId)
|
|
105
|
+
if (!device) {
|
|
106
|
+
return NextResponse.json({ error: translate('devices.errors.not_found', 'Device not found') }, { status: 404 })
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
110
|
+
if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: device.organizationId ?? null })) {
|
|
111
|
+
return NextResponse.json({ error: translate('devices.errors.forbidden', 'Access denied') }, { status: 403 })
|
|
112
|
+
}
|
|
113
|
+
const mctx: DeviceMutationContext = {
|
|
114
|
+
container,
|
|
115
|
+
auth,
|
|
116
|
+
scope,
|
|
117
|
+
organizationId: device.organizationId ?? null,
|
|
118
|
+
actorUserId,
|
|
119
|
+
request: req,
|
|
120
|
+
}
|
|
121
|
+
return await executeUpdate(mctx, device, body)
|
|
122
|
+
} catch (err) {
|
|
123
|
+
if (err instanceof z.ZodError) {
|
|
124
|
+
return NextResponse.json(
|
|
125
|
+
{ error: translate('devices.errors.invalid_payload', 'Invalid device payload'), details: err.flatten() },
|
|
126
|
+
{ status: 400 },
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
if (isCrudHttpError(err)) {
|
|
130
|
+
return NextResponse.json(err.body, { status: err.status })
|
|
131
|
+
}
|
|
132
|
+
logger.error('devices.admin.PUT failed', { err })
|
|
133
|
+
return NextResponse.json({ error: translate('devices.errors.update_failed', 'Failed to update device') }, { status: 500 })
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function DELETE(req: Request, { params }: { params: { id: string } }) {
|
|
138
|
+
const { translate } = await resolveTranslations()
|
|
139
|
+
try {
|
|
140
|
+
const container = await createRequestContainer()
|
|
141
|
+
const auth = await getAuthFromRequest(req)
|
|
142
|
+
const actorUserId = resolveDeviceActorUserId(auth)
|
|
143
|
+
if (!auth || !auth.tenantId || !actorUserId) {
|
|
144
|
+
return NextResponse.json({ error: translate('devices.errors.unauthorized', 'Unauthorized') }, { status: 401 })
|
|
145
|
+
}
|
|
146
|
+
const parsedParams = paramsSchema.safeParse({ id: params.id })
|
|
147
|
+
if (!parsedParams.success) {
|
|
148
|
+
return NextResponse.json({ error: translate('devices.errors.invalid_id', 'Invalid device id') }, { status: 400 })
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const device = await loadDevice(container, parsedParams.data.id, auth.tenantId)
|
|
152
|
+
if (!device) {
|
|
153
|
+
return NextResponse.json({ error: translate('devices.errors.not_found', 'Device not found') }, { status: 404 })
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
157
|
+
if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: device.organizationId ?? null })) {
|
|
158
|
+
return NextResponse.json({ error: translate('devices.errors.forbidden', 'Access denied') }, { status: 403 })
|
|
159
|
+
}
|
|
160
|
+
const mctx: DeviceMutationContext = {
|
|
161
|
+
container,
|
|
162
|
+
auth,
|
|
163
|
+
scope,
|
|
164
|
+
organizationId: device.organizationId ?? null,
|
|
165
|
+
actorUserId,
|
|
166
|
+
request: req,
|
|
167
|
+
}
|
|
168
|
+
return await executeDeactivate(mctx, device)
|
|
169
|
+
} catch (err) {
|
|
170
|
+
if (isCrudHttpError(err)) {
|
|
171
|
+
return NextResponse.json(err.body, { status: err.status })
|
|
172
|
+
}
|
|
173
|
+
logger.error('devices.admin.DELETE failed', { err })
|
|
174
|
+
return NextResponse.json({ error: translate('devices.errors.delete_failed', 'Failed to delete device') }, { status: 500 })
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const okResponseSchema = z.object({ ok: z.literal(true), id: z.string().uuid().optional() })
|
|
179
|
+
const errorResponseSchema = z.object({ error: z.string() })
|
|
180
|
+
const detailResponseSchema = z.object({
|
|
181
|
+
item: z.object({
|
|
182
|
+
id: z.string().uuid(),
|
|
183
|
+
user_id: z.string().uuid(),
|
|
184
|
+
device_id: z.string(),
|
|
185
|
+
platform: z.enum(['ios', 'android', 'web']),
|
|
186
|
+
client_app_version: z.string().nullable(),
|
|
187
|
+
os_version: z.string().nullable(),
|
|
188
|
+
push_provider: z.string().nullable(),
|
|
189
|
+
push_token_updated_at: z.string().nullable(),
|
|
190
|
+
last_seen_at: z.string().nullable(),
|
|
191
|
+
created_at: z.string().nullable(),
|
|
192
|
+
updated_at: z.string().nullable(),
|
|
193
|
+
}),
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
export const openApi: OpenApiRouteDoc = {
|
|
197
|
+
tag: 'Devices (admin)',
|
|
198
|
+
summary: 'Admin: read, update or deactivate any device in the tenant',
|
|
199
|
+
methods: {
|
|
200
|
+
GET: {
|
|
201
|
+
summary: 'Get any device',
|
|
202
|
+
description: 'Admin: fetch a single device by id (push_token is never returned).',
|
|
203
|
+
responses: [{ status: 200, description: 'Device', schema: detailResponseSchema }],
|
|
204
|
+
errors: [
|
|
205
|
+
{ status: 400, description: 'Invalid id', schema: errorResponseSchema },
|
|
206
|
+
{ status: 401, description: 'Unauthorized', schema: errorResponseSchema },
|
|
207
|
+
{ status: 403, description: 'Missing devices.admin', schema: errorResponseSchema },
|
|
208
|
+
{ status: 404, description: 'Device not found', schema: errorResponseSchema },
|
|
209
|
+
],
|
|
210
|
+
},
|
|
211
|
+
PUT: {
|
|
212
|
+
summary: 'Update any device',
|
|
213
|
+
description: 'Admin: update last-seen, app/OS metadata, and push token for any device. Set pushToken to null to clear it.',
|
|
214
|
+
requestBody: { schema: updateDeviceSchema },
|
|
215
|
+
responses: [{ status: 200, description: 'Device updated', schema: okResponseSchema }],
|
|
216
|
+
errors: [
|
|
217
|
+
{ status: 400, description: 'Invalid payload or id', schema: errorResponseSchema },
|
|
218
|
+
{ status: 401, description: 'Unauthorized', schema: errorResponseSchema },
|
|
219
|
+
{ status: 403, description: 'Missing devices.admin', schema: errorResponseSchema },
|
|
220
|
+
{ status: 404, description: 'Device not found', schema: errorResponseSchema },
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
DELETE: {
|
|
224
|
+
summary: 'Deactivate any device',
|
|
225
|
+
description: 'Admin: soft-delete any device in the tenant.',
|
|
226
|
+
responses: [{ status: 200, description: 'Device deactivated', schema: okResponseSchema }],
|
|
227
|
+
errors: [
|
|
228
|
+
{ status: 400, description: 'Invalid id', schema: errorResponseSchema },
|
|
229
|
+
{ status: 401, description: 'Unauthorized', schema: errorResponseSchema },
|
|
230
|
+
{ status: 403, description: 'Missing devices.admin', schema: errorResponseSchema },
|
|
231
|
+
{ status: 404, description: 'Device not found', schema: errorResponseSchema },
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
}
|