@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,219 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
5
+ import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
6
+ import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
7
+ import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
8
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
9
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
10
+ import { createLogger } from "@open-mercato/shared/lib/logger";
11
+ import { UserDevice } from "../../../../data/entities.js";
12
+ import { updateDeviceSchema } from "../../../../data/validators.js";
13
+ import { isOrganizationReadAccessAllowed } from "@open-mercato/core/modules/directory/utils/organizationScopeGuard";
14
+ import { resolveDeviceActorUserId } from "../../../auth.js";
15
+ import { executeUpdate, executeDeactivate } from "../../../deviceOps.js";
16
+ const logger = createLogger("devices");
17
+ const metadata = {
18
+ GET: { requireAuth: true, requireFeatures: ["devices.admin"] },
19
+ PUT: { requireAuth: true, requireFeatures: ["devices.admin"] },
20
+ DELETE: { requireAuth: true, requireFeatures: ["devices.admin"] }
21
+ };
22
+ const paramsSchema = z.object({ id: z.string().uuid() });
23
+ async function loadDevice(container, id, tenantId) {
24
+ const em = container.resolve("em");
25
+ return findOneWithDecryption(em, UserDevice, { id, tenantId, deletedAt: null }, void 0, { tenantId });
26
+ }
27
+ function serializeDevice(device) {
28
+ return {
29
+ id: device.id,
30
+ user_id: device.userId,
31
+ device_id: device.deviceId,
32
+ platform: device.platform,
33
+ client_app_version: device.clientAppVersion ?? null,
34
+ os_version: device.osVersion ?? null,
35
+ push_provider: device.pushProvider ?? null,
36
+ push_token_updated_at: device.pushTokenUpdatedAt ? device.pushTokenUpdatedAt.toISOString() : null,
37
+ last_seen_at: device.lastSeenAt ? device.lastSeenAt.toISOString() : null,
38
+ created_at: device.createdAt ? device.createdAt.toISOString() : null,
39
+ updated_at: device.updatedAt ? device.updatedAt.toISOString() : null
40
+ };
41
+ }
42
+ async function GET(req, { params }) {
43
+ const { translate } = await resolveTranslations();
44
+ try {
45
+ const container = await createRequestContainer();
46
+ const auth = await getAuthFromRequest(req);
47
+ const actorUserId = resolveDeviceActorUserId(auth);
48
+ if (!auth || !auth.tenantId || !actorUserId) {
49
+ return NextResponse.json({ error: translate("devices.errors.unauthorized", "Unauthorized") }, { status: 401 });
50
+ }
51
+ const parsedParams = paramsSchema.safeParse({ id: params.id });
52
+ if (!parsedParams.success) {
53
+ return NextResponse.json({ error: translate("devices.errors.invalid_id", "Invalid device id") }, { status: 400 });
54
+ }
55
+ const device = await loadDevice(container, parsedParams.data.id, auth.tenantId);
56
+ if (!device) {
57
+ return NextResponse.json({ error: translate("devices.errors.not_found", "Device not found") }, { status: 404 });
58
+ }
59
+ const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
60
+ if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: device.organizationId ?? null })) {
61
+ return NextResponse.json({ error: translate("devices.errors.forbidden", "Access denied") }, { status: 403 });
62
+ }
63
+ return NextResponse.json({ item: serializeDevice(device) });
64
+ } catch (err) {
65
+ if (isCrudHttpError(err)) {
66
+ return NextResponse.json(err.body, { status: err.status });
67
+ }
68
+ logger.error("devices.admin.GET failed", { err });
69
+ return NextResponse.json({ error: translate("devices.errors.load_failed", "Failed to load device") }, { status: 500 });
70
+ }
71
+ }
72
+ async function PUT(req, { params }) {
73
+ const { translate } = await resolveTranslations();
74
+ try {
75
+ const container = await createRequestContainer();
76
+ const auth = await getAuthFromRequest(req);
77
+ const actorUserId = resolveDeviceActorUserId(auth);
78
+ if (!auth || !auth.tenantId || !actorUserId) {
79
+ return NextResponse.json({ error: translate("devices.errors.unauthorized", "Unauthorized") }, { status: 401 });
80
+ }
81
+ const parsedParams = paramsSchema.safeParse({ id: params.id });
82
+ if (!parsedParams.success) {
83
+ return NextResponse.json({ error: translate("devices.errors.invalid_id", "Invalid device id") }, { status: 400 });
84
+ }
85
+ const body = updateDeviceSchema.parse(await readJsonSafe(req, {}));
86
+ const device = await loadDevice(container, parsedParams.data.id, auth.tenantId);
87
+ if (!device) {
88
+ return NextResponse.json({ error: translate("devices.errors.not_found", "Device not found") }, { status: 404 });
89
+ }
90
+ const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
91
+ if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: device.organizationId ?? null })) {
92
+ return NextResponse.json({ error: translate("devices.errors.forbidden", "Access denied") }, { status: 403 });
93
+ }
94
+ const mctx = {
95
+ container,
96
+ auth,
97
+ scope,
98
+ organizationId: device.organizationId ?? null,
99
+ actorUserId,
100
+ request: req
101
+ };
102
+ return await executeUpdate(mctx, device, body);
103
+ } catch (err) {
104
+ if (err instanceof z.ZodError) {
105
+ return NextResponse.json(
106
+ { error: translate("devices.errors.invalid_payload", "Invalid device payload"), details: err.flatten() },
107
+ { status: 400 }
108
+ );
109
+ }
110
+ if (isCrudHttpError(err)) {
111
+ return NextResponse.json(err.body, { status: err.status });
112
+ }
113
+ logger.error("devices.admin.PUT failed", { err });
114
+ return NextResponse.json({ error: translate("devices.errors.update_failed", "Failed to update device") }, { status: 500 });
115
+ }
116
+ }
117
+ async function DELETE(req, { params }) {
118
+ const { translate } = await resolveTranslations();
119
+ try {
120
+ const container = await createRequestContainer();
121
+ const auth = await getAuthFromRequest(req);
122
+ const actorUserId = resolveDeviceActorUserId(auth);
123
+ if (!auth || !auth.tenantId || !actorUserId) {
124
+ return NextResponse.json({ error: translate("devices.errors.unauthorized", "Unauthorized") }, { status: 401 });
125
+ }
126
+ const parsedParams = paramsSchema.safeParse({ id: params.id });
127
+ if (!parsedParams.success) {
128
+ return NextResponse.json({ error: translate("devices.errors.invalid_id", "Invalid device id") }, { status: 400 });
129
+ }
130
+ const device = await loadDevice(container, parsedParams.data.id, auth.tenantId);
131
+ if (!device) {
132
+ return NextResponse.json({ error: translate("devices.errors.not_found", "Device not found") }, { status: 404 });
133
+ }
134
+ const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
135
+ if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: device.organizationId ?? null })) {
136
+ return NextResponse.json({ error: translate("devices.errors.forbidden", "Access denied") }, { status: 403 });
137
+ }
138
+ const mctx = {
139
+ container,
140
+ auth,
141
+ scope,
142
+ organizationId: device.organizationId ?? null,
143
+ actorUserId,
144
+ request: req
145
+ };
146
+ return await executeDeactivate(mctx, device);
147
+ } catch (err) {
148
+ if (isCrudHttpError(err)) {
149
+ return NextResponse.json(err.body, { status: err.status });
150
+ }
151
+ logger.error("devices.admin.DELETE failed", { err });
152
+ return NextResponse.json({ error: translate("devices.errors.delete_failed", "Failed to delete device") }, { status: 500 });
153
+ }
154
+ }
155
+ const okResponseSchema = z.object({ ok: z.literal(true), id: z.string().uuid().optional() });
156
+ const errorResponseSchema = z.object({ error: z.string() });
157
+ const detailResponseSchema = z.object({
158
+ item: z.object({
159
+ id: z.string().uuid(),
160
+ user_id: z.string().uuid(),
161
+ device_id: z.string(),
162
+ platform: z.enum(["ios", "android", "web"]),
163
+ client_app_version: z.string().nullable(),
164
+ os_version: z.string().nullable(),
165
+ push_provider: z.string().nullable(),
166
+ push_token_updated_at: z.string().nullable(),
167
+ last_seen_at: z.string().nullable(),
168
+ created_at: z.string().nullable(),
169
+ updated_at: z.string().nullable()
170
+ })
171
+ });
172
+ const openApi = {
173
+ tag: "Devices (admin)",
174
+ summary: "Admin: read, update or deactivate any device in the tenant",
175
+ methods: {
176
+ GET: {
177
+ summary: "Get any device",
178
+ description: "Admin: fetch a single device by id (push_token is never returned).",
179
+ responses: [{ status: 200, description: "Device", schema: detailResponseSchema }],
180
+ errors: [
181
+ { status: 400, description: "Invalid id", schema: errorResponseSchema },
182
+ { status: 401, description: "Unauthorized", schema: errorResponseSchema },
183
+ { status: 403, description: "Missing devices.admin", schema: errorResponseSchema },
184
+ { status: 404, description: "Device not found", schema: errorResponseSchema }
185
+ ]
186
+ },
187
+ PUT: {
188
+ summary: "Update any device",
189
+ description: "Admin: update last-seen, app/OS metadata, and push token for any device. Set pushToken to null to clear it.",
190
+ requestBody: { schema: updateDeviceSchema },
191
+ responses: [{ status: 200, description: "Device updated", schema: okResponseSchema }],
192
+ errors: [
193
+ { status: 400, description: "Invalid payload or id", schema: errorResponseSchema },
194
+ { status: 401, description: "Unauthorized", schema: errorResponseSchema },
195
+ { status: 403, description: "Missing devices.admin", schema: errorResponseSchema },
196
+ { status: 404, description: "Device not found", schema: errorResponseSchema }
197
+ ]
198
+ },
199
+ DELETE: {
200
+ summary: "Deactivate any device",
201
+ description: "Admin: soft-delete any device in the tenant.",
202
+ responses: [{ status: 200, description: "Device deactivated", schema: okResponseSchema }],
203
+ errors: [
204
+ { status: 400, description: "Invalid id", schema: errorResponseSchema },
205
+ { status: 401, description: "Unauthorized", schema: errorResponseSchema },
206
+ { status: 403, description: "Missing devices.admin", schema: errorResponseSchema },
207
+ { status: 404, description: "Device not found", schema: errorResponseSchema }
208
+ ]
209
+ }
210
+ }
211
+ };
212
+ export {
213
+ DELETE,
214
+ GET,
215
+ PUT,
216
+ metadata,
217
+ openApi
218
+ };
219
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../src/modules/devices/api/admin/devices/%5Bid%5D/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { UserDevice } from '../../../../data/entities'\nimport { updateDeviceSchema } from '../../../../data/validators'\nimport { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'\nimport { resolveDeviceActorUserId } from '../../../auth'\nimport { executeUpdate, executeDeactivate, type DeviceMutationContext } from '../../../deviceOps'\n\nconst logger = createLogger('devices')\n\n// Admin: read/update/deactivate ANY device in the tenant. Gated by devices.admin (no owner check).\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['devices.admin'] },\n PUT: { requireAuth: true, requireFeatures: ['devices.admin'] },\n DELETE: { requireAuth: true, requireFeatures: ['devices.admin'] },\n}\n\nconst paramsSchema = z.object({ id: z.string().uuid() })\n\nasync function loadDevice(\n container: Awaited<ReturnType<typeof createRequestContainer>>,\n id: string,\n tenantId: string,\n): Promise<UserDevice | null> {\n const em = container.resolve('em') as EntityManager\n // push_token is encrypted at rest; the org is not known until the row loads, so pass tenant only \u2014\n // the helper prefers each record's own tenant/org and treats this scope as a fallback.\n return findOneWithDecryption(em, UserDevice, { id, tenantId, deletedAt: null }, undefined, { tenantId })\n}\n\n// push_token is a secret and is never returned.\nfunction serializeDevice(device: UserDevice) {\n return {\n id: device.id,\n user_id: device.userId,\n device_id: device.deviceId,\n platform: device.platform,\n client_app_version: device.clientAppVersion ?? null,\n os_version: device.osVersion ?? null,\n push_provider: device.pushProvider ?? null,\n push_token_updated_at: device.pushTokenUpdatedAt ? device.pushTokenUpdatedAt.toISOString() : null,\n last_seen_at: device.lastSeenAt ? device.lastSeenAt.toISOString() : null,\n created_at: device.createdAt ? device.createdAt.toISOString() : null,\n updated_at: device.updatedAt ? device.updatedAt.toISOString() : null,\n }\n}\n\nexport async function GET(req: Request, { params }: { params: { id: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const actorUserId = resolveDeviceActorUserId(auth)\n if (!auth || !auth.tenantId || !actorUserId) {\n return NextResponse.json({ error: translate('devices.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n const parsedParams = paramsSchema.safeParse({ id: params.id })\n if (!parsedParams.success) {\n return NextResponse.json({ error: translate('devices.errors.invalid_id', 'Invalid device id') }, { status: 400 })\n }\n const device = await loadDevice(container, parsedParams.data.id, auth.tenantId)\n if (!device) {\n return NextResponse.json({ error: translate('devices.errors.not_found', 'Device not found') }, { status: 404 })\n }\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: device.organizationId ?? null })) {\n return NextResponse.json({ error: translate('devices.errors.forbidden', 'Access denied') }, { status: 403 })\n }\n return NextResponse.json({ item: serializeDevice(device) })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('devices.admin.GET failed', { err })\n return NextResponse.json({ error: translate('devices.errors.load_failed', 'Failed to load device') }, { status: 500 })\n }\n}\n\nexport async function PUT(req: Request, { params }: { params: { id: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const actorUserId = resolveDeviceActorUserId(auth)\n if (!auth || !auth.tenantId || !actorUserId) {\n return NextResponse.json({ error: translate('devices.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n const parsedParams = paramsSchema.safeParse({ id: params.id })\n if (!parsedParams.success) {\n return NextResponse.json({ error: translate('devices.errors.invalid_id', 'Invalid device id') }, { status: 400 })\n }\n\n const body = updateDeviceSchema.parse(await readJsonSafe(req, {}))\n const device = await loadDevice(container, parsedParams.data.id, auth.tenantId)\n if (!device) {\n return NextResponse.json({ error: translate('devices.errors.not_found', 'Device not found') }, { status: 404 })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: device.organizationId ?? null })) {\n return NextResponse.json({ error: translate('devices.errors.forbidden', 'Access denied') }, { status: 403 })\n }\n const mctx: DeviceMutationContext = {\n container,\n auth,\n scope,\n organizationId: device.organizationId ?? null,\n actorUserId,\n request: req,\n }\n return await executeUpdate(mctx, device, body)\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json(\n { error: translate('devices.errors.invalid_payload', 'Invalid device payload'), details: err.flatten() },\n { status: 400 },\n )\n }\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('devices.admin.PUT failed', { err })\n return NextResponse.json({ error: translate('devices.errors.update_failed', 'Failed to update device') }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const actorUserId = resolveDeviceActorUserId(auth)\n if (!auth || !auth.tenantId || !actorUserId) {\n return NextResponse.json({ error: translate('devices.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n const parsedParams = paramsSchema.safeParse({ id: params.id })\n if (!parsedParams.success) {\n return NextResponse.json({ error: translate('devices.errors.invalid_id', 'Invalid device id') }, { status: 400 })\n }\n\n const device = await loadDevice(container, parsedParams.data.id, auth.tenantId)\n if (!device) {\n return NextResponse.json({ error: translate('devices.errors.not_found', 'Device not found') }, { status: 404 })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: device.organizationId ?? null })) {\n return NextResponse.json({ error: translate('devices.errors.forbidden', 'Access denied') }, { status: 403 })\n }\n const mctx: DeviceMutationContext = {\n container,\n auth,\n scope,\n organizationId: device.organizationId ?? null,\n actorUserId,\n request: req,\n }\n return await executeDeactivate(mctx, device)\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('devices.admin.DELETE failed', { err })\n return NextResponse.json({ error: translate('devices.errors.delete_failed', 'Failed to delete device') }, { status: 500 })\n }\n}\n\nconst okResponseSchema = z.object({ ok: z.literal(true), id: z.string().uuid().optional() })\nconst errorResponseSchema = z.object({ error: z.string() })\nconst detailResponseSchema = z.object({\n item: z.object({\n id: z.string().uuid(),\n user_id: z.string().uuid(),\n device_id: z.string(),\n platform: z.enum(['ios', 'android', 'web']),\n client_app_version: z.string().nullable(),\n os_version: z.string().nullable(),\n push_provider: z.string().nullable(),\n push_token_updated_at: z.string().nullable(),\n last_seen_at: z.string().nullable(),\n created_at: z.string().nullable(),\n updated_at: z.string().nullable(),\n }),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Devices (admin)',\n summary: 'Admin: read, update or deactivate any device in the tenant',\n methods: {\n GET: {\n summary: 'Get any device',\n description: 'Admin: fetch a single device by id (push_token is never returned).',\n responses: [{ status: 200, description: 'Device', schema: detailResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid id', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 403, description: 'Missing devices.admin', schema: errorResponseSchema },\n { status: 404, description: 'Device not found', schema: errorResponseSchema },\n ],\n },\n PUT: {\n summary: 'Update any device',\n description: 'Admin: update last-seen, app/OS metadata, and push token for any device. Set pushToken to null to clear it.',\n requestBody: { schema: updateDeviceSchema },\n responses: [{ status: 200, description: 'Device updated', schema: okResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid payload or id', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 403, description: 'Missing devices.admin', schema: errorResponseSchema },\n { status: 404, description: 'Device not found', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Deactivate any device',\n description: 'Admin: soft-delete any device in the tenant.',\n responses: [{ status: 200, description: 'Device deactivated', schema: okResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid id', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 403, description: 'Missing devices.admin', schema: errorResponseSchema },\n { status: 404, description: 'Device not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,140 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
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 { isOrganizationReadAccessAllowed } from "@open-mercato/core/modules/directory/utils/organizationScopeGuard";
8
+ import { User } from "@open-mercato/core/modules/auth/data/entities";
9
+ import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
10
+ import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
11
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
12
+ import { createLogger } from "@open-mercato/shared/lib/logger";
13
+ import { E } from "../../../../../generated/entities.ids.generated.js";
14
+ import { UserDevice } from "../../../data/entities.js";
15
+ import {
16
+ registerDeviceAdminSchema,
17
+ registerDeviceCommandSchema
18
+ } from "../../../data/validators.js";
19
+ import { resolveDeviceActorUserId } from "../../auth.js";
20
+ import { createDevicesCrudOpenApi, createPagedListResponseSchema } from "../../openapi.js";
21
+ import { deviceListSchema, deviceListFields, deviceListSortFieldMap, deviceListItemSchema } from "../../deviceList.js";
22
+ import { executeRegister } from "../../deviceOps.js";
23
+ const logger = createLogger("devices");
24
+ const routeMetadata = {
25
+ GET: { requireAuth: true, requireFeatures: ["devices.admin"] },
26
+ POST: { requireAuth: true, requireFeatures: ["devices.admin"] }
27
+ };
28
+ const metadata = routeMetadata;
29
+ const crud = makeCrudRoute({
30
+ metadata: routeMetadata,
31
+ orm: {
32
+ entity: UserDevice,
33
+ idField: "id",
34
+ // organization_id stays nullable, but scoping follows the standard org-scoped pattern: the factory
35
+ // auto-filters by the caller's organization scope (null-aware in the query engine). Unrestricted
36
+ // admins see the whole tenant including null-org rows; org-restricted admins see only their orgs.
37
+ orgField: "organizationId",
38
+ tenantField: "tenantId",
39
+ softDeleteField: "deletedAt"
40
+ },
41
+ indexer: { entityType: E.devices.user_device },
42
+ // Same cache resource tag as the self route + commands, so any device write busts both list caches.
43
+ events: { module: "devices", entity: "user_device" },
44
+ list: {
45
+ schema: deviceListSchema,
46
+ entityId: E.devices.user_device,
47
+ fields: deviceListFields,
48
+ sortFieldMap: deviceListSortFieldMap,
49
+ // Tenant + org scope is enforced by the factory (orm.tenantField + orm.orgField); only the optional
50
+ // userId/platform narrowing is left to do here.
51
+ buildFilters: async (query) => {
52
+ const filters = {};
53
+ if (query.userId) filters.user_id = { $eq: query.userId };
54
+ if (query.platform) filters.platform = { $eq: query.platform };
55
+ return filters;
56
+ }
57
+ }
58
+ });
59
+ const GET = crud.GET;
60
+ async function POST(req) {
61
+ const { translate } = await resolveTranslations();
62
+ try {
63
+ const container = await createRequestContainer();
64
+ const auth = await getAuthFromRequest(req);
65
+ const actorUserId = resolveDeviceActorUserId(auth);
66
+ if (!auth || !auth.tenantId || !actorUserId) {
67
+ return NextResponse.json({ error: translate("devices.errors.unauthorized", "Unauthorized") }, { status: 401 });
68
+ }
69
+ const body = registerDeviceAdminSchema.parse(await readJsonSafe(req, {}));
70
+ const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
71
+ const organizationId = scope?.selectedId ?? auth.orgId ?? null;
72
+ const em = container.resolve("em");
73
+ const targetUser = await em.findOne(
74
+ User,
75
+ { id: body.userId, tenantId: auth.tenantId, deletedAt: null },
76
+ { fields: ["id", "organizationId"] }
77
+ );
78
+ if (!targetUser) {
79
+ return NextResponse.json(
80
+ { error: translate("devices.errors.user_not_found", "Target user not found in this tenant") },
81
+ { status: 400 }
82
+ );
83
+ }
84
+ if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: targetUser.organizationId ?? null })) {
85
+ return NextResponse.json({ error: translate("devices.errors.forbidden", "Access denied") }, { status: 403 });
86
+ }
87
+ const commandInput = registerDeviceCommandSchema.parse({
88
+ ...body,
89
+ tenantId: auth.tenantId,
90
+ organizationId,
91
+ userId: body.userId
92
+ });
93
+ const mctx = { container, auth, scope, organizationId, actorUserId, request: req };
94
+ return await executeRegister(mctx, commandInput);
95
+ } catch (err) {
96
+ if (err instanceof z.ZodError) {
97
+ return NextResponse.json(
98
+ { error: translate("devices.errors.invalid_payload", "Invalid device payload"), details: err.flatten() },
99
+ { status: 400 }
100
+ );
101
+ }
102
+ if (isCrudHttpError(err) && err.body?.code === "device_already_registered") {
103
+ return NextResponse.json(
104
+ { error: translate("devices.errors.already_registered", "This device is already registered") },
105
+ { status: 409 }
106
+ );
107
+ }
108
+ if (isCrudHttpError(err)) {
109
+ return NextResponse.json(err.body, { status: err.status });
110
+ }
111
+ logger.error("devices.admin.POST failed", { err });
112
+ return NextResponse.json(
113
+ { error: translate("devices.errors.register_failed", "Failed to register device") },
114
+ { status: 500 }
115
+ );
116
+ }
117
+ }
118
+ const registerResponseSchema = z.object({
119
+ id: z.string().uuid(),
120
+ deviceId: z.string(),
121
+ revived: z.boolean()
122
+ });
123
+ const openApi = createDevicesCrudOpenApi({
124
+ resourceName: "Device (admin)",
125
+ pluralName: "Devices (admin)",
126
+ querySchema: deviceListSchema,
127
+ listResponseSchema: createPagedListResponseSchema(deviceListItemSchema),
128
+ create: {
129
+ schema: registerDeviceAdminSchema,
130
+ responseSchema: registerResponseSchema,
131
+ description: "Admin: register (idempotently upsert) a device on behalf of any user in the tenant."
132
+ }
133
+ });
134
+ export {
135
+ GET,
136
+ POST,
137
+ metadata,
138
+ openApi
139
+ };
140
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/devices/api/admin/devices/route.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { E } from '#generated/entities.ids.generated'\nimport { UserDevice } from '../../../data/entities'\nimport {\n registerDeviceAdminSchema,\n registerDeviceCommandSchema,\n type RegisterDeviceCommandInput,\n} from '../../../data/validators'\nimport { resolveDeviceActorUserId } from '../../auth'\nimport { createDevicesCrudOpenApi, createPagedListResponseSchema } from '../../openapi'\nimport { deviceListSchema, deviceListFields, deviceListSortFieldMap, deviceListItemSchema } from '../../deviceList'\nimport { executeRegister, type DeviceMutationContext } from '../../deviceOps'\n\nconst logger = createLogger('devices')\n\n// Admin device administration: cross-user listing + register-on-behalf-of-user. Gated by devices.admin.\n// Self-serve registration/listing stays on /api/devices (scoped to the acting user).\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['devices.admin'] },\n POST: { requireAuth: true, requireFeatures: ['devices.admin'] },\n}\n\nexport const metadata = routeMetadata\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: UserDevice,\n idField: 'id',\n // organization_id stays nullable, but scoping follows the standard org-scoped pattern: the factory\n // auto-filters by the caller's organization scope (null-aware in the query engine). Unrestricted\n // admins see the whole tenant including null-org rows; org-restricted admins see only their orgs.\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n indexer: { entityType: E.devices.user_device },\n // Same cache resource tag as the self route + commands, so any device write busts both list caches.\n events: { module: 'devices', entity: 'user_device' },\n list: {\n schema: deviceListSchema,\n entityId: E.devices.user_device,\n fields: deviceListFields,\n sortFieldMap: deviceListSortFieldMap,\n // Tenant + org scope is enforced by the factory (orm.tenantField + orm.orgField); only the optional\n // userId/platform narrowing is left to do here.\n buildFilters: async (query) => {\n const filters: Record<string, unknown> = {}\n if (query.userId) filters.user_id = { $eq: query.userId }\n if (query.platform) filters.platform = { $eq: query.platform }\n return filters\n },\n },\n})\n\nexport const GET = crud.GET\n\nexport async function POST(req: Request) {\n const { translate } = await resolveTranslations()\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const actorUserId = resolveDeviceActorUserId(auth)\n if (!auth || !auth.tenantId || !actorUserId) {\n return NextResponse.json({ error: translate('devices.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n\n const body = registerDeviceAdminSchema.parse(await readJsonSafe(req, {}))\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n\n // Validate the on-behalf-of target before registering. `body.userId` is caller-supplied, and the\n // fan-out delivers by `(tenant, org, user)` \u2014 so without this an admin could register their OWN push\n // token under an arbitrary `userId` (even a non-existent or cross-tenant one) and start receiving that\n // user's notifications. Require a real, active user in the admin's tenant who is also within the\n // admin's organization scope (org-restricted admins cannot reach users in orgs they can't access).\n // Only non-encrypted columns are selected, so no decryption is needed.\n const em = container.resolve('em') as EntityManager\n const targetUser = await em.findOne(\n User,\n { id: body.userId, tenantId: auth.tenantId, deletedAt: null },\n { fields: ['id', 'organizationId'] },\n )\n if (!targetUser) {\n return NextResponse.json(\n { error: translate('devices.errors.user_not_found', 'Target user not found in this tenant') },\n { status: 400 },\n )\n }\n if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: targetUser.organizationId ?? null })) {\n return NextResponse.json({ error: translate('devices.errors.forbidden', 'Access denied') }, { status: 403 })\n }\n\n // Register is an idempotent upsert keyed per (tenant, org, user, device). organizationId is the\n // admin's own resolved scope, so the upsert can only ever touch a row inside an org the admin can\n // access \u2014 no cross-org guard is needed here.\n const commandInput = registerDeviceCommandSchema.parse({\n ...body,\n tenantId: auth.tenantId,\n organizationId,\n userId: body.userId,\n } satisfies RegisterDeviceCommandInput)\n\n // actorUserId is the admin (for the mutation guard/audit); the device is owned by body.userId.\n const mctx: DeviceMutationContext = { container, auth, scope, organizationId, actorUserId, request: req }\n return await executeRegister(mctx, commandInput)\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json(\n { error: translate('devices.errors.invalid_payload', 'Invalid device payload'), details: err.flatten() },\n { status: 400 },\n )\n }\n if (isCrudHttpError(err) && (err.body as { code?: string } | undefined)?.code === 'device_already_registered') {\n return NextResponse.json(\n { error: translate('devices.errors.already_registered', 'This device is already registered') },\n { status: 409 },\n )\n }\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('devices.admin.POST failed', { err })\n return NextResponse.json(\n { error: translate('devices.errors.register_failed', 'Failed to register device') },\n { status: 500 },\n )\n }\n}\n\nconst registerResponseSchema = z.object({\n id: z.string().uuid(),\n deviceId: z.string(),\n revived: z.boolean(),\n})\n\nexport const openApi: OpenApiRouteDoc = createDevicesCrudOpenApi({\n resourceName: 'Device (admin)',\n pluralName: 'Devices (admin)',\n querySchema: deviceListSchema,\n listResponseSchema: createPagedListResponseSchema(deviceListItemSchema),\n create: {\n schema: registerDeviceAdminSchema,\n responseSchema: registerResponseSchema,\n description: 'Admin: register (idempotently upsert) a device on behalf of any user in the tenant.',\n },\n})\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,9 @@
1
+ function resolveDeviceActorUserId(auth) {
2
+ if (!auth || auth.isApiKey) return null;
3
+ const subjectId = typeof auth.sub === "string" ? auth.sub.trim() : "";
4
+ return subjectId || null;
5
+ }
6
+ export {
7
+ resolveDeviceActorUserId
8
+ };
9
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/devices/api/auth.ts"],
4
+ "sourcesContent": ["import type { AuthContext } from '@open-mercato/shared/lib/auth/server'\n\n/**\n * Resolves the acting user id for device routes. Device ownership is per-user, so API-key\n * principals (which have no human user) are not valid device actors and yield null. Mirrors the\n * `auth.isApiKey ? null : auth.sub` convention used across the codebase; the target user id is\n * validated as a UUID by the command schemas downstream.\n */\nexport function resolveDeviceActorUserId(auth: AuthContext | null): string | null {\n if (!auth || auth.isApiKey) return null\n const subjectId = typeof auth.sub === 'string' ? auth.sub.trim() : ''\n return subjectId || null\n}\n"],
5
+ "mappings": "AAQO,SAAS,yBAAyB,MAAyC;AAChF,MAAI,CAAC,QAAQ,KAAK,SAAU,QAAO;AACnC,QAAM,YAAY,OAAO,KAAK,QAAQ,WAAW,KAAK,IAAI,KAAK,IAAI;AACnE,SAAO,aAAa;AACtB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,53 @@
1
+ import { z } from "zod";
2
+ const deviceListSchema = z.object({
3
+ page: z.coerce.number().min(1).default(1),
4
+ pageSize: z.coerce.number().min(1).max(100).default(50),
5
+ platform: z.enum(["ios", "android", "web"]).optional(),
6
+ userId: z.string().uuid().optional(),
7
+ sortField: z.string().optional(),
8
+ sortDir: z.enum(["asc", "desc"]).optional()
9
+ }).passthrough();
10
+ const deviceListFields = [
11
+ "id",
12
+ "tenant_id",
13
+ "organization_id",
14
+ "user_id",
15
+ "device_id",
16
+ "platform",
17
+ "client_app_version",
18
+ "os_version",
19
+ "locale",
20
+ "push_provider",
21
+ "push_token_updated_at",
22
+ "last_seen_at",
23
+ "created_at",
24
+ "updated_at"
25
+ ];
26
+ const deviceListSortFieldMap = {
27
+ lastSeenAt: "last_seen_at",
28
+ createdAt: "created_at",
29
+ updatedAt: "updated_at"
30
+ };
31
+ const deviceListItemSchema = z.object({
32
+ id: z.string().uuid(),
33
+ tenant_id: z.string().uuid(),
34
+ organization_id: z.string().uuid().nullable().optional(),
35
+ user_id: z.string().uuid(),
36
+ device_id: z.string(),
37
+ platform: z.enum(["ios", "android", "web"]),
38
+ client_app_version: z.string().nullable().optional(),
39
+ os_version: z.string().nullable().optional(),
40
+ locale: z.string().nullable().optional(),
41
+ push_provider: z.string().nullable().optional(),
42
+ push_token_updated_at: z.string().nullable().optional(),
43
+ last_seen_at: z.string().nullable().optional(),
44
+ created_at: z.string().nullable().optional(),
45
+ updated_at: z.string().nullable().optional()
46
+ });
47
+ export {
48
+ deviceListFields,
49
+ deviceListItemSchema,
50
+ deviceListSchema,
51
+ deviceListSortFieldMap
52
+ };
53
+ //# sourceMappingURL=deviceList.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/devices/api/deviceList.ts"],
4
+ "sourcesContent": ["import { z } from 'zod'\n\n// Shared list contract for the self-serve (`/api/devices`) and admin (`/api/devices/admin/devices`)\n// list routes. push_token is a secret and is never part of the exposed field set.\nexport const deviceListSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n platform: z.enum(['ios', 'android', 'web']).optional(),\n userId: z.string().uuid().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n })\n .passthrough()\n\nexport const deviceListFields: string[] = [\n 'id',\n 'tenant_id',\n 'organization_id',\n 'user_id',\n 'device_id',\n 'platform',\n 'client_app_version',\n 'os_version',\n 'locale',\n 'push_provider',\n 'push_token_updated_at',\n 'last_seen_at',\n 'created_at',\n 'updated_at',\n]\n\nexport const deviceListSortFieldMap: Record<string, string> = {\n lastSeenAt: 'last_seen_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n}\n\nexport const deviceListItemSchema = z.object({\n id: z.string().uuid(),\n tenant_id: z.string().uuid(),\n organization_id: z.string().uuid().nullable().optional(),\n user_id: z.string().uuid(),\n device_id: z.string(),\n platform: z.enum(['ios', 'android', 'web']),\n client_app_version: z.string().nullable().optional(),\n os_version: z.string().nullable().optional(),\n locale: z.string().nullable().optional(),\n push_provider: z.string().nullable().optional(),\n push_token_updated_at: z.string().nullable().optional(),\n last_seen_at: z.string().nullable().optional(),\n created_at: z.string().nullable().optional(),\n updated_at: z.string().nullable().optional(),\n})\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAIX,MAAM,mBAAmB,EAC7B,OAAO;AAAA,EACN,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,EACxC,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AAAA,EACtD,UAAU,EAAE,KAAK,CAAC,OAAO,WAAW,KAAK,CAAC,EAAE,SAAS;AAAA,EACrD,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EACnC,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,SAAS,EAAE,KAAK,CAAC,OAAO,MAAM,CAAC,EAAE,SAAS;AAC5C,CAAC,EACA,YAAY;AAER,MAAM,mBAA6B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,MAAM,yBAAiD;AAAA,EAC5D,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,WAAW;AACb;AAEO,MAAM,uBAAuB,EAAE,OAAO;AAAA,EAC3C,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,WAAW,EAAE,OAAO,EAAE,KAAK;AAAA,EAC3B,iBAAiB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,EACvD,SAAS,EAAE,OAAO,EAAE,KAAK;AAAA,EACzB,WAAW,EAAE,OAAO;AAAA,EACpB,UAAU,EAAE,KAAK,CAAC,OAAO,WAAW,KAAK,CAAC;AAAA,EAC1C,oBAAoB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EACnD,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC3C,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EACvC,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC9C,uBAAuB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EACtD,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC7C,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC3C,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAC7C,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,118 @@
1
+ import { NextResponse } from "next/server";
2
+ import {
3
+ runCrudMutationGuardAfterSuccess,
4
+ validateCrudMutationGuard
5
+ } from "@open-mercato/shared/lib/crud/mutation-guard";
6
+ import { enforceCommandOptimisticLock } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
7
+ import { attachOperationMetadataHeader } from "../lib/operationMetadata.js";
8
+ const RESOURCE_KIND = "devices.user_device";
9
+ function redactMutationPayload(payload) {
10
+ if (!payload || !("pushToken" in payload)) return payload;
11
+ const { pushToken: _pushToken, ...rest } = payload;
12
+ return rest;
13
+ }
14
+ function commandCtx(mctx) {
15
+ return {
16
+ container: mctx.container,
17
+ auth: mctx.auth,
18
+ organizationScope: mctx.scope ?? null,
19
+ selectedOrganizationId: mctx.organizationId,
20
+ organizationIds: mctx.scope?.filterIds ?? (mctx.auth.orgId ? [mctx.auth.orgId] : null),
21
+ request: mctx.request
22
+ };
23
+ }
24
+ async function runGuards(mctx, operation, resourceId, mutationPayload) {
25
+ return validateCrudMutationGuard(mctx.container, {
26
+ tenantId: mctx.auth.tenantId,
27
+ organizationId: mctx.organizationId,
28
+ userId: mctx.actorUserId,
29
+ resourceKind: RESOURCE_KIND,
30
+ resourceId,
31
+ operation,
32
+ requestMethod: mctx.request.method,
33
+ requestHeaders: mctx.request.headers,
34
+ mutationPayload
35
+ });
36
+ }
37
+ async function runAfter(mctx, guardResult, operation, resourceId) {
38
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
39
+ await runCrudMutationGuardAfterSuccess(mctx.container, {
40
+ tenantId: mctx.auth.tenantId,
41
+ organizationId: mctx.organizationId,
42
+ userId: mctx.actorUserId,
43
+ resourceKind: RESOURCE_KIND,
44
+ resourceId,
45
+ operation,
46
+ requestMethod: mctx.request.method,
47
+ requestHeaders: mctx.request.headers,
48
+ metadata: guardResult.metadata ?? null
49
+ });
50
+ }
51
+ }
52
+ async function executeRegister(mctx, commandInput) {
53
+ const guardResourceId = `${commandInput.userId}:${commandInput.deviceId}`;
54
+ const guardResult = await runGuards(mctx, "create", guardResourceId, redactMutationPayload(commandInput));
55
+ if (guardResult && !guardResult.ok) return NextResponse.json(guardResult.body, { status: guardResult.status });
56
+ const commandBus = mctx.container.resolve("commandBus");
57
+ const { result, logEntry } = await commandBus.execute("devices.user_devices.register", { input: commandInput, ctx: commandCtx(mctx) });
58
+ await runAfter(mctx, guardResult, "create", guardResourceId);
59
+ const response = NextResponse.json(
60
+ { id: result.id, deviceId: result.deviceId, revived: result.revived },
61
+ { status: 201 }
62
+ );
63
+ attachOperationMetadataHeader(response, logEntry, { resourceKind: RESOURCE_KIND, resourceId: result.id });
64
+ return response;
65
+ }
66
+ async function executeUpdate(mctx, device, body) {
67
+ enforceCommandOptimisticLock({
68
+ resourceKind: RESOURCE_KIND,
69
+ resourceId: device.id,
70
+ current: device.updatedAt,
71
+ request: mctx.request
72
+ });
73
+ const guardResult = await runGuards(mctx, "update", device.id, redactMutationPayload(body));
74
+ if (guardResult && !guardResult.ok) return NextResponse.json(guardResult.body, { status: guardResult.status });
75
+ const commandBus = mctx.container.resolve("commandBus");
76
+ const { logEntry } = await commandBus.execute("devices.user_devices.update", {
77
+ input: {
78
+ ...body,
79
+ id: device.id,
80
+ tenantId: device.tenantId,
81
+ userId: device.userId,
82
+ organizationId: device.organizationId ?? null
83
+ },
84
+ ctx: commandCtx(mctx)
85
+ });
86
+ await runAfter(mctx, guardResult, "update", device.id);
87
+ const response = NextResponse.json({ ok: true, id: device.id });
88
+ attachOperationMetadataHeader(response, logEntry, { resourceKind: RESOURCE_KIND, resourceId: device.id });
89
+ return response;
90
+ }
91
+ async function executeDeactivate(mctx, device) {
92
+ const guardResult = await runGuards(mctx, "delete", device.id);
93
+ if (guardResult && !guardResult.ok) return NextResponse.json(guardResult.body, { status: guardResult.status });
94
+ const commandBus = mctx.container.resolve("commandBus");
95
+ const { logEntry } = await commandBus.execute(
96
+ "devices.user_devices.deactivate",
97
+ {
98
+ input: {
99
+ id: device.id,
100
+ tenantId: device.tenantId,
101
+ userId: device.userId,
102
+ organizationId: device.organizationId ?? null
103
+ },
104
+ ctx: commandCtx(mctx)
105
+ }
106
+ );
107
+ await runAfter(mctx, guardResult, "delete", device.id);
108
+ const response = NextResponse.json({ ok: true });
109
+ attachOperationMetadataHeader(response, logEntry, { resourceKind: RESOURCE_KIND, resourceId: device.id });
110
+ return response;
111
+ }
112
+ export {
113
+ executeDeactivate,
114
+ executeRegister,
115
+ executeUpdate,
116
+ redactMutationPayload
117
+ };
118
+ //# sourceMappingURL=deviceOps.js.map