@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,245 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import Link from "next/link";
5
+ import { useRouter } from "next/navigation";
6
+ import { Plus, X } from "lucide-react";
7
+ import { Page, PageBody } from "@open-mercato/ui/backend/Page";
8
+ import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
9
+ import { apiCall, apiCallOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
10
+ import { flash } from "@open-mercato/ui/backend/FlashMessages";
11
+ import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@open-mercato/ui/primitives/select";
12
+ import { Input } from "@open-mercato/ui/primitives/input";
13
+ import { IconButton } from "@open-mercato/ui/primitives/icon-button";
14
+ import { Button } from "@open-mercato/ui/primitives/button";
15
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
16
+ const DELIVERIES_HREF = "/backend/push_notifications";
17
+ const FORM_ID = "push-custom-send-form";
18
+ const ALL_DEVICES = "__all__";
19
+ function DeviceField({ value, setValue, setFormValue, values, onState }) {
20
+ const t = useT();
21
+ const userId = typeof values?.userId === "string" ? values.userId : "";
22
+ const mode = typeof values?.mode === "string" ? values.mode : "visible";
23
+ const selected = typeof value === "string" && value ? value : "";
24
+ const [devices, setDevices] = React.useState([]);
25
+ const [loading, setLoading] = React.useState(false);
26
+ React.useEffect(() => {
27
+ onState?.({ userId, count: devices.length, loading });
28
+ }, [userId, devices.length, loading, onState]);
29
+ React.useEffect(() => {
30
+ if (!userId) {
31
+ setDevices([]);
32
+ if (selected) setValue("");
33
+ return;
34
+ }
35
+ let cancelled = false;
36
+ setLoading(true);
37
+ const params = new URLSearchParams({ userId, pageSize: "50" });
38
+ apiCall(
39
+ `/api/devices/admin/devices?${params.toString()}`,
40
+ { headers: { "x-om-forbidden-redirect": "0" } },
41
+ { fallback: null }
42
+ ).catch(() => null).then((call) => {
43
+ if (cancelled) return;
44
+ const items = (call && call.ok ? call.result?.items : []) ?? [];
45
+ const opts = items.filter((d) => !!d && typeof d.id === "string" && !!d.push_provider).map((d) => ({ id: d.id, label: `${d.device_id} \xB7 ${d.platform}${d.push_provider ? ` \xB7 ${d.push_provider}` : ""}`, platform: d.platform }));
46
+ setDevices(opts);
47
+ if (selected && !opts.some((o) => o.id === selected)) setValue("");
48
+ }).finally(() => {
49
+ if (!cancelled) setLoading(false);
50
+ });
51
+ return () => {
52
+ cancelled = true;
53
+ };
54
+ }, [userId]);
55
+ const selectedPlatform = React.useMemo(() => devices.find((d) => d.id === selected)?.platform, [devices, selected]);
56
+ React.useEffect(() => {
57
+ const show = mode !== "silent" && selectedPlatform !== "ios";
58
+ if (values?.showChannelId !== show) setFormValue?.("showChannelId", show);
59
+ }, [mode, selectedPlatform, values?.showChannelId, setFormValue]);
60
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
61
+ /* @__PURE__ */ jsxs(Select, { value: selected || ALL_DEVICES, onValueChange: (v) => setValue(v === ALL_DEVICES ? "" : v), disabled: !userId, children: [
62
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: `${t("push_notifications.send.deviceAll")} (${devices.length})` }) }),
63
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
64
+ /* @__PURE__ */ jsx(SelectItem, { value: ALL_DEVICES, children: `${t("push_notifications.send.deviceAll")} (${devices.length})` }),
65
+ devices.map((d) => /* @__PURE__ */ jsx(SelectItem, { value: d.id, children: d.label }, d.id))
66
+ ] })
67
+ ] }),
68
+ /* @__PURE__ */ jsx("p", { className: `text-xs ${userId && !loading && devices.length === 0 ? "text-status-warning-text" : "text-muted-foreground"}`, children: !userId ? t("push_notifications.send.deviceSelectUserFirst") : loading ? t("common.loading", "Loading\u2026") : devices.length === 0 ? t("push_notifications.send.deviceNone") : t("push_notifications.send.deviceHint") })
69
+ ] });
70
+ }
71
+ function DataField({ value, setValue }) {
72
+ const t = useT();
73
+ const pairs = Array.isArray(value) ? value : [];
74
+ const update = (next) => setValue(next);
75
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
76
+ pairs.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("push_notifications.send.dataEmpty") }) : null,
77
+ pairs.map((pair, index) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
78
+ /* @__PURE__ */ jsx(
79
+ Input,
80
+ {
81
+ placeholder: t("push_notifications.send.dataKey"),
82
+ value: pair.key,
83
+ onChange: (e) => update(pairs.map((p, i) => i === index ? { ...p, key: e.target.value } : p))
84
+ }
85
+ ),
86
+ /* @__PURE__ */ jsx(
87
+ Input,
88
+ {
89
+ placeholder: t("push_notifications.send.dataValue"),
90
+ value: pair.value,
91
+ onChange: (e) => update(pairs.map((p, i) => i === index ? { ...p, value: e.target.value } : p))
92
+ }
93
+ ),
94
+ /* @__PURE__ */ jsx(
95
+ IconButton,
96
+ {
97
+ type: "button",
98
+ variant: "ghost",
99
+ "aria-label": t("push_notifications.send.dataRemove"),
100
+ onClick: () => update(pairs.filter((_, i) => i !== index)),
101
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
102
+ }
103
+ )
104
+ ] }, index)),
105
+ /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => update([...pairs, { key: "", value: "" }]), children: [
106
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4 mr-1" }),
107
+ t("push_notifications.send.dataAdd")
108
+ ] })
109
+ ] });
110
+ }
111
+ function PushCustomSendPage() {
112
+ const router = useRouter();
113
+ const t = useT();
114
+ const [deviceState, setDeviceState] = React.useState({ userId: "", count: 0, loading: false });
115
+ const [submitting, setSubmitting] = React.useState(false);
116
+ const reportDeviceState = React.useCallback((next) => {
117
+ setDeviceState((prev) => prev.userId === next.userId && prev.count === next.count && prev.loading === next.loading ? prev : next);
118
+ }, []);
119
+ const loadUserOptions = React.useCallback(async (query) => {
120
+ const params = new URLSearchParams();
121
+ params.set("page", "1");
122
+ params.set("pageSize", "20");
123
+ if (query && query.trim().length > 0) params.set("search", query.trim());
124
+ const call = await apiCall(
125
+ `/api/auth/users?${params.toString()}`,
126
+ { headers: { "x-om-forbidden-redirect": "0" } },
127
+ { fallback: null }
128
+ ).catch(() => null);
129
+ if (!call || !call.ok) return [];
130
+ return (call.result?.items ?? []).flatMap((item) => {
131
+ if (!item || typeof item.id !== "string" || !item.id.trim()) return [];
132
+ const name = typeof item.name === "string" && item.name.trim() ? item.name.trim() : null;
133
+ const email = typeof item.email === "string" && item.email.trim() ? item.email.trim() : null;
134
+ const label = name && email ? `${name} \u2014 ${email}` : email ?? name ?? item.id;
135
+ return [{ value: item.id, label }];
136
+ });
137
+ }, []);
138
+ const visibleOnly = { field: "mode", equals: "visible" };
139
+ const fields = React.useMemo(() => [
140
+ { id: "userId", label: t("push_notifications.send.userId"), type: "combobox", required: true, description: t("push_notifications.send.userIdHint"), loadOptions: loadUserOptions },
141
+ { id: "deviceId", label: t("push_notifications.send.device"), type: "custom", component: (props) => /* @__PURE__ */ jsx(DeviceField, { ...props, onState: reportDeviceState }) },
142
+ {
143
+ id: "mode",
144
+ label: t("push_notifications.send.mode"),
145
+ type: "select",
146
+ required: true,
147
+ description: t("push_notifications.send.modeHint"),
148
+ options: [
149
+ { value: "visible", label: t("push_notifications.send.modeVisible") },
150
+ { value: "silent", label: t("push_notifications.send.modeSilent") }
151
+ ]
152
+ },
153
+ { id: "title", label: t("push_notifications.send.title"), type: "text", required: true, visibleWhen: visibleOnly },
154
+ { id: "body", label: t("push_notifications.send.body"), type: "textarea", visibleWhen: visibleOnly },
155
+ { id: "data", label: t("push_notifications.send.data"), type: "custom", description: t("push_notifications.send.dataHint"), component: (props) => /* @__PURE__ */ jsx(DataField, { ...props }) },
156
+ { id: "sound", label: t("push_notifications.send.sound"), type: "text", visibleWhen: visibleOnly },
157
+ { id: "badge", label: t("push_notifications.send.badge"), type: "number", visibleWhen: visibleOnly },
158
+ { id: "image", label: t("push_notifications.send.image"), type: "text", visibleWhen: visibleOnly },
159
+ {
160
+ id: "priority",
161
+ label: t("push_notifications.send.priority"),
162
+ type: "select",
163
+ options: [
164
+ { value: "", label: t("push_notifications.send.priorityDefault") },
165
+ { value: "high", label: t("push_notifications.send.priorityHigh") },
166
+ { value: "normal", label: t("push_notifications.send.priorityNormal") }
167
+ ]
168
+ },
169
+ { id: "channelId", label: t("push_notifications.send.channelId"), type: "text", visibleWhen: { field: "showChannelId", equals: true } }
170
+ ], [t, loadUserOptions, reportDeviceState]);
171
+ return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsxs(PageBody, { children: [
172
+ /* @__PURE__ */ jsx(
173
+ CrudForm,
174
+ {
175
+ title: t("push_notifications.send.pageTitle"),
176
+ backHref: DELIVERIES_HREF,
177
+ formId: FORM_ID,
178
+ hideFooterActions: true,
179
+ fields,
180
+ initialValues: { userId: "", deviceId: "", mode: "visible", title: "", body: "", data: [], sound: "", badge: "", image: "", priority: "", channelId: "", showChannelId: true },
181
+ submitLabel: t("push_notifications.send.submit"),
182
+ onSubmit: async (values) => {
183
+ setSubmitting(true);
184
+ try {
185
+ const silent = values.mode === "silent";
186
+ const body = (values.body ?? "").trim();
187
+ const dataMap = {};
188
+ for (const pair of Array.isArray(values.data) ? values.data : []) {
189
+ const key = (pair?.key ?? "").trim();
190
+ if (key) dataMap[key] = `${pair?.value ?? ""}`;
191
+ }
192
+ const hasData = Object.keys(dataMap).length > 0;
193
+ const pushOptions = {};
194
+ if (!silent) {
195
+ if (typeof values.sound === "string" && values.sound.trim()) pushOptions.sound = values.sound.trim();
196
+ const badgeNum = typeof values.badge === "number" ? values.badge : Number.parseInt(String(values.badge ?? ""), 10);
197
+ if (Number.isFinite(badgeNum) && badgeNum >= 0) pushOptions.badge = badgeNum;
198
+ if (typeof values.image === "string" && values.image.trim()) pushOptions.image = values.image.trim();
199
+ if (typeof values.channelId === "string" && values.channelId.trim()) pushOptions.channelId = values.channelId.trim();
200
+ }
201
+ if (values.priority === "high" || values.priority === "normal") pushOptions.priority = values.priority;
202
+ const hasOptions = Object.keys(pushOptions).length > 0;
203
+ const title = silent ? (values.title ?? "").trim() || "Silent push" : (values.title ?? "").trim();
204
+ await apiCallOrThrow("/api/push_notifications/custom-send", {
205
+ method: "POST",
206
+ headers: { "content-type": "application/json" },
207
+ body: JSON.stringify({
208
+ recipientUserId: (values.userId ?? "").trim(),
209
+ deviceId: (values.deviceId ?? "").trim() || void 0,
210
+ title,
211
+ body: !silent && body.length > 0 ? body : void 0,
212
+ silent,
213
+ data: hasData ? dataMap : void 0,
214
+ pushOptions: hasOptions ? pushOptions : void 0
215
+ })
216
+ });
217
+ flash(t("push_notifications.send.success"), "success");
218
+ router.push(DELIVERIES_HREF);
219
+ } finally {
220
+ setSubmitting(false);
221
+ }
222
+ }
223
+ }
224
+ ),
225
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center justify-between gap-3", children: [
226
+ /* @__PURE__ */ jsx("div", { className: "text-xs", children: !deviceState.userId ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("push_notifications.send.deviceSelectUserFirst") }) : deviceState.count === 0 && !deviceState.loading ? /* @__PURE__ */ jsx("span", { className: "text-status-warning-text", children: t("push_notifications.send.deviceNone") }) : null }),
227
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
228
+ /* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", children: /* @__PURE__ */ jsx(Link, { href: DELIVERIES_HREF, children: t("common.cancel", "Cancel") }) }),
229
+ /* @__PURE__ */ jsx(
230
+ Button,
231
+ {
232
+ type: "submit",
233
+ form: FORM_ID,
234
+ disabled: !deviceState.userId || deviceState.count === 0 || deviceState.loading || submitting,
235
+ children: t("push_notifications.send.submit")
236
+ }
237
+ )
238
+ ] })
239
+ ] })
240
+ ] }) });
241
+ }
242
+ export {
243
+ PushCustomSendPage as default
244
+ };
245
+ //# sourceMappingURL=page.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/push_notifications/backend/push_notifications/send/page.tsx"],
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { Plus, X } from 'lucide-react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFieldOption, type CrudCustomFieldRenderProps } from '@open-mercato/ui/backend/CrudForm'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@open-mercato/ui/primitives/select'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype KeyValue = { key: string; value: string }\n\ntype FormValues = {\n userId: string\n deviceId: string\n mode: string\n title: string\n body: string\n data: KeyValue[]\n sound: string\n badge: number | string\n image: string\n priority: string\n channelId: string\n // form-only helper (never sent): drives channelId visibility (hidden in silent / for an iOS target)\n showChannelId: boolean\n}\n\nconst DELIVERIES_HREF = '/backend/push_notifications'\nconst FORM_ID = 'push-custom-send-form'\n// Radix Select forbids an empty-string item value, so \"all devices\" uses a sentinel that we map\n// back to '' (the form value that means \"fan out to every push-capable device\").\nconst ALL_DEVICES = '__all__'\n\ntype DeviceOption = { id: string; label: string; platform: string }\n// Reported up to the page so the (custom) submit button can disable when there is nothing to send to.\ntype DeviceState = { userId: string; count: number; loading: boolean }\n\n// Dependent picker: loads the *selected recipient's* push-capable devices (admin devices API,\n// filtered by userId) so an admin can target one device or all of them. Reads the live form values,\n// so it re-loads whenever the recipient changes and clears a stale selection. It also derives\n// `showChannelId` (Android channel id is Android-only + visible-only) so the channelId field can hide\n// itself for a selected iOS device or in silent mode. Degrades to \"All devices\" when the admin lacks\n// devices.admin or the recipient has no push-capable device.\nfunction DeviceField({ value, setValue, setFormValue, values, onState }: CrudCustomFieldRenderProps & { onState?: (s: DeviceState) => void }) {\n const t = useT()\n const userId = typeof values?.userId === 'string' ? values.userId : ''\n const mode = typeof values?.mode === 'string' ? values.mode : 'visible'\n const selected = typeof value === 'string' && value ? value : ''\n const [devices, setDevices] = React.useState<DeviceOption[]>([])\n const [loading, setLoading] = React.useState(false)\n\n // Report the recipient + push-capable device count up so the page can disable \"Send\" when there is\n // nowhere to deliver (no recipient, or the recipient has zero push-capable devices).\n React.useEffect(() => {\n onState?.({ userId, count: devices.length, loading })\n }, [userId, devices.length, loading, onState])\n\n React.useEffect(() => {\n if (!userId) {\n setDevices([])\n if (selected) setValue('')\n return\n }\n let cancelled = false\n setLoading(true)\n const params = new URLSearchParams({ userId, pageSize: '50' })\n apiCall<{ items?: Array<{ id: string; device_id: string; platform: string; push_provider?: string | null }> }>(\n `/api/devices/admin/devices?${params.toString()}`,\n { headers: { 'x-om-forbidden-redirect': '0' } },\n { fallback: null },\n )\n .catch(() => null)\n .then((call) => {\n if (cancelled) return\n const items = (call && call.ok ? call.result?.items : []) ?? []\n const opts = items\n .filter((d): d is { id: string; device_id: string; platform: string; push_provider?: string | null } =>\n !!d && typeof d.id === 'string' && !!d.push_provider)\n .map((d) => ({ id: d.id, label: `${d.device_id} \u00B7 ${d.platform}${d.push_provider ? ` \u00B7 ${d.push_provider}` : ''}`, platform: d.platform }))\n setDevices(opts)\n if (selected && !opts.some((o) => o.id === selected)) setValue('')\n })\n .finally(() => { if (!cancelled) setLoading(false) })\n return () => { cancelled = true }\n // Reload only when the recipient changes; `selected`/`setValue` are handled inside.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [userId])\n\n // channelId is Android-only and irrelevant to silent pushes \u2192 hide it for a selected iOS device or\n // in silent mode. Only write when the derived value actually changes (avoids a render loop).\n const selectedPlatform = React.useMemo(() => devices.find((d) => d.id === selected)?.platform, [devices, selected])\n React.useEffect(() => {\n const show = mode !== 'silent' && selectedPlatform !== 'ios'\n if (values?.showChannelId !== show) setFormValue?.('showChannelId', show)\n }, [mode, selectedPlatform, values?.showChannelId, setFormValue])\n\n return (\n <div className=\"space-y-1\">\n <Select value={selected || ALL_DEVICES} onValueChange={(v) => setValue(v === ALL_DEVICES ? '' : v)} disabled={!userId}>\n <SelectTrigger>\n <SelectValue placeholder={`${t('push_notifications.send.deviceAll')} (${devices.length})`} />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL_DEVICES}>{`${t('push_notifications.send.deviceAll')} (${devices.length})`}</SelectItem>\n {devices.map((d) => (\n <SelectItem key={d.id} value={d.id}>{d.label}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className={`text-xs ${userId && !loading && devices.length === 0 ? 'text-status-warning-text' : 'text-muted-foreground'}`}>\n {!userId\n ? t('push_notifications.send.deviceSelectUserFirst')\n : loading\n ? t('common.loading', 'Loading\u2026')\n : devices.length === 0\n ? t('push_notifications.send.deviceNone')\n : t('push_notifications.send.deviceHint')}\n </p>\n </div>\n )\n}\n\n// Arbitrary key/value payload delivered inside the push `data` map (the app reads it). Shown for both\n// visible and silent sends \u2014 for silent it is the entire payload.\nfunction DataField({ value, setValue }: CrudCustomFieldRenderProps) {\n const t = useT()\n const pairs: KeyValue[] = Array.isArray(value) ? (value as KeyValue[]) : []\n const update = (next: KeyValue[]) => setValue(next)\n return (\n <div className=\"space-y-2\">\n {pairs.length === 0 ? (\n <p className=\"text-xs text-muted-foreground\">{t('push_notifications.send.dataEmpty')}</p>\n ) : null}\n {pairs.map((pair, index) => (\n <div key={index} className=\"flex items-center gap-2\">\n <Input\n placeholder={t('push_notifications.send.dataKey')}\n value={pair.key}\n onChange={(e) => update(pairs.map((p, i) => (i === index ? { ...p, key: e.target.value } : p)))}\n />\n <Input\n placeholder={t('push_notifications.send.dataValue')}\n value={pair.value}\n onChange={(e) => update(pairs.map((p, i) => (i === index ? { ...p, value: e.target.value } : p)))}\n />\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n aria-label={t('push_notifications.send.dataRemove')}\n onClick={() => update(pairs.filter((_, i) => i !== index))}\n >\n <X className=\"h-4 w-4\" />\n </IconButton>\n </div>\n ))}\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => update([...pairs, { key: '', value: '' }])}>\n <Plus className=\"h-4 w-4 mr-1\" />\n {t('push_notifications.send.dataAdd')}\n </Button>\n </div>\n )\n}\n\nexport default function PushCustomSendPage() {\n const router = useRouter()\n const t = useT()\n const [deviceState, setDeviceState] = React.useState<DeviceState>({ userId: '', count: 0, loading: false })\n const [submitting, setSubmitting] = React.useState(false)\n\n // Stable reporter for the device picker \u2192 bails when unchanged so it can't loop the render.\n const reportDeviceState = React.useCallback((next: DeviceState) => {\n setDeviceState((prev) =>\n prev.userId === next.userId && prev.count === next.count && prev.loading === next.loading ? prev : next)\n }, [])\n\n // Recipient search by name/email via /api/auth/users (mirrors the devices list + admin\n // notification-preferences picker). Return results only \u2014 the combobox retains them internally, so\n // pushing them into state that `fields` depends on would re-render the whole form per keystroke.\n const loadUserOptions = React.useCallback(async (query?: string): Promise<CrudFieldOption[]> => {\n const params = new URLSearchParams()\n params.set('page', '1')\n params.set('pageSize', '20')\n if (query && query.trim().length > 0) params.set('search', query.trim())\n const call = await apiCall<{ items?: { id: string; name?: string | null; email?: string | null }[] }>(\n `/api/auth/users?${params.toString()}`,\n { headers: { 'x-om-forbidden-redirect': '0' } },\n { fallback: null },\n ).catch(() => null)\n if (!call || !call.ok) return []\n return (call.result?.items ?? []).flatMap((item): CrudFieldOption[] => {\n if (!item || typeof item.id !== 'string' || !item.id.trim()) return []\n const name = typeof item.name === 'string' && item.name.trim() ? item.name.trim() : null\n const email = typeof item.email === 'string' && item.email.trim() ? item.email.trim() : null\n const label = name && email ? `${name} \u2014 ${email}` : email ?? name ?? item.id\n return [{ value: item.id, label }]\n })\n }, [])\n\n const visibleOnly = { field: 'mode', equals: 'visible' } as const\n\n const fields = React.useMemo<CrudField[]>(() => [\n { id: 'userId', label: t('push_notifications.send.userId'), type: 'combobox', required: true, description: t('push_notifications.send.userIdHint'), loadOptions: loadUserOptions },\n { id: 'deviceId', label: t('push_notifications.send.device'), type: 'custom', component: (props) => <DeviceField {...props} onState={reportDeviceState} /> },\n {\n id: 'mode',\n label: t('push_notifications.send.mode'),\n type: 'select',\n required: true,\n description: t('push_notifications.send.modeHint'),\n options: [\n { value: 'visible', label: t('push_notifications.send.modeVisible') },\n { value: 'silent', label: t('push_notifications.send.modeSilent') },\n ],\n },\n { id: 'title', label: t('push_notifications.send.title'), type: 'text', required: true, visibleWhen: visibleOnly },\n { id: 'body', label: t('push_notifications.send.body'), type: 'textarea', visibleWhen: visibleOnly },\n { id: 'data', label: t('push_notifications.send.data'), type: 'custom', description: t('push_notifications.send.dataHint'), component: (props) => <DataField {...props} /> },\n { id: 'sound', label: t('push_notifications.send.sound'), type: 'text', visibleWhen: visibleOnly },\n { id: 'badge', label: t('push_notifications.send.badge'), type: 'number', visibleWhen: visibleOnly },\n { id: 'image', label: t('push_notifications.send.image'), type: 'text', visibleWhen: visibleOnly },\n {\n id: 'priority',\n label: t('push_notifications.send.priority'),\n type: 'select',\n options: [\n { value: '', label: t('push_notifications.send.priorityDefault') },\n { value: 'high', label: t('push_notifications.send.priorityHigh') },\n { value: 'normal', label: t('push_notifications.send.priorityNormal') },\n ],\n },\n { id: 'channelId', label: t('push_notifications.send.channelId'), type: 'text', visibleWhen: { field: 'showChannelId', equals: true } },\n ], [t, loadUserOptions, reportDeviceState])\n\n // NOTE: intentionally a flat field list (no `groups`). CrudForm only applies `visibleWhen` in the\n // ungrouped render path \u2014 grouped fields ignore it \u2014 and this form relies on mode/platform-driven\n // field visibility (silent hides title/body/options; iOS hides channelId).\n\n return (\n <Page>\n <PageBody>\n <CrudForm<FormValues>\n title={t('push_notifications.send.pageTitle')}\n backHref={DELIVERIES_HREF}\n formId={FORM_ID}\n hideFooterActions\n fields={fields}\n initialValues={{ userId: '', deviceId: '', mode: 'visible', title: '', body: '', data: [], sound: '', badge: '', image: '', priority: '', channelId: '', showChannelId: true }}\n submitLabel={t('push_notifications.send.submit')}\n onSubmit={async (values) => {\n setSubmitting(true)\n try {\n const silent = values.mode === 'silent'\n const body = (values.body ?? '').trim()\n\n const dataMap: Record<string, string> = {}\n for (const pair of Array.isArray(values.data) ? values.data : []) {\n const key = (pair?.key ?? '').trim()\n if (key) dataMap[key] = `${pair?.value ?? ''}`\n }\n const hasData = Object.keys(dataMap).length > 0\n\n // Options that only affect a visible banner are dropped for silent.\n const pushOptions: Record<string, unknown> = {}\n if (!silent) {\n if (typeof values.sound === 'string' && values.sound.trim()) pushOptions.sound = values.sound.trim()\n const badgeNum = typeof values.badge === 'number' ? values.badge : Number.parseInt(String(values.badge ?? ''), 10)\n if (Number.isFinite(badgeNum) && badgeNum >= 0) pushOptions.badge = badgeNum\n if (typeof values.image === 'string' && values.image.trim()) pushOptions.image = values.image.trim()\n if (typeof values.channelId === 'string' && values.channelId.trim()) pushOptions.channelId = values.channelId.trim()\n }\n if (values.priority === 'high' || values.priority === 'normal') pushOptions.priority = values.priority\n const hasOptions = Object.keys(pushOptions).length > 0\n\n // Silent hides the title field; the API still needs one, so use a stable label for the log.\n const title = silent ? (values.title ?? '').trim() || 'Silent push' : (values.title ?? '').trim()\n\n await apiCallOrThrow('/api/push_notifications/custom-send', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n recipientUserId: (values.userId ?? '').trim(),\n deviceId: (values.deviceId ?? '').trim() || undefined,\n title,\n body: !silent && body.length > 0 ? body : undefined,\n silent,\n data: hasData ? dataMap : undefined,\n pushOptions: hasOptions ? pushOptions : undefined,\n }),\n })\n flash(t('push_notifications.send.success'), 'success')\n router.push(DELIVERIES_HREF)\n } finally {\n setSubmitting(false)\n }\n }}\n />\n <div className=\"mt-4 flex items-center justify-between gap-3\">\n <div className=\"text-xs\">\n {!deviceState.userId ? (\n <span className=\"text-muted-foreground\">{t('push_notifications.send.deviceSelectUserFirst')}</span>\n ) : deviceState.count === 0 && !deviceState.loading ? (\n <span className=\"text-status-warning-text\">{t('push_notifications.send.deviceNone')}</span>\n ) : null}\n </div>\n <div className=\"flex gap-2\">\n <Button asChild variant=\"outline\">\n <Link href={DELIVERIES_HREF}>{t('common.cancel', 'Cancel')}</Link>\n </Button>\n <Button\n type=\"submit\"\n form={FORM_ID}\n disabled={!deviceState.userId || deviceState.count === 0 || deviceState.loading || submitting}\n >\n {t('push_notifications.send.submit')}\n </Button>\n </div>\n </div>\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+ const megaphoneIcon = React.createElement(
3
+ "svg",
4
+ { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" },
5
+ React.createElement("path", { d: "m3 11 18-5v12L3 14v-3z" }),
6
+ React.createElement("path", { d: "M11.6 16.8a3 3 0 1 1-5.8-1.6" })
7
+ );
8
+ const metadata = {
9
+ requireAuth: true,
10
+ requireFeatures: ["push_notifications.send_custom"],
11
+ pageTitle: "Send Push",
12
+ pageTitleKey: "push_notifications.send.pageTitle",
13
+ pageGroup: "External systems",
14
+ pageGroupKey: "backend.nav.externalSystems",
15
+ pageOrder: 55,
16
+ icon: megaphoneIcon,
17
+ pageContext: "settings",
18
+ breadcrumb: [
19
+ { label: "Push Deliveries", labelKey: "push_notifications.deliveries.pageTitle", href: "/backend/push_notifications" },
20
+ { label: "Send Push", labelKey: "push_notifications.send.pageTitle" }
21
+ ]
22
+ };
23
+ export {
24
+ metadata
25
+ };
26
+ //# sourceMappingURL=page.meta.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/push_notifications/backend/push_notifications/send/page.meta.ts"],
4
+ "sourcesContent": ["import React from 'react'\n\nconst megaphoneIcon = React.createElement(\n 'svg',\n { width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' },\n React.createElement('path', { d: 'm3 11 18-5v12L3 14v-3z' }),\n React.createElement('path', { d: 'M11.6 16.8a3 3 0 1 1-5.8-1.6' }),\n)\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['push_notifications.send_custom'],\n pageTitle: 'Send Push',\n pageTitleKey: 'push_notifications.send.pageTitle',\n pageGroup: 'External systems',\n pageGroupKey: 'backend.nav.externalSystems',\n pageOrder: 55,\n icon: megaphoneIcon,\n pageContext: 'settings' as const,\n breadcrumb: [\n { label: 'Push Deliveries', labelKey: 'push_notifications.deliveries.pageTitle', href: '/backend/push_notifications' },\n { label: 'Send Push', labelKey: 'push_notifications.send.pageTitle' },\n ],\n} as const\n"],
5
+ "mappings": "AAAA,OAAO,WAAW;AAElB,MAAM,gBAAgB,MAAM;AAAA,EAC1B;AAAA,EACA,EAAE,OAAO,IAAI,QAAQ,IAAI,SAAS,aAAa,MAAM,QAAQ,QAAQ,gBAAgB,aAAa,GAAG,eAAe,SAAS,gBAAgB,QAAQ;AAAA,EACrJ,MAAM,cAAc,QAAQ,EAAE,GAAG,yBAAyB,CAAC;AAAA,EAC3D,MAAM,cAAc,QAAQ,EAAE,GAAG,+BAA+B,CAAC;AACnE;AAEO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,gCAAgC;AAAA,EAClD,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,MAAM;AAAA,EACN,aAAa;AAAA,EACb,YAAY;AAAA,IACV,EAAE,OAAO,mBAAmB,UAAU,2CAA2C,MAAM,8BAA8B;AAAA,IACrH,EAAE,OAAO,aAAa,UAAU,oCAAoC;AAAA,EACtE;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,92 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+ import { OptionalProps } from "@mikro-orm/core";
12
+ import { Entity, Index, PrimaryKey, Property } from "@mikro-orm/decorators/legacy";
13
+ OptionalProps;
14
+ let PushNotificationDelivery = class {
15
+ constructor() {
16
+ this.silent = false;
17
+ this.status = "pending";
18
+ this.attempts = 0;
19
+ this.createdAt = /* @__PURE__ */ new Date();
20
+ this.updatedAt = /* @__PURE__ */ new Date();
21
+ }
22
+ };
23
+ __decorateClass([
24
+ PrimaryKey({ type: "uuid", defaultRaw: "gen_random_uuid()" })
25
+ ], PushNotificationDelivery.prototype, "id", 2);
26
+ __decorateClass([
27
+ Property({ name: "tenant_id", type: "uuid" })
28
+ ], PushNotificationDelivery.prototype, "tenantId", 2);
29
+ __decorateClass([
30
+ Property({ name: "organization_id", type: "uuid", nullable: true })
31
+ ], PushNotificationDelivery.prototype, "organizationId", 2);
32
+ __decorateClass([
33
+ Property({ name: "notification_id", type: "uuid", nullable: true })
34
+ ], PushNotificationDelivery.prototype, "notificationId", 2);
35
+ __decorateClass([
36
+ Property({ name: "notification_type_id", type: "text" })
37
+ ], PushNotificationDelivery.prototype, "notificationTypeId", 2);
38
+ __decorateClass([
39
+ Property({ name: "user_device_id", type: "uuid" })
40
+ ], PushNotificationDelivery.prototype, "userDeviceId", 2);
41
+ __decorateClass([
42
+ Property({ name: "user_id", type: "uuid" })
43
+ ], PushNotificationDelivery.prototype, "userId", 2);
44
+ __decorateClass([
45
+ Property({ name: "provider", type: "text" })
46
+ ], PushNotificationDelivery.prototype, "provider", 2);
47
+ __decorateClass([
48
+ Property({ name: "token_snapshot", type: "text" })
49
+ ], PushNotificationDelivery.prototype, "tokenSnapshot", 2);
50
+ __decorateClass([
51
+ Property({ name: "silent", type: "boolean", default: false })
52
+ ], PushNotificationDelivery.prototype, "silent", 2);
53
+ __decorateClass([
54
+ Property({ name: "status", type: "text", default: "pending" })
55
+ ], PushNotificationDelivery.prototype, "status", 2);
56
+ __decorateClass([
57
+ Property({ name: "attempts", type: "int", default: 0 })
58
+ ], PushNotificationDelivery.prototype, "attempts", 2);
59
+ __decorateClass([
60
+ Property({ name: "last_error", type: "text", nullable: true })
61
+ ], PushNotificationDelivery.prototype, "lastError", 2);
62
+ __decorateClass([
63
+ Property({ name: "payload", type: "json" })
64
+ ], PushNotificationDelivery.prototype, "payload", 2);
65
+ __decorateClass([
66
+ Property({ name: "provider_response", type: "json", nullable: true })
67
+ ], PushNotificationDelivery.prototype, "providerResponse", 2);
68
+ __decorateClass([
69
+ Property({ name: "created_at", type: Date, onCreate: () => /* @__PURE__ */ new Date() })
70
+ ], PushNotificationDelivery.prototype, "createdAt", 2);
71
+ __decorateClass([
72
+ Property({ name: "sent_at", type: Date, nullable: true })
73
+ ], PushNotificationDelivery.prototype, "sentAt", 2);
74
+ __decorateClass([
75
+ Property({ name: "next_retry_at", type: Date, nullable: true })
76
+ ], PushNotificationDelivery.prototype, "nextRetryAt", 2);
77
+ __decorateClass([
78
+ Property({ name: "updated_at", type: Date, onCreate: () => /* @__PURE__ */ new Date(), onUpdate: () => /* @__PURE__ */ new Date() })
79
+ ], PushNotificationDelivery.prototype, "updatedAt", 2);
80
+ PushNotificationDelivery = __decorateClass([
81
+ Entity({ tableName: "push_notification_deliveries" }),
82
+ Index({ name: "push_notification_deliveries_tenant_status_idx", properties: ["tenantId", "status", "createdAt"] }),
83
+ Index({ name: "push_notification_deliveries_notification_idx", properties: ["notificationId"] }),
84
+ Index({
85
+ name: "push_notification_deliveries_notif_device_unique",
86
+ expression: 'create unique index "push_notification_deliveries_notif_device_unique" on "push_notification_deliveries" ("notification_id", "user_device_id") where "notification_id" is not null'
87
+ })
88
+ ], PushNotificationDelivery);
89
+ export {
90
+ PushNotificationDelivery
91
+ };
92
+ //# sourceMappingURL=entities.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/push_notifications/data/entities.ts"],
4
+ "sourcesContent": ["import { OptionalProps } from '@mikro-orm/core'\nimport { Entity, Index, PrimaryKey, Property } from '@mikro-orm/decorators/legacy'\n\nexport type PushDeliveryStatus = 'pending' | 'sending' | 'sent' | 'failed' | 'skipped' | 'expired'\n\n/**\n * Append-only delivery log for a single push notification attempt against one device.\n *\n * One row per (notification, device). The strategy inserts rows `pending`; the `send-push` worker\n * atomically claims a row (`pending` \u2192 `sending`, so a redelivered job is processed once) and then\n * transitions it to `sent`/`failed`/`skipped`, or `expired` once retries are exhausted. This is a\n * background-job/log row, so it is intentionally EXEMPT from optimistic locking\n * (status transitions are server-driven, never concurrently user-edited) and is\n * NOT added to the curated `optimistic-lock-editable-entities` list.\n *\n * `push_token` is a secret: the full token is never persisted here \u2014 only\n * `token_snapshot` (last 8 chars, for debugging across token rotation) plus the\n * `provider` snapshot so the audit trail survives token rotation and device deletion.\n */\n@Entity({ tableName: 'push_notification_deliveries' })\n@Index({ name: 'push_notification_deliveries_tenant_status_idx', properties: ['tenantId', 'status', 'createdAt'] })\n@Index({ name: 'push_notification_deliveries_notification_idx', properties: ['notificationId'] })\n// Idempotency guard for the fan-out: the `push` strategy runs inside the at-least-once persistent\n// `notifications:deliver` subscriber, so a redelivered event would otherwise insert a second set of\n// rows for the same (notification, device) \u2192 a duplicate push. A partial unique index (only where\n// notification_id is set \u2014 direct/silent pushes without a notification are out of scope) lets the\n// strategy insert-on-conflict-do-nothing, making a re-run a no-op.\n@Index({\n name: 'push_notification_deliveries_notif_device_unique',\n expression:\n 'create unique index \"push_notification_deliveries_notif_device_unique\" on \"push_notification_deliveries\" (\"notification_id\", \"user_device_id\") where \"notification_id\" is not null',\n})\nexport class PushNotificationDelivery {\n [OptionalProps]?:\n | 'organizationId'\n | 'notificationId'\n | 'status'\n | 'attempts'\n | 'lastError'\n | 'providerResponse'\n | 'silent'\n | 'createdAt'\n | 'sentAt'\n | 'nextRetryAt'\n | 'updatedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n // Soft FK -> notifications.notifications (nullable: a push can be dispatched without an in-app row).\n @Property({ name: 'notification_id', type: 'uuid', nullable: true })\n notificationId?: string | null\n\n @Property({ name: 'notification_type_id', type: 'text' })\n notificationTypeId!: string\n\n // Soft FK -> devices.user_devices (link declared in data/extensions.ts).\n @Property({ name: 'user_device_id', type: 'uuid' })\n userDeviceId!: string\n\n @Property({ name: 'user_id', type: 'uuid' })\n userId!: string\n\n // Snapshot of the push CommunicationChannel providerKey used at send time.\n @Property({ name: 'provider', type: 'text' })\n provider!: string\n\n // Last 8 chars of the device push token only \u2014 never the full secret.\n @Property({ name: 'token_snapshot', type: 'text' })\n tokenSnapshot!: string\n\n // Snapshot of whether this was a silent / content-available wake-up (derived from the\n // notification type's `silent` flag at fan-out time) so the log distinguishes silent deliveries.\n @Property({ name: 'silent', type: 'boolean', default: false })\n silent: boolean = false\n\n @Property({ name: 'status', type: 'text', default: 'pending' })\n status: PushDeliveryStatus = 'pending'\n\n @Property({ name: 'attempts', type: 'int', default: 0 })\n attempts: number = 0\n\n @Property({ name: 'last_error', type: 'text', nullable: true })\n lastError?: string | null\n\n @Property({ name: 'payload', type: 'json' })\n payload!: Record<string, unknown>\n\n @Property({ name: 'provider_response', type: 'json', nullable: true })\n providerResponse?: Record<string, unknown> | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'sent_at', type: Date, nullable: true })\n sentAt?: Date | null\n\n // Set when a retryable failure re-enqueues the job (observability for the admin delivery log);\n // cleared once the delivery reaches a terminal state.\n @Property({ name: 'next_retry_at', type: Date, nullable: true })\n nextRetryAt?: Date | null\n\n @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n}\n"],
5
+ "mappings": ";;;;;;;;;;AAAA,SAAS,qBAAqB;AAC9B,SAAS,QAAQ,OAAO,YAAY,gBAAgB;AAgCjD;AADI,IAAM,2BAAN,MAA+B;AAAA,EAA/B;AAgDL,kBAAkB;AAGlB,kBAA6B;AAG7B,oBAAmB;AAYnB,qBAAkB,oBAAI,KAAK;AAW3B,qBAAkB,oBAAI,KAAK;AAAA;AAC7B;AA/DE;AAAA,EADC,WAAW,EAAE,MAAM,QAAQ,YAAY,oBAAoB,CAAC;AAAA,GAdlD,yBAeX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,aAAa,MAAM,OAAO,CAAC;AAAA,GAjBlC,yBAkBX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,mBAAmB,MAAM,QAAQ,UAAU,KAAK,CAAC;AAAA,GApBxD,yBAqBX;AAIA;AAAA,EADC,SAAS,EAAE,MAAM,mBAAmB,MAAM,QAAQ,UAAU,KAAK,CAAC;AAAA,GAxBxD,yBAyBX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,wBAAwB,MAAM,OAAO,CAAC;AAAA,GA3B7C,yBA4BX;AAIA;AAAA,EADC,SAAS,EAAE,MAAM,kBAAkB,MAAM,OAAO,CAAC;AAAA,GA/BvC,yBAgCX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,WAAW,MAAM,OAAO,CAAC;AAAA,GAlChC,yBAmCX;AAIA;AAAA,EADC,SAAS,EAAE,MAAM,YAAY,MAAM,OAAO,CAAC;AAAA,GAtCjC,yBAuCX;AAIA;AAAA,EADC,SAAS,EAAE,MAAM,kBAAkB,MAAM,OAAO,CAAC;AAAA,GA1CvC,yBA2CX;AAKA;AAAA,EADC,SAAS,EAAE,MAAM,UAAU,MAAM,WAAW,SAAS,MAAM,CAAC;AAAA,GA/ClD,yBAgDX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,UAAU,MAAM,QAAQ,SAAS,UAAU,CAAC;AAAA,GAlDnD,yBAmDX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,YAAY,MAAM,OAAO,SAAS,EAAE,CAAC;AAAA,GArD5C,yBAsDX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,QAAQ,UAAU,KAAK,CAAC;AAAA,GAxDnD,yBAyDX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,WAAW,MAAM,OAAO,CAAC;AAAA,GA3DhC,yBA4DX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,qBAAqB,MAAM,QAAQ,UAAU,KAAK,CAAC;AAAA,GA9D1D,yBA+DX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,MAAM,UAAU,MAAM,oBAAI,KAAK,EAAE,CAAC;AAAA,GAjE7D,yBAkEX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,WAAW,MAAM,MAAM,UAAU,KAAK,CAAC;AAAA,GApE9C,yBAqEX;AAKA;AAAA,EADC,SAAS,EAAE,MAAM,iBAAiB,MAAM,MAAM,UAAU,KAAK,CAAC;AAAA,GAzEpD,yBA0EX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,MAAM,UAAU,MAAM,oBAAI,KAAK,GAAG,UAAU,MAAM,oBAAI,KAAK,EAAE,CAAC;AAAA,GA5EzF,yBA6EX;AA7EW,2BAAN;AAAA,EAbN,OAAO,EAAE,WAAW,+BAA+B,CAAC;AAAA,EACpD,MAAM,EAAE,MAAM,kDAAkD,YAAY,CAAC,YAAY,UAAU,WAAW,EAAE,CAAC;AAAA,EACjH,MAAM,EAAE,MAAM,iDAAiD,YAAY,CAAC,gBAAgB,EAAE,CAAC;AAAA,EAM/F,MAAM;AAAA,IACL,MAAM;AAAA,IACN,YACE;AAAA,EACJ,CAAC;AAAA,GACY;",
6
+ "names": []
7
+ }
@@ -0,0 +1,23 @@
1
+ const entityExtensions = [
2
+ {
3
+ base: "push_notifications:push_notification_delivery",
4
+ extension: "devices:user_device",
5
+ join: { baseKey: "user_device_id", extensionKey: "id" },
6
+ cardinality: "many-to-one",
7
+ description: "Links a push delivery row to the target user device it was sent to"
8
+ },
9
+ {
10
+ base: "push_notifications:push_notification_delivery",
11
+ extension: "notifications:notification",
12
+ join: { baseKey: "notification_id", extensionKey: "id" },
13
+ cardinality: "many-to-one",
14
+ description: "Links a push delivery row to the in-app notification that triggered it"
15
+ }
16
+ ];
17
+ const extensions = entityExtensions;
18
+ var extensions_default = entityExtensions;
19
+ export {
20
+ extensions_default as default,
21
+ extensions
22
+ };
23
+ //# sourceMappingURL=extensions.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/push_notifications/data/extensions.ts"],
4
+ "sourcesContent": ["import type { EntityExtension } from '@open-mercato/shared/modules/entities'\n\n/**\n * Cross-module entity links declared by the push_notifications module.\n *\n * Per root AGENTS.md, modules do NOT form direct ORM relationships across\n * boundaries. `PushNotificationDelivery` carries plain UUID columns that\n * reference rows owned by other modules; the links are declared here so the\n * data engine + UI tooling can traverse them.\n */\nconst entityExtensions: EntityExtension[] = [\n {\n base: 'push_notifications:push_notification_delivery',\n extension: 'devices:user_device',\n join: { baseKey: 'user_device_id', extensionKey: 'id' },\n cardinality: 'many-to-one',\n description: 'Links a push delivery row to the target user device it was sent to',\n },\n {\n base: 'push_notifications:push_notification_delivery',\n extension: 'notifications:notification',\n join: { baseKey: 'notification_id', extensionKey: 'id' },\n cardinality: 'many-to-one',\n description: 'Links a push delivery row to the in-app notification that triggered it',\n },\n]\n\nexport const extensions = entityExtensions\nexport default entityExtensions\n"],
5
+ "mappings": "AAUA,MAAM,mBAAsC;AAAA,EAC1C;AAAA,IACE,MAAM;AAAA,IACN,WAAW;AAAA,IACX,MAAM,EAAE,SAAS,kBAAkB,cAAc,KAAK;AAAA,IACtD,aAAa;AAAA,IACb,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,WAAW;AAAA,IACX,MAAM,EAAE,SAAS,mBAAmB,cAAc,KAAK;AAAA,IACvD,aAAa;AAAA,IACb,aAAa;AAAA,EACf;AACF;AAEO,MAAM,aAAa;AAC1B,IAAO,qBAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,89 @@
1
+ import { z } from "zod";
2
+ import { pushOptionsSchema } from "@open-mercato/core/modules/notifications/data/validators";
3
+ const PUSH_DELIVERY_STATUSES = ["pending", "sending", "sent", "failed", "skipped", "expired"];
4
+ const rangeDateFilter = z.union([z.string().datetime({ offset: true }), z.string().date()]);
5
+ const customSendSchema = z.object({
6
+ recipientUserId: z.string().uuid(),
7
+ // Optional: target one of the recipient's devices; omit to send to all their devices.
8
+ deviceId: z.string().uuid().optional(),
9
+ title: z.string().trim().min(1).max(500),
10
+ body: z.string().trim().max(2e3).nullish(),
11
+ data: z.record(z.string(), z.string()).optional(),
12
+ pushOptions: pushOptionsSchema.optional(),
13
+ // When true, deliver a silent data-only wake-up instead of a visible banner.
14
+ silent: z.boolean().optional()
15
+ }).strict();
16
+ const CUSTOM_SEND_NO_DEVICES_WARNING = "no_matching_devices_in_scope";
17
+ const customSendResponseSchema = z.object({
18
+ enqueued: z.number(),
19
+ warning: z.literal(CUSTOM_SEND_NO_DEVICES_WARNING).optional(),
20
+ message: z.string().optional()
21
+ });
22
+ const deliveryListSchema = z.object({
23
+ page: z.coerce.number().min(1).default(1),
24
+ pageSize: z.coerce.number().min(1).max(100).default(50),
25
+ status: z.enum(PUSH_DELIVERY_STATUSES).optional(),
26
+ userId: z.string().uuid().optional(),
27
+ from: rangeDateFilter.optional(),
28
+ to: rangeDateFilter.optional(),
29
+ sortField: z.string().optional(),
30
+ sortDir: z.enum(["asc", "desc"]).optional()
31
+ }).passthrough();
32
+ const deliveryListFields = [
33
+ "id",
34
+ "tenant_id",
35
+ "organization_id",
36
+ "notification_id",
37
+ "notification_type_id",
38
+ "user_device_id",
39
+ "user_id",
40
+ "provider",
41
+ "token_snapshot",
42
+ "status",
43
+ "attempts",
44
+ "last_error",
45
+ "created_at",
46
+ "sent_at",
47
+ "next_retry_at",
48
+ "updated_at"
49
+ ];
50
+ const deliveryListSortFieldMap = {
51
+ createdAt: "created_at",
52
+ sentAt: "sent_at",
53
+ updatedAt: "updated_at",
54
+ status: "status"
55
+ };
56
+ const deliveryListItemSchema = z.object({
57
+ id: z.string().uuid(),
58
+ tenant_id: z.string().uuid(),
59
+ organization_id: z.string().uuid().nullable().optional(),
60
+ notification_id: z.string().uuid().nullable().optional(),
61
+ notification_type_id: z.string(),
62
+ user_device_id: z.string().uuid(),
63
+ user_id: z.string().uuid(),
64
+ provider: z.string(),
65
+ token_snapshot: z.string(),
66
+ status: z.enum(PUSH_DELIVERY_STATUSES),
67
+ attempts: z.number(),
68
+ last_error: z.string().nullable().optional(),
69
+ created_at: z.string().nullable().optional(),
70
+ sent_at: z.string().nullable().optional(),
71
+ next_retry_at: z.string().nullable().optional(),
72
+ updated_at: z.string().nullable().optional()
73
+ });
74
+ const deliveryDetailItemSchema = deliveryListItemSchema.extend({
75
+ payload: z.record(z.string(), z.unknown()).nullable().optional(),
76
+ provider_response: z.record(z.string(), z.unknown()).nullable().optional()
77
+ });
78
+ export {
79
+ CUSTOM_SEND_NO_DEVICES_WARNING,
80
+ PUSH_DELIVERY_STATUSES,
81
+ customSendResponseSchema,
82
+ customSendSchema,
83
+ deliveryDetailItemSchema,
84
+ deliveryListFields,
85
+ deliveryListItemSchema,
86
+ deliveryListSchema,
87
+ deliveryListSortFieldMap
88
+ };
89
+ //# sourceMappingURL=validators.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/push_notifications/data/validators.ts"],
4
+ "sourcesContent": ["import { z } from 'zod'\nimport { pushOptionsSchema } from '@open-mercato/core/modules/notifications/data/validators'\n\nexport const PUSH_DELIVERY_STATUSES = ['pending', 'sending', 'sent', 'failed', 'skipped', 'expired'] as const\n\n// A `created_at` range bound: an ISO-8601 date (`2026-07-01`) or datetime (`2026-07-01T00:00:00Z`).\n// Validated here so a malformed value fails with a 400 instead of reaching the query engine / Postgres.\nconst rangeDateFilter = z.union([z.string().datetime({ offset: true }), z.string().date()])\n\n// POST /api/push_notifications/custom-send \u2014 admin composes a one-off visible push to a single user.\n// title/body are literal free text (not i18n keys). data/pushOptions reuse the notifications contract.\nexport const customSendSchema = z\n .object({\n recipientUserId: z.string().uuid(),\n // Optional: target one of the recipient's devices; omit to send to all their devices.\n deviceId: z.string().uuid().optional(),\n title: z.string().trim().min(1).max(500),\n body: z.string().trim().max(2000).nullish(),\n data: z.record(z.string(), z.string()).optional(),\n pushOptions: pushOptionsSchema.optional(),\n // When true, deliver a silent data-only wake-up instead of a visible banner.\n silent: z.boolean().optional(),\n })\n .strict()\nexport type CustomSendInput = z.infer<typeof customSendSchema>\n\n// Machine-readable warning surfaced when a well-formed send resolved no deliverable device in scope\n// (no push channel configured, no in-scope devices, or none whose provider matches an active channel).\n// The caller gets `enqueued: 0` plus this code + a human `message` instead of a misleading silent success.\nexport const CUSTOM_SEND_NO_DEVICES_WARNING = 'no_matching_devices_in_scope'\n\nexport const customSendResponseSchema = z.object({\n enqueued: z.number(),\n warning: z.literal(CUSTOM_SEND_NO_DEVICES_WARNING).optional(),\n message: z.string().optional(),\n})\n\n// Read-only delivery-log list contract (admin observability). No full push token is ever exposed \u2014\n// only `token_snapshot` (last 8 chars) and the `provider` snapshot.\nexport const deliveryListSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n status: z.enum(PUSH_DELIVERY_STATUSES).optional(),\n userId: z.string().uuid().optional(),\n from: rangeDateFilter.optional(),\n to: rangeDateFilter.optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n })\n .passthrough()\n\nexport const deliveryListFields: string[] = [\n 'id',\n 'tenant_id',\n 'organization_id',\n 'notification_id',\n 'notification_type_id',\n 'user_device_id',\n 'user_id',\n 'provider',\n 'token_snapshot',\n 'status',\n 'attempts',\n 'last_error',\n 'created_at',\n 'sent_at',\n 'next_retry_at',\n 'updated_at',\n]\n\nexport const deliveryListSortFieldMap: Record<string, string> = {\n createdAt: 'created_at',\n sentAt: 'sent_at',\n updatedAt: 'updated_at',\n status: 'status',\n}\n\nexport const deliveryListItemSchema = z.object({\n id: z.string().uuid(),\n tenant_id: z.string().uuid(),\n organization_id: z.string().uuid().nullable().optional(),\n notification_id: z.string().uuid().nullable().optional(),\n notification_type_id: z.string(),\n user_device_id: z.string().uuid(),\n user_id: z.string().uuid(),\n provider: z.string(),\n token_snapshot: z.string(),\n status: z.enum(PUSH_DELIVERY_STATUSES),\n attempts: z.number(),\n last_error: z.string().nullable().optional(),\n created_at: z.string().nullable().optional(),\n sent_at: z.string().nullable().optional(),\n next_retry_at: z.string().nullable().optional(),\n updated_at: z.string().nullable().optional(),\n})\n\n// Detail adds the JSON payload + provider response (still no full token).\nexport const deliveryDetailItemSchema = deliveryListItemSchema.extend({\n payload: z.record(z.string(), z.unknown()).nullable().optional(),\n provider_response: z.record(z.string(), z.unknown()).nullable().optional(),\n})\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,17 @@
1
+ import { asValue } from "awilix";
2
+ import { PushNotificationDelivery } from "./data/entities.js";
3
+ import { ensurePushStubAdapterRegistered } from "./lib/push-stub-adapter.js";
4
+ import { pushNotificationService } from "./lib/send-custom-push.js";
5
+ function register(container) {
6
+ ensurePushStubAdapterRegistered();
7
+ container.register({
8
+ PushNotificationDelivery: asValue(PushNotificationDelivery),
9
+ // Stateless service backing the admin custom-send push (decoupled via DI). The caller passes
10
+ // its own scoped `resolve` in the call args.
11
+ pushNotificationService: asValue(pushNotificationService)
12
+ });
13
+ }
14
+ export {
15
+ register
16
+ };
17
+ //# sourceMappingURL=di.js.map