@open-mercato/core 0.6.8-develop.6984.1.f0cf23f0f6 → 0.6.8-develop.6985.1.fb93574faa

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -719,5 +719,6 @@
719
719
  "customers~catalog.errors.id_required": "Datensatzkennung ist erforderlich.",
720
720
  "customers~catalog.errors.organization_required": "Organisationskontext ist erforderlich.",
721
721
  "customers~catalog.errors.tenant_required": "Mandantenkontext ist erforderlich.",
722
- "customers~catalog.nav.group": "Katalog"
722
+ "customers~catalog.nav.group": "Katalog",
723
+ "notifications.categories.catalog": "Katalog"
723
724
  }
@@ -719,5 +719,6 @@
719
719
  "customers~catalog.errors.id_required": "Record identifier is required.",
720
720
  "customers~catalog.errors.organization_required": "Organization context is required.",
721
721
  "customers~catalog.errors.tenant_required": "Tenant context is required.",
722
- "customers~catalog.nav.group": "Catalog"
722
+ "customers~catalog.nav.group": "Catalog",
723
+ "notifications.categories.catalog": "Catalog"
723
724
  }
@@ -719,5 +719,6 @@
719
719
  "customers~catalog.errors.id_required": "El identificador del registro es obligatorio.",
720
720
  "customers~catalog.errors.organization_required": "El contexto de organización es obligatorio.",
721
721
  "customers~catalog.errors.tenant_required": "El contexto de tenant es obligatorio.",
722
- "customers~catalog.nav.group": "Catálogo"
722
+ "customers~catalog.nav.group": "Catálogo",
723
+ "notifications.categories.catalog": "Catálogo"
723
724
  }
@@ -719,5 +719,6 @@
719
719
  "customers~catalog.errors.id_required": "레코드 식별자가 필요합니다.",
720
720
  "customers~catalog.errors.organization_required": "조직 컨텍스트가 필요합니다.",
721
721
  "customers~catalog.errors.tenant_required": "테넌트 컨텍스트가 필요합니다.",
722
- "customers~catalog.nav.group": "카탈로그"
722
+ "customers~catalog.nav.group": "카탈로그",
723
+ "notifications.categories.catalog": "카탈로그"
723
724
  }
@@ -719,5 +719,6 @@
719
719
  "customers~catalog.errors.id_required": "Identyfikator rekordu jest wymagany.",
720
720
  "customers~catalog.errors.organization_required": "Kontekst organizacji jest wymagany.",
721
721
  "customers~catalog.errors.tenant_required": "Kontekst tenanta jest wymagany.",
722
- "customers~catalog.nav.group": "Katalog"
722
+ "customers~catalog.nav.group": "Katalog",
723
+ "notifications.categories.catalog": "Katalog"
723
724
  }
