@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.
Files changed (597) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/notification/index.js +6 -0
  3. package/dist/generated/entities/notification/index.js.map +2 -2
  4. package/dist/generated/entities/notification_preference/index.js +19 -0
  5. package/dist/generated/entities/notification_preference/index.js.map +7 -0
  6. package/dist/generated/entities/notification_type/index.js +21 -0
  7. package/dist/generated/entities/notification_type/index.js.map +7 -0
  8. package/dist/generated/entities/notification_type_override/index.js +17 -0
  9. package/dist/generated/entities/notification_type_override/index.js.map +7 -0
  10. package/dist/generated/entities/push_notification_delivery/index.js +41 -0
  11. package/dist/generated/entities/push_notification_delivery/index.js.map +7 -0
  12. package/dist/generated/entities/user_device/index.js +35 -0
  13. package/dist/generated/entities/user_device/index.js.map +7 -0
  14. package/dist/generated/entities.ids.generated.js +12 -1
  15. package/dist/generated/entities.ids.generated.js.map +2 -2
  16. package/dist/generated/entity-fields-registry.js +72 -0
  17. package/dist/generated/entity-fields-registry.js.map +2 -2
  18. package/dist/helpers/integration/api.js +2 -1
  19. package/dist/helpers/integration/api.js.map +2 -2
  20. package/dist/helpers/integration/appRoot.js +9 -0
  21. package/dist/helpers/integration/appRoot.js.map +7 -0
  22. package/dist/helpers/integration/dbFixtures.js +1 -4
  23. package/dist/helpers/integration/dbFixtures.js.map +2 -2
  24. package/dist/helpers/integration/pushFake.js +126 -0
  25. package/dist/helpers/integration/pushFake.js.map +7 -0
  26. package/dist/modules/auth/notifications.js +16 -0
  27. package/dist/modules/auth/notifications.js.map +2 -2
  28. package/dist/modules/business_rules/notifications.js +2 -0
  29. package/dist/modules/business_rules/notifications.js.map +2 -2
  30. package/dist/modules/catalog/notifications.js +2 -0
  31. package/dist/modules/catalog/notifications.js.map +2 -2
  32. package/dist/modules/communication_channels/acl.js +9 -0
  33. package/dist/modules/communication_channels/acl.js.map +2 -2
  34. package/dist/modules/communication_channels/api/delete/admin/channels/[id]/route.js +133 -0
  35. package/dist/modules/communication_channels/api/delete/admin/channels/[id]/route.js.map +7 -0
  36. package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js +6 -2
  37. package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js.map +2 -2
  38. package/dist/modules/communication_channels/api/get/channels/[id]/route.js +6 -2
  39. package/dist/modules/communication_channels/api/get/channels/[id]/route.js.map +2 -2
  40. package/dist/modules/communication_channels/api/get/channels/route.js +2 -1
  41. package/dist/modules/communication_channels/api/get/channels/route.js.map +2 -2
  42. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +10 -5
  43. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +2 -2
  44. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +29 -1
  45. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +2 -2
  46. package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js +157 -0
  47. package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js.map +7 -0
  48. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +8 -1
  49. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +2 -2
  50. package/dist/modules/communication_channels/commands/admin-delete-channel.js +141 -0
  51. package/dist/modules/communication_channels/commands/admin-delete-channel.js.map +7 -0
  52. package/dist/modules/communication_channels/commands/connect-credential-channel.js +25 -8
  53. package/dist/modules/communication_channels/commands/connect-credential-channel.js.map +2 -2
  54. package/dist/modules/communication_channels/commands/deliver-outbound-message.js +2 -2
  55. package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
  56. package/dist/modules/communication_channels/commands/push-register.js +1 -1
  57. package/dist/modules/communication_channels/commands/push-register.js.map +2 -2
  58. package/dist/modules/communication_channels/commands/push-unregister.js +4 -3
  59. package/dist/modules/communication_channels/commands/push-unregister.js.map +2 -2
  60. package/dist/modules/communication_channels/data/entities.js +4 -0
  61. package/dist/modules/communication_channels/data/entities.js.map +2 -2
  62. package/dist/modules/communication_channels/data/validators.js +2 -1
  63. package/dist/modules/communication_channels/data/validators.js.map +2 -2
  64. package/dist/modules/communication_channels/lib/access-control.js +11 -1
  65. package/dist/modules/communication_channels/lib/access-control.js.map +2 -2
  66. package/dist/modules/communication_channels/lib/connect-channel.js +20 -12
  67. package/dist/modules/communication_channels/lib/connect-channel.js.map +2 -2
  68. package/dist/modules/communication_channels/lib/push-adapter.js +68 -0
  69. package/dist/modules/communication_channels/lib/push-adapter.js.map +7 -0
  70. package/dist/modules/communication_channels/lib/push-capabilities.js +31 -0
  71. package/dist/modules/communication_channels/lib/push-capabilities.js.map +7 -0
  72. package/dist/modules/communication_channels/lib/push-connect-error.js +20 -0
  73. package/dist/modules/communication_channels/lib/push-connect-error.js.map +7 -0
  74. package/dist/modules/communication_channels/lib/push-credential-errors.js +41 -0
  75. package/dist/modules/communication_channels/lib/push-credential-errors.js.map +7 -0
  76. package/dist/modules/communication_channels/lib/push-envelope.js +46 -0
  77. package/dist/modules/communication_channels/lib/push-envelope.js.map +7 -0
  78. package/dist/modules/communication_channels/lib/refcounted-client-cache.js +64 -0
  79. package/dist/modules/communication_channels/lib/refcounted-client-cache.js.map +7 -0
  80. package/dist/modules/communication_channels/migrations/Migration20260703123630_communication_channels.js +13 -0
  81. package/dist/modules/communication_channels/migrations/Migration20260703123630_communication_channels.js.map +7 -0
  82. package/dist/modules/communication_channels/notifications.js +4 -0
  83. package/dist/modules/communication_channels/notifications.js.map +2 -2
  84. package/dist/modules/communication_channels/setup.js +2 -0
  85. package/dist/modules/communication_channels/setup.js.map +2 -2
  86. package/dist/modules/configs/lib/upgrade-actions.js +21 -0
  87. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  88. package/dist/modules/customer_accounts/notifications.js +12 -0
  89. package/dist/modules/customer_accounts/notifications.js.map +2 -2
  90. package/dist/modules/customers/notifications.js +4 -0
  91. package/dist/modules/customers/notifications.js.map +2 -2
  92. package/dist/modules/devices/acl.js +11 -0
  93. package/dist/modules/devices/acl.js.map +7 -0
  94. package/dist/modules/devices/api/[id]/route.js +167 -0
  95. package/dist/modules/devices/api/[id]/route.js.map +7 -0
  96. package/dist/modules/devices/api/admin/devices/[id]/route.js +219 -0
  97. package/dist/modules/devices/api/admin/devices/[id]/route.js.map +7 -0
  98. package/dist/modules/devices/api/admin/devices/route.js +140 -0
  99. package/dist/modules/devices/api/admin/devices/route.js.map +7 -0
  100. package/dist/modules/devices/api/auth.js +9 -0
  101. package/dist/modules/devices/api/auth.js.map +7 -0
  102. package/dist/modules/devices/api/deviceList.js +53 -0
  103. package/dist/modules/devices/api/deviceList.js.map +7 -0
  104. package/dist/modules/devices/api/deviceOps.js +118 -0
  105. package/dist/modules/devices/api/deviceOps.js.map +7 -0
  106. package/dist/modules/devices/api/openapi.js +19 -0
  107. package/dist/modules/devices/api/openapi.js.map +7 -0
  108. package/dist/modules/devices/api/route.js +143 -0
  109. package/dist/modules/devices/api/route.js.map +7 -0
  110. package/dist/modules/devices/backend/devices/[id]/page.js +135 -0
  111. package/dist/modules/devices/backend/devices/[id]/page.js.map +7 -0
  112. package/dist/modules/devices/backend/devices/[id]/page.meta.js +16 -0
  113. package/dist/modules/devices/backend/devices/[id]/page.meta.js.map +7 -0
  114. package/dist/modules/devices/backend/devices/create/page.js +68 -0
  115. package/dist/modules/devices/backend/devices/create/page.js.map +7 -0
  116. package/dist/modules/devices/backend/devices/create/page.meta.js +16 -0
  117. package/dist/modules/devices/backend/devices/create/page.meta.js.map +7 -0
  118. package/dist/modules/devices/backend/devices/page.js +235 -0
  119. package/dist/modules/devices/backend/devices/page.js.map +7 -0
  120. package/dist/modules/devices/backend/devices/page.meta.js +23 -0
  121. package/dist/modules/devices/backend/devices/page.meta.js.map +7 -0
  122. package/dist/modules/devices/commands/deactivate.js +107 -0
  123. package/dist/modules/devices/commands/deactivate.js.map +7 -0
  124. package/dist/modules/devices/commands/index.js +4 -0
  125. package/dist/modules/devices/commands/index.js.map +7 -0
  126. package/dist/modules/devices/commands/register.js +183 -0
  127. package/dist/modules/devices/commands/register.js.map +7 -0
  128. package/dist/modules/devices/commands/shared.js +74 -0
  129. package/dist/modules/devices/commands/shared.js.map +7 -0
  130. package/dist/modules/devices/commands/update.js +126 -0
  131. package/dist/modules/devices/commands/update.js.map +7 -0
  132. package/dist/modules/devices/data/entities.js +83 -0
  133. package/dist/modules/devices/data/entities.js.map +7 -0
  134. package/dist/modules/devices/data/validators.js +49 -0
  135. package/dist/modules/devices/data/validators.js.map +7 -0
  136. package/dist/modules/devices/di.js +11 -0
  137. package/dist/modules/devices/di.js.map +7 -0
  138. package/dist/modules/devices/encryption.js +12 -0
  139. package/dist/modules/devices/encryption.js.map +7 -0
  140. package/dist/modules/devices/events.js +19 -0
  141. package/dist/modules/devices/events.js.map +7 -0
  142. package/dist/modules/devices/index.js +17 -0
  143. package/dist/modules/devices/index.js.map +7 -0
  144. package/dist/modules/devices/lib/operationMetadata.js +20 -0
  145. package/dist/modules/devices/lib/operationMetadata.js.map +7 -0
  146. package/dist/modules/devices/migrations/Migration20260602120000.js +15 -0
  147. package/dist/modules/devices/migrations/Migration20260602120000.js.map +7 -0
  148. package/dist/modules/devices/migrations/Migration20260630120000.js +15 -0
  149. package/dist/modules/devices/migrations/Migration20260630120000.js.map +7 -0
  150. package/dist/modules/devices/migrations/Migration20260701120000.js +13 -0
  151. package/dist/modules/devices/migrations/Migration20260701120000.js.map +7 -0
  152. package/dist/modules/devices/migrations/Migration20260721120000.js +15 -0
  153. package/dist/modules/devices/migrations/Migration20260721120000.js.map +7 -0
  154. package/dist/modules/devices/setup.js +13 -0
  155. package/dist/modules/devices/setup.js.map +7 -0
  156. package/dist/modules/entities/cli.js +8 -3
  157. package/dist/modules/entities/cli.js.map +2 -2
  158. package/dist/modules/entities/migrations/Migration20260722120000.js +28 -0
  159. package/dist/modules/entities/migrations/Migration20260722120000.js.map +7 -0
  160. package/dist/modules/inbox_ops/notifications.js +2 -0
  161. package/dist/modules/inbox_ops/notifications.js.map +2 -2
  162. package/dist/modules/messages/notifications.js +2 -0
  163. package/dist/modules/messages/notifications.js.map +2 -2
  164. package/dist/modules/notifications/acl.js +3 -1
  165. package/dist/modules/notifications/acl.js.map +2 -2
  166. package/dist/modules/notifications/api/admin/preferences/route.js +167 -0
  167. package/dist/modules/notifications/api/admin/preferences/route.js.map +7 -0
  168. package/dist/modules/notifications/api/channels/route.js +54 -0
  169. package/dist/modules/notifications/api/channels/route.js.map +7 -0
  170. package/dist/modules/notifications/api/preferences/route.js +126 -0
  171. package/dist/modules/notifications/api/preferences/route.js.map +7 -0
  172. package/dist/modules/notifications/api/route.js +8 -1
  173. package/dist/modules/notifications/api/route.js.map +2 -2
  174. package/dist/modules/notifications/api/types/[id]/channels/[channel]/route.js +187 -0
  175. package/dist/modules/notifications/api/types/[id]/channels/[channel]/route.js.map +7 -0
  176. package/dist/modules/notifications/api/types/route.js +225 -0
  177. package/dist/modules/notifications/api/types/route.js.map +7 -0
  178. package/dist/modules/notifications/api/unread-count/route.js +5 -1
  179. package/dist/modules/notifications/api/unread-count/route.js.map +2 -2
  180. package/dist/modules/notifications/backend/notifications/user-preferences/page.js +10 -0
  181. package/dist/modules/notifications/backend/notifications/user-preferences/page.js.map +7 -0
  182. package/dist/modules/notifications/backend/notifications/user-preferences/page.meta.js +28 -0
  183. package/dist/modules/notifications/backend/notifications/user-preferences/page.meta.js.map +7 -0
  184. package/dist/modules/notifications/backend/profile/notification-preferences/page.js +10 -0
  185. package/dist/modules/notifications/backend/profile/notification-preferences/page.js.map +7 -0
  186. package/dist/modules/notifications/backend/profile/notification-preferences/page.meta.js +33 -0
  187. package/dist/modules/notifications/backend/profile/notification-preferences/page.meta.js.map +7 -0
  188. package/dist/modules/notifications/data/entities.js +128 -1
  189. package/dist/modules/notifications/data/entities.js.map +2 -2
  190. package/dist/modules/notifications/data/validators.js +95 -2
  191. package/dist/modules/notifications/data/validators.js.map +2 -2
  192. package/dist/modules/notifications/di.js +7 -0
  193. package/dist/modules/notifications/di.js.map +2 -2
  194. package/dist/modules/notifications/events.js +17 -0
  195. package/dist/modules/notifications/events.js.map +2 -2
  196. package/dist/modules/notifications/frontend/NotificationPreferenceMatrix.js +190 -0
  197. package/dist/modules/notifications/frontend/NotificationPreferenceMatrix.js.map +7 -0
  198. package/dist/modules/notifications/frontend/NotificationPreferencesPageClient.js +124 -0
  199. package/dist/modules/notifications/frontend/NotificationPreferencesPageClient.js.map +7 -0
  200. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +166 -1
  201. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +2 -2
  202. package/dist/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.js +172 -0
  203. package/dist/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.js.map +7 -0
  204. package/dist/modules/notifications/generators.js +63 -0
  205. package/dist/modules/notifications/generators.js.map +7 -0
  206. package/dist/modules/notifications/lib/delivery-strategies-registry.js +19 -0
  207. package/dist/modules/notifications/lib/delivery-strategies-registry.js.map +7 -0
  208. package/dist/modules/notifications/lib/deliveryStrategies.js.map +2 -2
  209. package/dist/modules/notifications/lib/derive-category.js +8 -0
  210. package/dist/modules/notifications/lib/derive-category.js.map +7 -0
  211. package/dist/modules/notifications/lib/notification-channel-registry.js +35 -0
  212. package/dist/modules/notifications/lib/notification-channel-registry.js.map +7 -0
  213. package/dist/modules/notifications/lib/notification-type-registry.js +87 -0
  214. package/dist/modules/notifications/lib/notification-type-registry.js.map +7 -0
  215. package/dist/modules/notifications/lib/notificationCopy.js +14 -0
  216. package/dist/modules/notifications/lib/notificationCopy.js.map +7 -0
  217. package/dist/modules/notifications/lib/notificationFactory.js +4 -1
  218. package/dist/modules/notifications/lib/notificationFactory.js.map +2 -2
  219. package/dist/modules/notifications/lib/notificationMapper.js +2 -0
  220. package/dist/modules/notifications/lib/notificationMapper.js.map +2 -2
  221. package/dist/modules/notifications/lib/notificationPreferenceService.js +83 -0
  222. package/dist/modules/notifications/lib/notificationPreferenceService.js.map +7 -0
  223. package/dist/modules/notifications/lib/notificationService.js +83 -28
  224. package/dist/modules/notifications/lib/notificationService.js.map +2 -2
  225. package/dist/modules/notifications/lib/notificationVisibility.js +24 -0
  226. package/dist/modules/notifications/lib/notificationVisibility.js.map +7 -0
  227. package/dist/modules/notifications/lib/routeHelpers.js +11 -1
  228. package/dist/modules/notifications/lib/routeHelpers.js.map +2 -2
  229. package/dist/modules/notifications/lib/shouldDeliver.js +46 -0
  230. package/dist/modules/notifications/lib/shouldDeliver.js.map +7 -0
  231. package/dist/modules/notifications/lib/strategies/email-delivery-strategy.js +54 -0
  232. package/dist/modules/notifications/lib/strategies/email-delivery-strategy.js.map +7 -0
  233. package/dist/modules/notifications/lib/strategies/in-app-delivery-strategy.js +12 -0
  234. package/dist/modules/notifications/lib/strategies/in-app-delivery-strategy.js.map +7 -0
  235. package/dist/modules/notifications/lib/typeCatalogue.js +71 -0
  236. package/dist/modules/notifications/lib/typeCatalogue.js.map +7 -0
  237. package/dist/modules/notifications/lib/typeChannelSettings.js +12 -0
  238. package/dist/modules/notifications/lib/typeChannelSettings.js.map +7 -0
  239. package/dist/modules/notifications/lib/typeOverrides.js +19 -0
  240. package/dist/modules/notifications/lib/typeOverrides.js.map +7 -0
  241. package/dist/modules/notifications/migrations/Migration20260625122947_notifications.js +14 -0
  242. package/dist/modules/notifications/migrations/Migration20260625122947_notifications.js.map +7 -0
  243. package/dist/modules/notifications/migrations/Migration20260626120500_notifications_push_payload.js +15 -0
  244. package/dist/modules/notifications/migrations/Migration20260626120500_notifications_push_payload.js.map +7 -0
  245. package/dist/modules/notifications/migrations/Migration20260626130000_notifications_non_opt_out.js +13 -0
  246. package/dist/modules/notifications/migrations/Migration20260626130000_notifications_non_opt_out.js.map +7 -0
  247. package/dist/modules/notifications/migrations/Migration20260626140000_notifications_type_category_silent.js +15 -0
  248. package/dist/modules/notifications/migrations/Migration20260626140000_notifications_type_category_silent.js.map +7 -0
  249. package/dist/modules/notifications/migrations/Migration20260706120000_notifications_channels.js +13 -0
  250. package/dist/modules/notifications/migrations/Migration20260706120000_notifications_channels.js.map +7 -0
  251. package/dist/modules/notifications/migrations/Migration20260716123121_notifications.js +14 -0
  252. package/dist/modules/notifications/migrations/Migration20260716123121_notifications.js.map +7 -0
  253. package/dist/modules/notifications/notification-channels.js +26 -0
  254. package/dist/modules/notifications/notification-channels.js.map +7 -0
  255. package/dist/modules/notifications/notifications.delivery-strategies.js +12 -0
  256. package/dist/modules/notifications/notifications.delivery-strategies.js.map +7 -0
  257. package/dist/modules/notifications/setup.js +16 -1
  258. package/dist/modules/notifications/setup.js.map +2 -2
  259. package/dist/modules/notifications/subscribers/deliver-notification.js +66 -57
  260. package/dist/modules/notifications/subscribers/deliver-notification.js.map +2 -2
  261. package/dist/modules/notifications/subscribers/sync-notification-types.js +16 -0
  262. package/dist/modules/notifications/subscribers/sync-notification-types.js.map +7 -0
  263. package/dist/modules/notifications/widgets/injection/profile-preferences-menu/widget.js +23 -0
  264. package/dist/modules/notifications/widgets/injection/profile-preferences-menu/widget.js.map +7 -0
  265. package/dist/modules/notifications/widgets/injection-table.js +14 -0
  266. package/dist/modules/notifications/widgets/injection-table.js.map +7 -0
  267. package/dist/modules/push_notifications/acl.js +13 -0
  268. package/dist/modules/push_notifications/acl.js.map +7 -0
  269. package/dist/modules/push_notifications/api/custom-send/route.js +141 -0
  270. package/dist/modules/push_notifications/api/custom-send/route.js.map +7 -0
  271. package/dist/modules/push_notifications/api/deliveries/[id]/route.js +110 -0
  272. package/dist/modules/push_notifications/api/deliveries/[id]/route.js.map +7 -0
  273. package/dist/modules/push_notifications/api/deliveries/route.js +56 -0
  274. package/dist/modules/push_notifications/api/deliveries/route.js.map +7 -0
  275. package/dist/modules/push_notifications/api/openapi.js +19 -0
  276. package/dist/modules/push_notifications/api/openapi.js.map +7 -0
  277. package/dist/modules/push_notifications/backend/push_notifications/[id]/page.js +103 -0
  278. package/dist/modules/push_notifications/backend/push_notifications/[id]/page.js.map +7 -0
  279. package/dist/modules/push_notifications/backend/push_notifications/[id]/page.meta.js +13 -0
  280. package/dist/modules/push_notifications/backend/push_notifications/[id]/page.meta.js.map +7 -0
  281. package/dist/modules/push_notifications/backend/push_notifications/page.js +213 -0
  282. package/dist/modules/push_notifications/backend/push_notifications/page.js.map +7 -0
  283. package/dist/modules/push_notifications/backend/push_notifications/page.meta.js +23 -0
  284. package/dist/modules/push_notifications/backend/push_notifications/page.meta.js.map +7 -0
  285. package/dist/modules/push_notifications/backend/push_notifications/send/page.js +245 -0
  286. package/dist/modules/push_notifications/backend/push_notifications/send/page.js.map +7 -0
  287. package/dist/modules/push_notifications/backend/push_notifications/send/page.meta.js +26 -0
  288. package/dist/modules/push_notifications/backend/push_notifications/send/page.meta.js.map +7 -0
  289. package/dist/modules/push_notifications/data/entities.js +92 -0
  290. package/dist/modules/push_notifications/data/entities.js.map +7 -0
  291. package/dist/modules/push_notifications/data/extensions.js +23 -0
  292. package/dist/modules/push_notifications/data/extensions.js.map +7 -0
  293. package/dist/modules/push_notifications/data/validators.js +89 -0
  294. package/dist/modules/push_notifications/data/validators.js.map +7 -0
  295. package/dist/modules/push_notifications/di.js +17 -0
  296. package/dist/modules/push_notifications/di.js.map +7 -0
  297. package/dist/modules/push_notifications/events.js +24 -0
  298. package/dist/modules/push_notifications/events.js.map +7 -0
  299. package/dist/modules/push_notifications/index.js +16 -0
  300. package/dist/modules/push_notifications/index.js.map +7 -0
  301. package/dist/modules/push_notifications/lib/fake-provider-recorder.js +80 -0
  302. package/dist/modules/push_notifications/lib/fake-provider-recorder.js.map +7 -0
  303. package/dist/modules/push_notifications/lib/push-delivery-strategy.js +54 -0
  304. package/dist/modules/push_notifications/lib/push-delivery-strategy.js.map +7 -0
  305. package/dist/modules/push_notifications/lib/push-delivery.js +297 -0
  306. package/dist/modules/push_notifications/lib/push-delivery.js.map +7 -0
  307. package/dist/modules/push_notifications/lib/push-fanout.js +138 -0
  308. package/dist/modules/push_notifications/lib/push-fanout.js.map +7 -0
  309. package/dist/modules/push_notifications/lib/push-health.js +32 -0
  310. package/dist/modules/push_notifications/lib/push-health.js.map +7 -0
  311. package/dist/modules/push_notifications/lib/push-reaper.js +87 -0
  312. package/dist/modules/push_notifications/lib/push-reaper.js.map +7 -0
  313. package/dist/modules/push_notifications/lib/push-receipt-reaper.js +136 -0
  314. package/dist/modules/push_notifications/lib/push-receipt-reaper.js.map +7 -0
  315. package/dist/modules/push_notifications/lib/push-stub-adapter.js +63 -0
  316. package/dist/modules/push_notifications/lib/push-stub-adapter.js.map +7 -0
  317. package/dist/modules/push_notifications/lib/queue.js +53 -0
  318. package/dist/modules/push_notifications/lib/queue.js.map +7 -0
  319. package/dist/modules/push_notifications/lib/reclaim-window.js +13 -0
  320. package/dist/modules/push_notifications/lib/reclaim-window.js.map +7 -0
  321. package/dist/modules/push_notifications/lib/send-custom-push.js +48 -0
  322. package/dist/modules/push_notifications/lib/send-custom-push.js.map +7 -0
  323. package/dist/modules/push_notifications/migrations/Migration20260625150049_push_notifications.js +16 -0
  324. package/dist/modules/push_notifications/migrations/Migration20260625150049_push_notifications.js.map +7 -0
  325. package/dist/modules/push_notifications/migrations/Migration20260626120000_push_notifications_silent.js +13 -0
  326. package/dist/modules/push_notifications/migrations/Migration20260626120000_push_notifications_silent.js.map +7 -0
  327. package/dist/modules/push_notifications/notifications.delivery-strategies.js +8 -0
  328. package/dist/modules/push_notifications/notifications.delivery-strategies.js.map +7 -0
  329. package/dist/modules/push_notifications/notifications.js +40 -0
  330. package/dist/modules/push_notifications/notifications.js.map +7 -0
  331. package/dist/modules/push_notifications/setup.js +52 -0
  332. package/dist/modules/push_notifications/setup.js.map +7 -0
  333. package/dist/modules/push_notifications/workers/reclaim-stuck.worker.js +57 -0
  334. package/dist/modules/push_notifications/workers/reclaim-stuck.worker.js.map +7 -0
  335. package/dist/modules/push_notifications/workers/send-push.worker.js +27 -0
  336. package/dist/modules/push_notifications/workers/send-push.worker.js.map +7 -0
  337. package/dist/modules/sales/notifications.js +8 -0
  338. package/dist/modules/sales/notifications.js.map +2 -2
  339. package/dist/modules/staff/notifications.js +6 -0
  340. package/dist/modules/staff/notifications.js.map +2 -2
  341. package/dist/modules/workflows/notifications.js +2 -0
  342. package/dist/modules/workflows/notifications.js.map +2 -2
  343. package/generated/entities/notification/index.ts +3 -0
  344. package/generated/entities/notification_preference/index.ts +8 -0
  345. package/generated/entities/notification_type/index.ts +9 -0
  346. package/generated/entities/notification_type_override/index.ts +7 -0
  347. package/generated/entities/push_notification_delivery/index.ts +19 -0
  348. package/generated/entities/user_device/index.ts +16 -0
  349. package/generated/entities.ids.generated.ts +12 -1
  350. package/generated/entity-fields-registry.ts +72 -0
  351. package/package.json +7 -7
  352. package/src/helpers/integration/api.ts +2 -1
  353. package/src/helpers/integration/appRoot.ts +13 -0
  354. package/src/helpers/integration/dbFixtures.ts +1 -5
  355. package/src/helpers/integration/pushFake.ts +219 -0
  356. package/src/modules/auth/i18n/de.json +1 -0
  357. package/src/modules/auth/i18n/en.json +1 -0
  358. package/src/modules/auth/i18n/es.json +1 -0
  359. package/src/modules/auth/i18n/ko.json +1 -0
  360. package/src/modules/auth/i18n/pl.json +1 -0
  361. package/src/modules/auth/notifications.ts +16 -0
  362. package/src/modules/business_rules/i18n/de.json +2 -1
  363. package/src/modules/business_rules/i18n/en.json +2 -1
  364. package/src/modules/business_rules/i18n/es.json +2 -1
  365. package/src/modules/business_rules/i18n/ko.json +2 -1
  366. package/src/modules/business_rules/i18n/pl.json +2 -1
  367. package/src/modules/business_rules/notifications.ts +2 -0
  368. package/src/modules/catalog/i18n/de.json +2 -1
  369. package/src/modules/catalog/i18n/en.json +2 -1
  370. package/src/modules/catalog/i18n/es.json +2 -1
  371. package/src/modules/catalog/i18n/ko.json +2 -1
  372. package/src/modules/catalog/i18n/pl.json +2 -1
  373. package/src/modules/catalog/notifications.ts +2 -0
  374. package/src/modules/communication_channels/acl.ts +9 -0
  375. package/src/modules/communication_channels/api/delete/admin/channels/[id]/route.ts +168 -0
  376. package/src/modules/communication_channels/api/get/channels/[id]/health/route.ts +6 -2
  377. package/src/modules/communication_channels/api/get/channels/[id]/route.ts +6 -2
  378. package/src/modules/communication_channels/api/get/channels/route.ts +2 -1
  379. package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +16 -5
  380. package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +37 -1
  381. package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts +175 -0
  382. package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +11 -1
  383. package/src/modules/communication_channels/commands/admin-delete-channel.ts +203 -0
  384. package/src/modules/communication_channels/commands/connect-credential-channel.ts +63 -15
  385. package/src/modules/communication_channels/commands/deliver-outbound-message.ts +10 -2
  386. package/src/modules/communication_channels/commands/push-register.ts +7 -1
  387. package/src/modules/communication_channels/commands/push-unregister.ts +14 -3
  388. package/src/modules/communication_channels/data/entities.ts +10 -0
  389. package/src/modules/communication_channels/data/validators.ts +1 -0
  390. package/src/modules/communication_channels/i18n/de.json +35 -1
  391. package/src/modules/communication_channels/i18n/en.json +35 -1
  392. package/src/modules/communication_channels/i18n/es.json +35 -1
  393. package/src/modules/communication_channels/i18n/ko.json +35 -1
  394. package/src/modules/communication_channels/i18n/pl.json +35 -1
  395. package/src/modules/communication_channels/lib/access-control.ts +42 -0
  396. package/src/modules/communication_channels/lib/adapter.ts +25 -0
  397. package/src/modules/communication_channels/lib/connect-channel.ts +61 -26
  398. package/src/modules/communication_channels/lib/push-adapter.ts +139 -0
  399. package/src/modules/communication_channels/lib/push-capabilities.ts +42 -0
  400. package/src/modules/communication_channels/lib/push-connect-error.ts +52 -0
  401. package/src/modules/communication_channels/lib/push-credential-errors.ts +58 -0
  402. package/src/modules/communication_channels/lib/push-envelope.ts +104 -0
  403. package/src/modules/communication_channels/lib/refcounted-client-cache.ts +123 -0
  404. package/src/modules/communication_channels/migrations/.snapshot-open-mercato.json +9 -0
  405. package/src/modules/communication_channels/migrations/Migration20260703123630_communication_channels.ts +20 -0
  406. package/src/modules/communication_channels/notifications.ts +4 -0
  407. package/src/modules/communication_channels/setup.ts +2 -0
  408. package/src/modules/configs/i18n/de.json +5 -1
  409. package/src/modules/configs/i18n/en.json +5 -1
  410. package/src/modules/configs/i18n/es.json +5 -1
  411. package/src/modules/configs/i18n/ko.json +5 -1
  412. package/src/modules/configs/i18n/pl.json +5 -1
  413. package/src/modules/configs/lib/upgrade-actions.ts +21 -0
  414. package/src/modules/customer_accounts/i18n/de.json +2 -1
  415. package/src/modules/customer_accounts/i18n/en.json +2 -1
  416. package/src/modules/customer_accounts/i18n/es.json +2 -1
  417. package/src/modules/customer_accounts/i18n/ko.json +2 -1
  418. package/src/modules/customer_accounts/i18n/pl.json +2 -1
  419. package/src/modules/customer_accounts/notifications.ts +12 -0
  420. package/src/modules/customers/i18n/de.json +2 -1
  421. package/src/modules/customers/i18n/en.json +2 -1
  422. package/src/modules/customers/i18n/es.json +2 -1
  423. package/src/modules/customers/i18n/ko.json +2 -1
  424. package/src/modules/customers/i18n/pl.json +2 -1
  425. package/src/modules/customers/notifications.ts +4 -0
  426. package/src/modules/devices/AGENTS.md +101 -0
  427. package/src/modules/devices/acl.ts +7 -0
  428. package/src/modules/devices/api/[id]/route.ts +180 -0
  429. package/src/modules/devices/api/admin/devices/[id]/route.ts +235 -0
  430. package/src/modules/devices/api/admin/devices/route.ts +160 -0
  431. package/src/modules/devices/api/auth.ts +13 -0
  432. package/src/modules/devices/api/deviceList.ts +54 -0
  433. package/src/modules/devices/api/deviceOps.ts +186 -0
  434. package/src/modules/devices/api/openapi.ts +23 -0
  435. package/src/modules/devices/api/route.ts +165 -0
  436. package/src/modules/devices/backend/devices/[id]/page.meta.ts +12 -0
  437. package/src/modules/devices/backend/devices/[id]/page.tsx +164 -0
  438. package/src/modules/devices/backend/devices/create/page.meta.ts +12 -0
  439. package/src/modules/devices/backend/devices/create/page.tsx +81 -0
  440. package/src/modules/devices/backend/devices/page.meta.ts +21 -0
  441. package/src/modules/devices/backend/devices/page.tsx +259 -0
  442. package/src/modules/devices/commands/deactivate.ts +126 -0
  443. package/src/modules/devices/commands/index.ts +3 -0
  444. package/src/modules/devices/commands/register.ts +199 -0
  445. package/src/modules/devices/commands/shared.ts +119 -0
  446. package/src/modules/devices/commands/update.ts +142 -0
  447. package/src/modules/devices/data/entities.ts +79 -0
  448. package/src/modules/devices/data/validators.ts +65 -0
  449. package/src/modules/devices/di.ts +9 -0
  450. package/src/modules/devices/encryption.ts +15 -0
  451. package/src/modules/devices/events.ts +16 -0
  452. package/src/modules/devices/i18n/de.json +45 -0
  453. package/src/modules/devices/i18n/en.json +45 -0
  454. package/src/modules/devices/i18n/es.json +45 -0
  455. package/src/modules/devices/i18n/ko.json +45 -0
  456. package/src/modules/devices/i18n/pl.json +45 -0
  457. package/src/modules/devices/index.ts +15 -0
  458. package/src/modules/devices/lib/operationMetadata.ts +35 -0
  459. package/src/modules/devices/migrations/.snapshot-open-mercato.json +200 -0
  460. package/src/modules/devices/migrations/Migration20260602120000.ts +15 -0
  461. package/src/modules/devices/migrations/Migration20260630120000.ts +20 -0
  462. package/src/modules/devices/migrations/Migration20260701120000.ts +13 -0
  463. package/src/modules/devices/migrations/Migration20260721120000.ts +18 -0
  464. package/src/modules/devices/setup.ts +11 -0
  465. package/src/modules/entities/cli.ts +16 -2
  466. package/src/modules/entities/migrations/Migration20260722120000.ts +38 -0
  467. package/src/modules/inbox_ops/i18n/de.json +2 -1
  468. package/src/modules/inbox_ops/i18n/en.json +2 -1
  469. package/src/modules/inbox_ops/i18n/es.json +2 -1
  470. package/src/modules/inbox_ops/i18n/ko.json +2 -1
  471. package/src/modules/inbox_ops/i18n/pl.json +2 -1
  472. package/src/modules/inbox_ops/notifications.ts +2 -0
  473. package/src/modules/messages/i18n/de.json +2 -1
  474. package/src/modules/messages/i18n/en.json +2 -1
  475. package/src/modules/messages/i18n/es.json +2 -1
  476. package/src/modules/messages/i18n/ko.json +2 -1
  477. package/src/modules/messages/i18n/pl.json +2 -1
  478. package/src/modules/messages/notifications.ts +2 -0
  479. package/src/modules/notifications/AGENTS.md +54 -0
  480. package/src/modules/notifications/acl.ts +2 -0
  481. package/src/modules/notifications/api/admin/preferences/route.ts +193 -0
  482. package/src/modules/notifications/api/channels/route.ts +54 -0
  483. package/src/modules/notifications/api/preferences/route.ts +131 -0
  484. package/src/modules/notifications/api/route.ts +8 -1
  485. package/src/modules/notifications/api/types/[id]/channels/[channel]/route.ts +226 -0
  486. package/src/modules/notifications/api/types/route.ts +239 -0
  487. package/src/modules/notifications/api/unread-count/route.ts +5 -1
  488. package/src/modules/notifications/backend/notifications/user-preferences/page.meta.ts +26 -0
  489. package/src/modules/notifications/backend/notifications/user-preferences/page.tsx +12 -0
  490. package/src/modules/notifications/backend/profile/notification-preferences/page.meta.ts +31 -0
  491. package/src/modules/notifications/backend/profile/notification-preferences/page.tsx +12 -0
  492. package/src/modules/notifications/data/entities.ts +144 -0
  493. package/src/modules/notifications/data/validators.ts +116 -0
  494. package/src/modules/notifications/di.ts +7 -0
  495. package/src/modules/notifications/events.ts +23 -0
  496. package/src/modules/notifications/frontend/NotificationPreferenceMatrix.tsx +287 -0
  497. package/src/modules/notifications/frontend/NotificationPreferencesPageClient.tsx +155 -0
  498. package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +273 -2
  499. package/src/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.tsx +214 -0
  500. package/src/modules/notifications/generators.ts +82 -0
  501. package/src/modules/notifications/i18n/de.json +43 -0
  502. package/src/modules/notifications/i18n/en.json +43 -0
  503. package/src/modules/notifications/i18n/es.json +43 -0
  504. package/src/modules/notifications/i18n/ko.json +43 -0
  505. package/src/modules/notifications/i18n/pl.json +44 -1
  506. package/src/modules/notifications/lib/delivery-strategies-registry.ts +30 -0
  507. package/src/modules/notifications/lib/deliveryStrategies.ts +40 -4
  508. package/src/modules/notifications/lib/derive-category.ts +15 -0
  509. package/src/modules/notifications/lib/notification-channel-registry.ts +59 -0
  510. package/src/modules/notifications/lib/notification-type-registry.ts +182 -0
  511. package/src/modules/notifications/lib/notificationCopy.ts +34 -0
  512. package/src/modules/notifications/lib/notificationFactory.ts +5 -1
  513. package/src/modules/notifications/lib/notificationMapper.ts +2 -0
  514. package/src/modules/notifications/lib/notificationPreferenceService.ts +138 -0
  515. package/src/modules/notifications/lib/notificationService.ts +129 -25
  516. package/src/modules/notifications/lib/notificationVisibility.ts +48 -0
  517. package/src/modules/notifications/lib/routeHelpers.ts +14 -1
  518. package/src/modules/notifications/lib/shouldDeliver.ts +112 -0
  519. package/src/modules/notifications/lib/strategies/email-delivery-strategy.ts +63 -0
  520. package/src/modules/notifications/lib/strategies/in-app-delivery-strategy.ts +21 -0
  521. package/src/modules/notifications/lib/typeCatalogue.ts +141 -0
  522. package/src/modules/notifications/lib/typeChannelSettings.ts +29 -0
  523. package/src/modules/notifications/lib/typeOverrides.ts +46 -0
  524. package/src/modules/notifications/migrations/.snapshot-open-mercato.json +874 -100
  525. package/src/modules/notifications/migrations/Migration20260625122947_notifications.ts +14 -0
  526. package/src/modules/notifications/migrations/Migration20260626120500_notifications_push_payload.ts +15 -0
  527. package/src/modules/notifications/migrations/Migration20260626130000_notifications_non_opt_out.ts +13 -0
  528. package/src/modules/notifications/migrations/Migration20260626140000_notifications_type_category_silent.ts +15 -0
  529. package/src/modules/notifications/migrations/Migration20260706120000_notifications_channels.ts +13 -0
  530. package/src/modules/notifications/migrations/Migration20260716123121_notifications.ts +14 -0
  531. package/src/modules/notifications/notification-channels.ts +30 -0
  532. package/src/modules/notifications/notifications.delivery-strategies.ts +15 -0
  533. package/src/modules/notifications/setup.ts +23 -1
  534. package/src/modules/notifications/subscribers/deliver-notification.ts +105 -71
  535. package/src/modules/notifications/subscribers/sync-notification-types.ts +18 -0
  536. package/src/modules/notifications/widgets/injection/profile-preferences-menu/widget.ts +35 -0
  537. package/src/modules/notifications/widgets/injection-table.ts +18 -0
  538. package/src/modules/push_notifications/AGENTS.md +92 -0
  539. package/src/modules/push_notifications/acl.ts +9 -0
  540. package/src/modules/push_notifications/api/custom-send/route.ts +175 -0
  541. package/src/modules/push_notifications/api/deliveries/[id]/route.ts +122 -0
  542. package/src/modules/push_notifications/api/deliveries/route.ts +58 -0
  543. package/src/modules/push_notifications/api/openapi.ts +22 -0
  544. package/src/modules/push_notifications/backend/push_notifications/[id]/page.meta.ts +9 -0
  545. package/src/modules/push_notifications/backend/push_notifications/[id]/page.tsx +149 -0
  546. package/src/modules/push_notifications/backend/push_notifications/page.meta.ts +21 -0
  547. package/src/modules/push_notifications/backend/push_notifications/page.tsx +239 -0
  548. package/src/modules/push_notifications/backend/push_notifications/send/page.meta.ts +24 -0
  549. package/src/modules/push_notifications/backend/push_notifications/send/page.tsx +327 -0
  550. package/src/modules/push_notifications/data/entities.ts +111 -0
  551. package/src/modules/push_notifications/data/extensions.ts +29 -0
  552. package/src/modules/push_notifications/data/validators.ts +102 -0
  553. package/src/modules/push_notifications/di.ts +20 -0
  554. package/src/modules/push_notifications/events.ts +21 -0
  555. package/src/modules/push_notifications/i18n/de.json +79 -0
  556. package/src/modules/push_notifications/i18n/en.json +79 -0
  557. package/src/modules/push_notifications/i18n/es.json +79 -0
  558. package/src/modules/push_notifications/i18n/ko.json +79 -0
  559. package/src/modules/push_notifications/i18n/pl.json +79 -0
  560. package/src/modules/push_notifications/index.ts +15 -0
  561. package/src/modules/push_notifications/lib/fake-provider-recorder.ts +154 -0
  562. package/src/modules/push_notifications/lib/push-delivery-strategy.ts +90 -0
  563. package/src/modules/push_notifications/lib/push-delivery.ts +455 -0
  564. package/src/modules/push_notifications/lib/push-fanout.ts +237 -0
  565. package/src/modules/push_notifications/lib/push-health.ts +47 -0
  566. package/src/modules/push_notifications/lib/push-reaper.ts +140 -0
  567. package/src/modules/push_notifications/lib/push-receipt-reaper.ts +210 -0
  568. package/src/modules/push_notifications/lib/push-stub-adapter.ts +80 -0
  569. package/src/modules/push_notifications/lib/queue.ts +74 -0
  570. package/src/modules/push_notifications/lib/reclaim-window.ts +16 -0
  571. package/src/modules/push_notifications/lib/send-custom-push.ts +100 -0
  572. package/src/modules/push_notifications/migrations/.snapshot-open-mercato.json +386 -0
  573. package/src/modules/push_notifications/migrations/Migration20260625150049_push_notifications.ts +16 -0
  574. package/src/modules/push_notifications/migrations/Migration20260626120000_push_notifications_silent.ts +13 -0
  575. package/src/modules/push_notifications/notifications.delivery-strategies.ts +8 -0
  576. package/src/modules/push_notifications/notifications.ts +50 -0
  577. package/src/modules/push_notifications/setup.ts +92 -0
  578. package/src/modules/push_notifications/workers/reclaim-stuck.worker.ts +78 -0
  579. package/src/modules/push_notifications/workers/send-push.worker.ts +34 -0
  580. package/src/modules/sales/i18n/de.json +1 -0
  581. package/src/modules/sales/i18n/en.json +1 -0
  582. package/src/modules/sales/i18n/es.json +1 -0
  583. package/src/modules/sales/i18n/ko.json +1 -0
  584. package/src/modules/sales/i18n/pl.json +1 -0
  585. package/src/modules/sales/notifications.ts +8 -0
  586. package/src/modules/staff/i18n/de.json +1 -0
  587. package/src/modules/staff/i18n/en.json +1 -0
  588. package/src/modules/staff/i18n/es.json +1 -0
  589. package/src/modules/staff/i18n/ko.json +1 -0
  590. package/src/modules/staff/i18n/pl.json +1 -0
  591. package/src/modules/staff/notifications.ts +6 -0
  592. package/src/modules/workflows/i18n/de.json +1 -0
  593. package/src/modules/workflows/i18n/en.json +1 -0
  594. package/src/modules/workflows/i18n/es.json +1 -0
  595. package/src/modules/workflows/i18n/ko.json +1 -0
  596. package/src/modules/workflows/i18n/pl.json +1 -0
  597. 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
+ }