@@ -3,6 +3,8 @@ import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/no
3
3
  export const notificationTypes: NotificationTypeDefinition[] = [
4
4
  {
5
5
  type: 'catalog.product.low_stock',
6
+ // Ships without push — operators re-enable it per type from the Notification Delivery settings.
7
+ channels: ['in_app', 'email'],
6
8
  module: 'catalog',
7
9
  titleKey: 'catalog.notifications.product.lowStock.title',
8
10
  bodyKey: 'catalog.notifications.product.lowStock.body',
@@ -12,6 +12,15 @@ export const features = [
12
12
  * Default-granted to all roles in `setup.ts`.
13
13
  */
14
14
  { id: 'communication_channels.connect_user_channel', title: 'Connect own communication channel', module: 'communication_channels' },
15
+ /**
16
+ * Connect a tenant-wide channel (`CommunicationChannel.user_id IS NULL`) that
17
+ * serves every user in the tenant — used for push providers (FCM/APNs/Expo)
18
+ * whose service account / signing key is shared tenant infrastructure. Gates
19
+ * `POST /channels/connect/tenant-credentials`. Admin/superadmin only: unlike
20
+ * `connect_user_channel` (everyone links their own mailbox), connecting shared
21
+ * push credentials is an administrative action.
22
+ */
23
+ { id: 'communication_channels.connect_tenant_channel', title: 'Connect tenant-wide communication channel', module: 'communication_channels' },
15
24
  /**
16
25
  * Reserved for a future v2 team-oversight capability. NOT consulted in v1:
17
26
  * personal mailboxes (`CommunicationChannel.user_id` set) follow the strict
@@ -0,0 +1,168 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
4
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
+ import type { CommandBus } from '@open-mercato/shared/lib/commands'
6
+ import type { EntityManager } from '@mikro-orm/postgresql'
7
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
8
+ import { CommunicationChannel } from '../../../../../data/entities'
9
+ import {
10
+ ChannelAccessDeniedError,
11
+ assertCanManageChannel,
12
+ channelOrgScopeWhere,
13
+ } from '../../../../../lib/access-control'
14
+ import {
15
+ COMMUNICATION_CHANNELS_ADMIN_DELETE_CHANNEL_COMMAND_ID,
16
+ type AdminDeleteChannelInput,
17
+ type AdminDeleteChannelResult,
18
+ } from '../../../../../commands/admin-delete-channel'
19
+ import { validateRouteMutationGuard } from '../../../../../lib/route-mutation-guard'
20
+
21
+ type RbacServiceLike = {
22
+ loadAcl: (
23
+ userId: string,
24
+ scope: { tenantId: string | null; organizationId: string | null },
25
+ ) => Promise<{ isSuperAdmin: boolean; features: string[]; organizations: string[] | null }>
26
+ }
27
+
28
+ /**
29
+ * Admin soft-delete for a TENANT-WIDE communication channel (`user_id IS NULL`).
30
+ *
31
+ * Companion to `/communication_channels/channels/[id]` (owner-only, per-user
32
+ * path). Tenant-wide channels — shared inboxes and the push providers
33
+ * FCM/APNs/Expo — have no owner, so the owner route rejects them; this route
34
+ * deletes them under `communication_channels.admin`. The channel is loaded
35
+ * org-agnostically so tenant-wide `organization_id IS NULL` rows resolve from
36
+ * any session org; per-user channels are masked as 404.
37
+ */
38
+ export const metadata = {
39
+ path: '/communication_channels/admin/channels/[id]',
40
+ DELETE: {
41
+ requireAuth: true,
42
+ requireFeatures: ['communication_channels.admin'],
43
+ },
44
+ }
45
+
46
+ type RouteContext = {
47
+ params: Promise<{ id: string }> | { id: string }
48
+ }
49
+
50
+ export async function DELETE(req: Request, context: RouteContext): Promise<Response> {
51
+ const { id } = await context.params
52
+ if (!z.string().uuid().safeParse(id).success) {
53
+ return NextResponse.json({ error: 'Invalid channel id' }, { status: 400 })
54
+ }
55
+
56
+ const auth = await getAuthFromRequest(req)
57
+ if (!auth?.sub || !auth?.tenantId) {
58
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
59
+ }
60
+
61
+ const container = await createRequestContainer()
62
+ const em = (container.resolve('em') as EntityManager).fork()
63
+ const organizationId = (auth as { orgId?: string | null }).orgId ?? null
64
+ const dscope = { tenantId: auth.tenantId as string, organizationId }
65
+
66
+ const channel = await findOneWithDecryption(
67
+ em,
68
+ CommunicationChannel,
69
+ {
70
+ id,
71
+ tenantId: auth.tenantId as string,
72
+ ...channelOrgScopeWhere(organizationId),
73
+ deletedAt: null,
74
+ },
75
+ undefined,
76
+ dscope,
77
+ )
78
+ // Mask a missing channel AND a per-user channel (which must go through the
79
+ // owner path) as 404 so admins can't probe personal-mailbox existence.
80
+ if (!channel || (channel as { userId?: string | null }).userId != null) {
81
+ return NextResponse.json({ error: 'Channel not found' }, { status: 404 })
82
+ }
83
+
84
+ let userFeatures: string[] = []
85
+ try {
86
+ const rbac = container.resolve('rbacService') as RbacServiceLike
87
+ const acl = await rbac.loadAcl(auth.sub as string, {
88
+ tenantId: auth.tenantId as string,
89
+ organizationId,
90
+ })
91
+ userFeatures = acl?.isSuperAdmin ? ['*'] : Array.isArray(acl?.features) ? acl.features : []
92
+ } catch {
93
+ userFeatures = []
94
+ }
95
+ try {
96
+ assertCanManageChannel(
97
+ { userId: (channel as { userId?: string | null }).userId },
98
+ auth.sub as string,
99
+ userFeatures,
100
+ 'communication_channels.manage',
101
+ )
102
+ } catch (err) {
103
+ if (err instanceof ChannelAccessDeniedError) {
104
+ return NextResponse.json({ error: 'Channel not found' }, { status: 404 })
105
+ }
106
+ throw err
107
+ }
108
+
109
+ const guard = await validateRouteMutationGuard({
110
+ container,
111
+ req,
112
+ auth,
113
+ input: {
114
+ resourceKind: 'communication_channels.channel',
115
+ resourceId: id,
116
+ operation: 'delete',
117
+ },
118
+ })
119
+ if ('response' in guard) return guard.response
120
+
121
+ const commandBus = container.resolve('commandBus') as CommandBus
122
+ // Use the CHANNEL's own org (not the caller's session org) so push teardown
123
+ // and credential resolution key where the channel stored them.
124
+ const channelOrganizationId = (channel as { organizationId?: string | null }).organizationId ?? null
125
+ const input: AdminDeleteChannelInput = {
126
+ channelId: id,
127
+ scope: { tenantId: auth.tenantId as string, organizationId: channelOrganizationId },
128
+ }
129
+ const { result } = await commandBus.execute<AdminDeleteChannelInput, AdminDeleteChannelResult>(
130
+ COMMUNICATION_CHANNELS_ADMIN_DELETE_CHANNEL_COMMAND_ID,
131
+ {
132
+ input,
133
+ ctx: {
134
+ container,
135
+ auth: auth as never,
136
+ organizationScope: null,
137
+ selectedOrganizationId: organizationId,
138
+ organizationIds: organizationId ? [organizationId] : null,
139
+ },
140
+ },
141
+ )
142
+
143
+ // 'noop' (channel vanished between the load and the command) and
144
+ // 'not_tenant_wide' both map to 404 to avoid leaking existence.
145
+ if (result.status !== 'deleted') {
146
+ return NextResponse.json({ error: 'Channel not found' }, { status: 404 })
147
+ }
148
+ await guard.afterSuccess()
149
+ return new NextResponse(null, { status: 204 })
150
+ }
151
+
152
+ export const openApi = {
153
+ tags: ['CommunicationChannels'],
154
+ methods: {
155
+ DELETE: {
156
+ summary: 'Admin delete (soft-delete) a tenant-wide communication channel',
157
+ tags: ['CommunicationChannels'],
158
+ responses: [
159
+ { status: 204, description: 'Channel deleted' },
160
+ { status: 400, description: 'Invalid channel id' },
161
+ { status: 401, description: 'Unauthorized' },
162
+ { status: 404, description: 'Channel not found or not a tenant-wide channel' },
163
+ ],
164
+ },
165
+ },
166
+ }
167
+
168
+ export default DELETE
@@ -8,7 +8,11 @@ import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/
8
8
  import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
9
9
  import { resolveOrganizationScopeFilter } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'
10
10
  import { CommunicationChannel, ExternalConversation, MessageChannelLink } from '../../../../../data/entities'
11
- import { ChannelAccessDeniedError, assertCanAccessChannel } from '../../../../../lib/access-control'
11
+ import {
12
+ ChannelAccessDeniedError,
13
+ assertCanAccessChannel,
14
+ channelOrgScopeWhereFromFilter,
15
+ } from '../../../../../lib/access-control'
12
16
 
13
17
  type RbacServiceLike = {
14
18
  loadAcl: (
@@ -67,7 +71,7 @@ export async function GET(req: Request, context: RouteContext): Promise<Response
67
71
  const channel = await findOneWithDecryption(
68
72
  em,
69
73
  CommunicationChannel,
70
- { id, tenantId: auth.tenantId, ...orgFilter.where, deletedAt: null },
74
+ { id, tenantId: auth.tenantId, ...channelOrgScopeWhereFromFilter(orgFilter), deletedAt: null },
71
75
  undefined,
72
76
  dscope,
73
77
  )
@@ -7,7 +7,11 @@ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
7
7
  import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
8
8
  import { resolveOrganizationScopeFilter } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'
9
9
  import { CommunicationChannel } from '../../../../data/entities'
10
- import { ChannelAccessDeniedError, assertCanAccessChannel } from '../../../../lib/access-control'
10
+ import {
11
+ ChannelAccessDeniedError,
12
+ assertCanAccessChannel,
13
+ channelOrgScopeWhereFromFilter,
14
+ } from '../../../../lib/access-control'
11
15
 
12
16
  type RbacServiceLike = {
13
17
  loadAcl: (
@@ -50,7 +54,7 @@ export async function GET(req: Request, context: RouteContext): Promise<Response
50
54
  {
51
55
  id,
52
56
  tenantId: auth.tenantId,
53
- ...orgFilter.where,
57
+ ...channelOrgScopeWhereFromFilter(orgFilter),
54
58
  deletedAt: null,
55
59
  },
56
60
  undefined,
@@ -7,6 +7,7 @@ import { findAndCountWithDecryption } from '@open-mercato/shared/lib/encryption/
7
7
  import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
8
8
  import { resolveOrganizationScopeFilter } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'
9
9
  import { CommunicationChannel } from '../../../data/entities'
10
+ import { channelOrgScopeWhereFromFilter } from '../../../lib/access-control'
10
11
 
11
12
  export const metadata = {
12
13
  path: '/communication_channels/channels',
@@ -58,7 +59,7 @@ export async function GET(req: Request): Promise<Response> {
58
59
  // one — admins included — can see another user's connected personal account.
59
60
  const where: Record<string, unknown> = {
60
61
  tenantId: auth.tenantId,
61
- ...orgFilter.where,
62
+ ...channelOrgScopeWhereFromFilter(orgFilter),
62
63
  deletedAt: null,
63
64
  userId: null,
64
65
  }
@@ -7,7 +7,11 @@ import type { EntityManager } from '@mikro-orm/postgresql'
7
7
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
8
8
  import { CommunicationChannel } from '../../../../../data/entities'
9
9
  import { getChannelAdapter } from '../../../../../lib/adapter-registry-singleton'
10
- import { ChannelAccessDeniedError, assertCanManageChannel } from '../../../../../lib/access-control'
10
+ import {
11
+ ChannelAccessDeniedError,
12
+ assertCanManageChannel,
13
+ channelOrgScopeWhere,
14
+ } from '../../../../../lib/access-control'
11
15
  import { refreshCredentialsIfNeeded } from '../../../../../lib/credential-refresh'
12
16
  import { validateRouteMutationGuard } from '../../../../../lib/route-mutation-guard'
13
17
 
@@ -90,7 +94,7 @@ export async function POST(req: Request, context: RouteContext): Promise<Respons
90
94
  {
91
95
  id,
92
96
  tenantId: auth.tenantId as string,
93
- organizationId,
97
+ ...channelOrgScopeWhere(organizationId),
94
98
  deletedAt: null,
95
99
  },
96
100
  undefined,
@@ -99,6 +103,13 @@ export async function POST(req: Request, context: RouteContext): Promise<Respons
99
103
  if (!channel) {
100
104
  return NextResponse.json({ error: 'Channel not found' }, { status: 404 })
101
105
  }
106
+ // Resolve credentials at the channel's OWN org, not the caller's selected org.
107
+ // Tenant-wide channels store `organization_id = NULL` and their credentials
108
+ // live at `organization_id = tenantId` (see connect-credential-channel.ts), so
109
+ // the read key must be `channel.organizationId ?? tenantId` — keying on the
110
+ // session org would resolve `{}` for a tenant-wide channel viewed from a
111
+ // non-null org.
112
+ const channelCredentialsOrg = channel.organizationId ?? (auth.tenantId as string)
102
113
  // Load features via RBAC service so admin bypass (`communication_channels.admin`,
103
114
  // wildcards, super-admin) is honoured. The `auth` object from
104
115
  // `getAuthFromRequest` carries identity only — feature ACLs live in the RBAC
@@ -172,14 +183,14 @@ export async function POST(req: Request, context: RouteContext): Promise<Respons
172
183
  (await credentialsService
173
184
  .resolve(`channel_${channel.providerKey}`, {
174
185
  tenantId: auth.tenantId as string,
175
- organizationId: organizationId ?? (auth.tenantId as string),
186
+ organizationId: channelCredentialsOrg,
176
187
  userId: channel.userId ?? null,
177
188
  })
178
189
  .catch(() => null)) ?? {}
179
190
  }
180
191
  const credentialScope = {
181
192
  tenantId: auth.tenantId as string,
182
- organizationId: organizationId ?? (auth.tenantId as string),
193
+ organizationId: channelCredentialsOrg,
183
194
  userId: channel.userId ?? null,
184
195
  }
185
196
  const refreshed = await refreshCredentialsIfNeeded(
@@ -203,7 +214,7 @@ export async function POST(req: Request, context: RouteContext): Promise<Respons
203
214
  credentials,
204
215
  scope: {
205
216
  tenantId: auth.tenantId as string,
206
- organizationId: organizationId ?? (auth.tenantId as string),
217
+ organizationId: channelCredentialsOrg,
207
218
  },
208
219
  metadata: { to: body.to, subject: body.subject ?? 'Test send', testSend: true },
209
220
  })
@@ -9,6 +9,7 @@ import {
9
9
  type ConnectCredentialChannelInput,
10
10
  type ConnectCredentialChannelResult,
11
11
  } from '../../../../../commands/connect-credential-channel'
12
+ import type { ChannelAdapterRegistry } from '../../../../../lib/registry'
12
13
  import { validateRouteMutationGuard } from '../../../../../lib/route-mutation-guard'
13
14
 
14
15
  export const metadata = {
@@ -44,6 +45,24 @@ export async function POST(req: Request): Promise<Response> {
44
45
  }
45
46
 
46
47
  const container = await createRequestContainer()
48
+
49
+ // Tenant-scoped providers (push: FCM/APNs/Expo) must go through the admin-gated
50
+ // tenant route, not this per-user one (granted to manager/employee via
51
+ // `connect_user_channel`). Reject them here so a non-admin can't mint a
52
+ // privileged tenant-wide channel. The command enforces the same invariant
53
+ // (`wrong_scope_for_route`) as defense-in-depth.
54
+ const adapterRegistry = container.resolve('channelAdapterRegistry') as ChannelAdapterRegistry
55
+ const earlyAdapter = adapterRegistry.get(body.providerKey)
56
+ if (earlyAdapter && earlyAdapter.channelScope === 'tenant') {
57
+ return NextResponse.json(
58
+ {
59
+ error: 'This provider is connected tenant-wide by an administrator, not per-user.',
60
+ code: 'provider_is_tenant_scoped',
61
+ },
62
+ { status: 403 },
63
+ )
64
+ }
65
+
47
66
  const guard = await validateRouteMutationGuard({
48
67
  container,
49
68
  req,
@@ -93,8 +112,24 @@ export async function POST(req: Request): Promise<Response> {
93
112
  if (result.status === 'no_adapter') {
94
113
  return NextResponse.json({ error: result.reason }, { status: 404 })
95
114
  }
115
+ if (result.status === 'wrong_scope_for_route') {
116
+ return NextResponse.json(
117
+ {
118
+ error: 'This provider is connected tenant-wide by an administrator, not per-user.',
119
+ code: 'provider_is_tenant_scoped',
120
+ },
121
+ { status: 403 },
122
+ )
123
+ }
96
124
  if (result.status === 'validation_failed') {
97
- return NextResponse.json({ error: 'Credential validation failed', fieldErrors: result.errors }, { status: 422 })
125
+ return NextResponse.json(
126
+ {
127
+ error: 'Credential validation failed',
128
+ fieldErrors: result.errors,
129
+ ...(result.errorCodes ? { fieldErrorCodes: result.errorCodes } : {}),
130
+ },
131
+ { status: 422 },
132
+ )
98
133
  }
99
134
  if (result.status === 'duplicate_mailbox') {
100
135
  return NextResponse.json(
@@ -124,6 +159,7 @@ export const openApi = {
124
159
  responses: [
125
160
  { status: 201, description: 'Channel connected' },
126
161
  { status: 401, description: 'Unauthorized' },
162
+ { status: 403, description: 'Provider is tenant-scoped — use the tenant connect route' },
127
163
  { status: 404, description: 'Unknown provider' },
128
164
  { status: 409, description: 'Mailbox already connected via another provider' },
129
165
  { status: 422, description: 'Invalid body or credential validation failed' },
@@ -0,0 +1,175 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
4
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
+ import type { CommandBus } from '@open-mercato/shared/lib/commands'
6
+ import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
7
+ import {
8
+ COMMUNICATION_CHANNELS_CONNECT_CREDENTIAL_CHANNEL_COMMAND_ID,
9
+ type ConnectCredentialChannelInput,
10
+ type ConnectCredentialChannelResult,
11
+ } from '../../../../../commands/connect-credential-channel'
12
+ import type { ChannelAdapterRegistry } from '../../../../../lib/registry'
13
+ import { validateRouteMutationGuard } from '../../../../../lib/route-mutation-guard'
14
+
15
+ export const metadata = {
16
+ path: '/communication_channels/channels/connect/tenant-credentials',
17
+ POST: {
18
+ requireAuth: true,
19
+ requireFeatures: ['communication_channels.connect_tenant_channel'],
20
+ },
21
+ }
22
+
23
+ const bodySchema = z.object({
24
+ providerKey: z.string().min(1).max(64),
25
+ displayName: z.string().min(1).max(255),
26
+ credentials: z.record(z.string(), z.unknown()),
27
+ pollIntervalSeconds: z.number().int().positive().max(86_400).optional(),
28
+ })
29
+
30
+ export async function POST(req: Request): Promise<Response> {
31
+ const auth = await getAuthFromRequest(req)
32
+ if (!auth?.sub || !auth?.tenantId) {
33
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
34
+ }
35
+
36
+ let body: z.infer<typeof bodySchema>
37
+ try {
38
+ const json = await readJsonSafe(req, null)
39
+ body = bodySchema.parse(json)
40
+ } catch (err) {
41
+ return NextResponse.json(
42
+ { error: err instanceof Error ? err.message : 'Invalid request body' },
43
+ { status: 422 },
44
+ )
45
+ }
46
+
47
+ const container = await createRequestContainer()
48
+
49
+ // Reject providers that are NOT tenant-scoped so an admin can't force a
50
+ // per-user provider (Gmail/IMAP) into a shared tenant-wide channel. Push
51
+ // providers (FCM/APNs/Expo) declare `channelScope: 'tenant'` on their adapter.
52
+ const adapterRegistry = container.resolve('channelAdapterRegistry') as ChannelAdapterRegistry
53
+ const adapter = adapterRegistry.get(body.providerKey)
54
+ if (!adapter) {
55
+ return NextResponse.json(
56
+ { error: `No adapter registered for provider '${body.providerKey}'` },
57
+ { status: 404 },
58
+ )
59
+ }
60
+ if (adapter.channelScope !== 'tenant') {
61
+ return NextResponse.json(
62
+ {
63
+ error: 'This provider connects per-user channels; use the personal channel connect flow instead.',
64
+ code: 'provider_not_tenant_scoped',
65
+ },
66
+ { status: 400 },
67
+ )
68
+ }
69
+
70
+ const guard = await validateRouteMutationGuard({
71
+ container,
72
+ req,
73
+ auth,
74
+ input: {
75
+ resourceKind: 'communication_channels.channel',
76
+ resourceId: `new:${body.providerKey}`,
77
+ operation: 'create',
78
+ mutationPayload: {
79
+ providerKey: body.providerKey,
80
+ displayName: body.displayName,
81
+ pollIntervalSeconds: body.pollIntervalSeconds,
82
+ },
83
+ },
84
+ })
85
+ if ('response' in guard) return guard.response
86
+
87
+ const commandBus = container.resolve('commandBus') as CommandBus
88
+ const orgId = (auth as { orgId?: string | null }).orgId ?? null
89
+
90
+ const input: ConnectCredentialChannelInput = {
91
+ providerKey: body.providerKey,
92
+ displayName: body.displayName,
93
+ credentials: body.credentials,
94
+ pollIntervalSeconds: body.pollIntervalSeconds,
95
+ // Tenant-wide: no owning user. The command re-derives the effective scope
96
+ // from the adapter, so the channel + credentials are stored with user_id NULL.
97
+ userId: null,
98
+ scope: {
99
+ tenantId: auth.tenantId as string,
100
+ organizationId: orgId,
101
+ },
102
+ }
103
+ const { result } = await commandBus.execute<
104
+ ConnectCredentialChannelInput,
105
+ ConnectCredentialChannelResult
106
+ >(COMMUNICATION_CHANNELS_CONNECT_CREDENTIAL_CHANNEL_COMMAND_ID, {
107
+ input,
108
+ ctx: {
109
+ container,
110
+ auth: auth as never,
111
+ organizationScope: null,
112
+ selectedOrganizationId: orgId,
113
+ organizationIds: orgId ? [orgId] : null,
114
+ },
115
+ })
116
+
117
+ if (result.status === 'no_adapter') {
118
+ return NextResponse.json({ error: result.reason }, { status: 404 })
119
+ }
120
+ if (result.status === 'wrong_scope_for_route') {
121
+ // Unreachable in practice — this route always dispatches with userId: null —
122
+ // but handle it so the result union narrows and the invariant is explicit.
123
+ return NextResponse.json(
124
+ { error: 'Unexpected per-user scope on the tenant connect route.', code: 'wrong_scope_for_route' },
125
+ { status: 500 },
126
+ )
127
+ }
128
+ if (result.status === 'validation_failed') {
129
+ // `fieldErrors` stays English for API consumers and logs; `fieldErrorCodes`
130
+ // lets the connect widget render a localized message per field.
131
+ return NextResponse.json(
132
+ {
133
+ error: 'Credential validation failed',
134
+ fieldErrors: result.errors,
135
+ ...(result.errorCodes ? { fieldErrorCodes: result.errorCodes } : {}),
136
+ },
137
+ { status: 422 },
138
+ )
139
+ }
140
+ if (result.status === 'duplicate_mailbox') {
141
+ return NextResponse.json(
142
+ {
143
+ error: `This mailbox is already connected via "${result.existingProviderKey}". Disconnect it there before connecting it again with a different provider.`,
144
+ code: 'mailbox_already_connected',
145
+ },
146
+ { status: 409 },
147
+ )
148
+ }
149
+ await guard.afterSuccess()
150
+ return NextResponse.json(
151
+ {
152
+ channelId: result.channelId,
153
+ externalIdentifier: result.externalIdentifier,
154
+ },
155
+ { status: 201 },
156
+ )
157
+ }
158
+
159
+ export const openApi = {
160
+ tags: ['CommunicationChannels'],
161
+ methods: {
162
+ POST: {
163
+ summary: 'Connect a tenant-wide credential-based channel (push: FCM/APNs/Expo)',
164
+ tags: ['CommunicationChannels'],
165
+ responses: [
166
+ { status: 201, description: 'Channel connected' },
167
+ { status: 400, description: 'Provider is not tenant-scoped' },
168
+ { status: 401, description: 'Unauthorized' },
169
+ { status: 404, description: 'Unknown provider' },
170
+ { status: 422, description: 'Invalid body or credential validation failed' },
171
+ ],
172
+ },
173
+ },
174
+ }
175
+ export default POST
@@ -34,8 +34,17 @@ export default function ChannelsListPage() {
34
34
  const [page, setPage] = React.useState(1)
35
35
  const [isLoading, setIsLoading] = React.useState(true)
36
36
  const [errorMessage, setErrorMessage] = React.useState<string | null>(null)
37
+ // Bumped by injected connect widgets (FCM/APNs/Expo) so a freshly connected
38
+ // tenant-wide channel appears without a manual page reload.
39
+ const [reloadToken, setReloadToken] = React.useState(0)
37
40
  const pageSize = 50
38
41
 
42
+ const reload = React.useCallback(() => setReloadToken((token) => token + 1), [])
43
+ const injectionContext = React.useMemo(
44
+ () => ({ tableId: extensionPoints.hosts.channelsTable.tableId, reload }),
45
+ [reload],
46
+ )
47
+
39
48
  React.useEffect(() => {
40
49
  let cancelled = false
41
50
  async function load() {
@@ -70,7 +79,7 @@ export default function ChannelsListPage() {
70
79
  return () => {
71
80
  cancelled = true
72
81
  }
73
- }, [page, t])
82
+ }, [page, reloadToken, t])
74
83
 
75
84
  const columns = React.useMemo<ColumnDef<ChannelRow>[]>(
76
85
  () => [
@@ -116,6 +125,7 @@ export default function ChannelsListPage() {
116
125
  <DataTable<ChannelRow>
117
126
  title={t('communication_channels.nav.title', 'Communication Channels')}
118
127
  extensionTableId={extensionPoints.hosts.channelsTable.tableId}
128
+ injectionContext={injectionContext}
119
129
  columns={columns}
120
130
  data={rows}
121
131
  isLoading={isLoading}