@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,226 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { EntityManager } from '@mikro-orm/postgresql'
4
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
6
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
7
+ import { isUniqueViolation } from '@open-mercato/shared/lib/crud/errors'
8
+ import { createLogger } from '@open-mercato/shared/lib/logger'
9
+ import { NotificationType } from '../../../../../data/entities'
10
+ import { syncNotificationTypes } from '../../../../../lib/notification-type-registry'
11
+ import { computeChannelsPatch } from '../../../../../lib/typeChannelSettings'
12
+ import {
13
+ applyTypeOverride,
14
+ loadTypeOverrideForUpdate,
15
+ resolveCatalogueTranslate,
16
+ resolveEffectiveChannels,
17
+ resolveRegisteredChannelIds,
18
+ typeItem,
19
+ } from '../../../../../lib/typeCatalogue'
20
+ import { notificationTypeItemSchema } from '../../../../../data/validators'
21
+ import { errorResponseSchema } from '../../../../openapi'
22
+ import {
23
+ NOTIFICATION_SETTINGS_RESOURCE_KIND,
24
+ notificationCrudErrorResponse,
25
+ runGuardedNotificationWrite,
26
+ } from '../../../../../lib/routeHelpers'
27
+
28
+ const logger = createLogger('notifications').child({ component: 'type-channels-api' })
29
+
30
+ const paramsSchema = z.object({
31
+ id: z.string().min(1),
32
+ channel: z.string().min(1),
33
+ })
34
+
35
+ export const metadata = {
36
+ PUT: { requireAuth: true, requireFeatures: ['notifications.manage'] },
37
+ DELETE: { requireAuth: true, requireFeatures: ['notifications.manage'] },
38
+ }
39
+
40
+ type RouteContext = { params?: { id?: string; channel?: string } | Promise<{ id?: string; channel?: string }> }
41
+
42
+ async function readParams(ctx: RouteContext) {
43
+ const raw = await ctx.params
44
+ return paramsSchema.parse({ id: raw?.id, channel: raw?.channel })
45
+ }
46
+
47
+ /**
48
+ * Enable (`PUT`) or disable (`DELETE`) ONE channel for a notification type in the caller's tenant.
49
+ *
50
+ * Why a per-channel sub-resource rather than PATCHing the whole `channels` array: the array form
51
+ * makes the client read-modify-write the full set, so two operators editing the same type from
52
+ * stale views silently clobber each other — and while no override row exists yet there is no
53
+ * `updated_at` to version-lock against, so the optimistic-lock guard cannot even detect it. Here
54
+ * the server derives the next set from the CURRENT stored state under a row lock, so a concurrent
55
+ * toggle of a different channel survives. Same shape as the other member-collection endpoints in
56
+ * the repo (`customers/people/{id}/companies/{companyId}`, `catalog` media attach/detach).
57
+ *
58
+ * The full-array `PATCH /api/notifications/types` stays supported and unchanged for callers that
59
+ * genuinely want to set the whole set at once.
60
+ */
61
+ async function toggleChannel(req: Request, ctx: RouteContext, enabled: boolean): Promise<Response> {
62
+ const { t } = await resolveTranslations()
63
+ const auth = await getAuthFromRequest(req)
64
+ if (!auth?.sub || !auth.tenantId) {
65
+ return NextResponse.json({ error: t('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })
66
+ }
67
+ const tenantId = auth.tenantId
68
+
69
+ let params: z.infer<typeof paramsSchema>
70
+ try {
71
+ params = await readParams(ctx)
72
+ } catch {
73
+ return NextResponse.json(
74
+ { error: t('api.errors.invalidPayload', 'Invalid request body') },
75
+ { status: 400 },
76
+ )
77
+ }
78
+
79
+ const container = await createRequestContainer()
80
+ try {
81
+ const em = container.resolve('em') as EntityManager
82
+ await syncNotificationTypes(em)
83
+ const row = await em.findOne(NotificationType, {
84
+ id: params.id,
85
+ $or: [{ tenantId: null }, { tenantId }],
86
+ })
87
+ if (!row) {
88
+ return NextResponse.json(
89
+ { error: t('notifications.types.unknownType', 'Unknown notification type') },
90
+ { status: 404 },
91
+ )
92
+ }
93
+
94
+ // Reject unregistered channels up front: silently storing one would put a dead entry in the
95
+ // eligibility set that no delivery strategy can ever satisfy.
96
+ const registeredChannelIds = resolveRegisteredChannelIds()
97
+ if (!registeredChannelIds.includes(params.channel)) {
98
+ return NextResponse.json(
99
+ { error: t('notifications.types.unknownChannel', 'Unknown notification channel') },
100
+ { status: 404 },
101
+ )
102
+ }
103
+
104
+ const translate = await resolveCatalogueTranslate(req)
105
+
106
+ const guarded = await runGuardedNotificationWrite(
107
+ container,
108
+ {
109
+ tenantId,
110
+ organizationId: auth.orgId ?? null,
111
+ userId: auth.sub ?? null,
112
+ },
113
+ req,
114
+ {
115
+ resourceKind: NOTIFICATION_SETTINGS_RESOURCE_KIND,
116
+ resourceId: row.id,
117
+ operation: 'update',
118
+ payload: { id: row.id, channel: params.channel, enabled },
119
+ },
120
+ async () => {
121
+ // optimistic-lock-exempt: notification type channel add/remove. The next set is derived
122
+ // server-side from the current stored state inside a transaction that holds a row lock on
123
+ // the override, so a concurrent toggle cannot be lost — there is no shared aggregate for a
124
+ // version check to protect. Mirrors the add/remove endpoints in customers/catalog/staff.
125
+ const applyOnce = async () => {
126
+ return em.transactional(async (tem) => {
127
+ const existing = await loadTypeOverrideForUpdate(tem, tenantId, row.id)
128
+ const base = resolveEffectiveChannels(row.id, existing?.channels, registeredChannelIds)
129
+ // Unchecking the last channel maps to `null` (clear the override → the code-declared
130
+ // default reapplies) rather than an empty set, which would black-hole the type.
131
+ const nextChannels = computeChannelsPatch(base, params.channel, enabled)
132
+ const override = applyTypeOverride(tem, {
133
+ tenantId,
134
+ notificationTypeId: row.id,
135
+ existing,
136
+ nextChannels,
137
+ nextNonOptOut: existing?.nonOptOut ?? null,
138
+ })
139
+ await tem.flush()
140
+ return typeItem(row, override, translate)
141
+ })
142
+ }
143
+
144
+ try {
145
+ return await applyOnce()
146
+ } catch (err) {
147
+ // First-write race: no row existed to lock, so a concurrent INSERT for this
148
+ // (tenant, type) won and the partial unique index rejected ours. A single-channel
149
+ // toggle is idempotent, so re-apply it on top of the winner instead of surfacing a
150
+ // conflict the operator would only have to resolve by clicking again.
151
+ if (!isUniqueViolation(err)) throw err
152
+ em.clear()
153
+ return applyOnce()
154
+ }
155
+ },
156
+ )
157
+ if (!guarded.ok) return guarded.response
158
+ return NextResponse.json({ ok: true, item: guarded.result })
159
+ } catch (error) {
160
+ const crudResponse = notificationCrudErrorResponse(error)
161
+ if (crudResponse) return crudResponse
162
+ logger.error('notification type channel toggle failed', {
163
+ typeId: params.id,
164
+ channel: params.channel,
165
+ enabled,
166
+ error: error instanceof Error ? error.message : String(error),
167
+ })
168
+ return NextResponse.json(
169
+ { error: t('api.errors.internal', 'Internal error') },
170
+ { status: 500 },
171
+ )
172
+ } finally {
173
+ const disposable = container as unknown as { dispose?: () => Promise<void> }
174
+ if (typeof disposable.dispose === 'function') await disposable.dispose()
175
+ }
176
+ }
177
+
178
+ export async function PUT(req: Request, ctx: RouteContext): Promise<Response> {
179
+ return toggleChannel(req, ctx, true)
180
+ }
181
+
182
+ export async function DELETE(req: Request, ctx: RouteContext): Promise<Response> {
183
+ return toggleChannel(req, ctx, false)
184
+ }
185
+
186
+ const toggleResponseSchema = z.object({
187
+ ok: z.literal(true),
188
+ item: notificationTypeItemSchema,
189
+ })
190
+
191
+ export const openApi = {
192
+ PUT: {
193
+ summary: 'Enable one delivery channel for a notification type',
194
+ description:
195
+ 'Adds a single channel to the tenant\'s eligibility set for this notification type. The next set is derived server-side from the currently stored state under a row lock, so concurrent toggles of different channels do not overwrite each other — prefer this over `PATCH /api/notifications/types` with a full `channels` array whenever a UI toggles one cell at a time. Idempotent: enabling an already-enabled channel is a no-op. Returns the refreshed catalogue item.',
196
+ tags: ['Notifications'],
197
+ responses: {
198
+ 200: {
199
+ description: 'Updated notification type',
200
+ content: { 'application/json': { schema: toggleResponseSchema } },
201
+ },
202
+ 401: { description: 'Unauthorized', content: { 'application/json': { schema: errorResponseSchema } } },
203
+ 404: {
204
+ description: 'Unknown notification type or unregistered channel',
205
+ content: { 'application/json': { schema: errorResponseSchema } },
206
+ },
207
+ },
208
+ },
209
+ DELETE: {
210
+ summary: 'Disable one delivery channel for a notification type',
211
+ description:
212
+ 'Removes a single channel from the tenant\'s eligibility set for this notification type, with the same concurrency-safe server-side derivation as `PUT`. Removing the last remaining channel clears the override entirely so the code-declared default reapplies (an empty set is never stored — it would leave the type undeliverable). Idempotent: disabling an already-disabled channel is a no-op.',
213
+ tags: ['Notifications'],
214
+ responses: {
215
+ 200: {
216
+ description: 'Updated notification type',
217
+ content: { 'application/json': { schema: toggleResponseSchema } },
218
+ },
219
+ 401: { description: 'Unauthorized', content: { 'application/json': { schema: errorResponseSchema } } },
220
+ 404: {
221
+ description: 'Unknown notification type or unregistered channel',
222
+ content: { 'application/json': { schema: errorResponseSchema } },
223
+ },
224
+ },
225
+ },
226
+ }
@@ -0,0 +1,239 @@
1
+ import { NextResponse } from 'next/server'
2
+ import type { EntityManager } from '@mikro-orm/postgresql'
3
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
4
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
5
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
6
+ import { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
7
+ import { conflict, isUniqueViolation } from '@open-mercato/shared/lib/crud/errors'
8
+ import { createLogger } from '@open-mercato/shared/lib/logger'
9
+ import { z } from 'zod'
10
+ import { NotificationType, NotificationTypeOverride } from '../../data/entities'
11
+ import { syncNotificationTypes } from '../../lib/notification-type-registry'
12
+ import { applyTypeOverride, resolveCatalogueTranslate, typeItem } from '../../lib/typeCatalogue'
13
+ import { notificationTypeItemSchema, updateNotificationTypeSchema } from '../../data/validators'
14
+ import { errorResponseSchema } from '../openapi'
15
+ import {
16
+ NOTIFICATION_SETTINGS_RESOURCE_KIND,
17
+ notificationCrudErrorResponse,
18
+ runGuardedNotificationWrite,
19
+ } from '../../lib/routeHelpers'
20
+
21
+ const logger = createLogger('notifications').child({ component: 'types-api' })
22
+
23
+ export const metadata = {
24
+ GET: { requireAuth: true, requireFeatures: ['notifications.view'] },
25
+ PATCH: { requireAuth: true, requireFeatures: ['notifications.manage'] },
26
+ }
27
+
28
+ export async function GET(req: Request) {
29
+ const { t } = await resolveTranslations()
30
+ const auth = await getAuthFromRequest(req)
31
+ if (!auth?.sub || !auth.tenantId) {
32
+ return NextResponse.json({ error: t('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })
33
+ }
34
+
35
+ const container = await createRequestContainer()
36
+ try {
37
+ const em = container.resolve('em') as EntityManager
38
+ // Read-through mirror: ensure the catalogue is reflected in the DB (once per process).
39
+ await syncNotificationTypes(em)
40
+ const rows = await em.find(
41
+ NotificationType,
42
+ { $or: [{ tenantId: null }, { tenantId: auth.tenantId }] },
43
+ { orderBy: { id: 'asc' } },
44
+ )
45
+ const overrides = await em.find(NotificationTypeOverride, {
46
+ tenantId: auth.tenantId,
47
+ notificationTypeId: { $in: rows.map((row) => row.id) },
48
+ })
49
+ const overrideByType = new Map(overrides.map((override) => [override.notificationTypeId, override]))
50
+ const translate = await resolveCatalogueTranslate(req)
51
+ return NextResponse.json({
52
+ items: rows.map((row) => typeItem(row, overrideByType.get(row.id), translate)),
53
+ })
54
+ } finally {
55
+ const disposable = container as unknown as { dispose?: () => Promise<void> }
56
+ if (typeof disposable.dispose === 'function') await disposable.dispose()
57
+ }
58
+ }
59
+
60
+ export async function PATCH(req: Request) {
61
+ // Admin write: the echoed item stays on the request-context locale (no `?locale=`
62
+ // override), so the confirmation matches the operator's own session.
63
+ const { t, translate } = await resolveTranslations()
64
+ const auth = await getAuthFromRequest(req)
65
+ if (!auth?.sub || !auth.tenantId) {
66
+ return NextResponse.json({ error: t('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })
67
+ }
68
+ const tenantId = auth.tenantId
69
+
70
+ let body: unknown
71
+ try {
72
+ body = await req.json()
73
+ } catch {
74
+ return NextResponse.json(
75
+ { error: t('api.errors.invalidPayload', 'Invalid request body') },
76
+ { status: 400 },
77
+ )
78
+ }
79
+
80
+ const parsed = updateNotificationTypeSchema.safeParse(body)
81
+ if (!parsed.success) {
82
+ return NextResponse.json(
83
+ { error: t('notifications.types.invalidChannels', 'Invalid channels payload') },
84
+ { status: 400 },
85
+ )
86
+ }
87
+
88
+ const container = await createRequestContainer()
89
+ try {
90
+ const em = container.resolve('em') as EntityManager
91
+ await syncNotificationTypes(em)
92
+ const row = await em.findOne(NotificationType, {
93
+ id: parsed.data.id,
94
+ $or: [{ tenantId: null }, { tenantId }],
95
+ })
96
+ if (!row) {
97
+ return NextResponse.json(
98
+ { error: t('notifications.types.unknownType', 'Unknown notification type') },
99
+ { status: 404 },
100
+ )
101
+ }
102
+ const existing = await em.findOne(NotificationTypeOverride, {
103
+ tenantId,
104
+ notificationTypeId: row.id,
105
+ })
106
+
107
+ const guarded = await runGuardedNotificationWrite(
108
+ container,
109
+ {
110
+ tenantId,
111
+ organizationId: auth.orgId ?? null,
112
+ userId: auth.sub ?? null,
113
+ },
114
+ req,
115
+ {
116
+ resourceKind: NOTIFICATION_SETTINGS_RESOURCE_KIND,
117
+ resourceId: row.id,
118
+ operation: 'update',
119
+ payload: parsed.data as unknown as Record<string, unknown>,
120
+ },
121
+ async () => {
122
+ // Same 409 contract as the CRUD guard: a stale admin view (another operator saved
123
+ // since this client loaded the catalogue) must not silently clobber the newer
124
+ // override — the full `channels` array replaces, so a blind write loses edits.
125
+ enforceCommandOptimisticLock({
126
+ resourceKind: NOTIFICATION_SETTINGS_RESOURCE_KIND,
127
+ resourceId: row.id,
128
+ current: existing?.updatedAt ?? null,
129
+ request: req,
130
+ })
131
+ const nextChannels = parsed.data.channels !== undefined ? parsed.data.channels : existing?.channels ?? null
132
+ const nextNonOptOut = parsed.data.nonOptOut !== undefined ? parsed.data.nonOptOut : existing?.nonOptOut ?? null
133
+ const override = applyTypeOverride(em, {
134
+ tenantId,
135
+ notificationTypeId: row.id,
136
+ existing,
137
+ nextChannels,
138
+ nextNonOptOut,
139
+ })
140
+ try {
141
+ await em.flush()
142
+ } catch (err) {
143
+ // Concurrent first-write race: another operator created the override for this (tenant, type)
144
+ // between our read (existing === null) and this flush, so the partial unique index rejects
145
+ // ours. There was no version to compare, so the optimistic-lock guard couldn't fire — surface
146
+ // the same 409 it would rather than letting the unique violation fall through to a generic 500.
147
+ if (isUniqueViolation(err)) {
148
+ throw conflict(
149
+ t(
150
+ 'notifications.types.overrideConflict',
151
+ 'Another operator just saved this notification type. Reload and try again.',
152
+ ),
153
+ )
154
+ }
155
+ throw err
156
+ }
157
+ return typeItem(row, override, translate)
158
+ },
159
+ )
160
+ if (!guarded.ok) return guarded.response
161
+ return NextResponse.json({ ok: true, item: guarded.result })
162
+ } catch (error) {
163
+ const crudResponse = notificationCrudErrorResponse(error)
164
+ if (crudResponse) return crudResponse
165
+ logger.error('notification type override update failed', {
166
+ typeId: parsed.data.id,
167
+ error: error instanceof Error ? error.message : String(error),
168
+ })
169
+ return NextResponse.json(
170
+ { error: t('api.errors.internal', 'Internal error') },
171
+ { status: 500 },
172
+ )
173
+ } finally {
174
+ const disposable = container as unknown as { dispose?: () => Promise<void> }
175
+ if (typeof disposable.dispose === 'function') await disposable.dispose()
176
+ }
177
+ }
178
+
179
+ export const openApi = {
180
+ GET: {
181
+ summary: 'List notification types',
182
+ description: 'Returns the notification type catalogue (system-wide + tenant) so clients can render a preferences screen. `channels` is the effective channel eligibility for the caller\'s tenant (stored override, else the code-declared set; `null` = every channel); a channel outside it never delivers in that tenant and cannot be enabled by users. `updatedAt` is the override row\'s optimistic-lock version (`null` when the tenant stores no override). `label`, `description` and `categoryLabel` are resolved server-side using the request locale (`?locale=`, `x-locale`, the `locale` cookie, or `Accept-Language`) so clients without the app dictionary can render them directly; `categoryLabel` falls back to the raw category key when the owning module ships no `notifications.categories.<key>` translation, so `categoryLabel === category` signals "no server-side label". `category` defaults to the prefix before the first dot in the type id and is the stable grouping key — group on `category`, display `categoryLabel`.',
183
+ tags: ['Notifications'],
184
+ responses: {
185
+ 200: {
186
+ description: 'Notification type catalogue',
187
+ content: {
188
+ 'application/json': {
189
+ schema: z.object({ items: z.array(notificationTypeItemSchema) }),
190
+ },
191
+ },
192
+ },
193
+ 401: {
194
+ description: 'Unauthorized',
195
+ content: { 'application/json': { schema: errorResponseSchema } },
196
+ },
197
+ },
198
+ },
199
+ PATCH: {
200
+ summary: 'Override a notification type\'s channel eligibility and opt-out governance for the caller\'s tenant',
201
+ description: 'Tenant-scoped operator overrides for a notification type (stored in `notification_type_overrides`; other tenants are unaffected). `channels` replaces the code-declared eligibility (a channel outside the effective set is completely off for the tenant: it beats user preferences and `nonOptOut`, and preference UIs lock the cell). `nonOptOut` overrides the code-declared opt-out governance (`true` forces the type on for users, `false` makes a required type user-editable). Omitted fields stay untouched; pass `null` to clear a stored override and inherit the code declaration. Sends the standard optimistic-lock header contract: pass the item\'s `updatedAt` as `x-om-ext-optimistic-lock-expected-updated-at` to detect concurrent edits (409 on mismatch).',
202
+ tags: ['Notifications'],
203
+ requestBody: {
204
+ required: true,
205
+ content: {
206
+ 'application/json': { schema: updateNotificationTypeSchema },
207
+ },
208
+ },
209
+ responses: {
210
+ 200: {
211
+ description: 'Override saved',
212
+ content: {
213
+ 'application/json': {
214
+ schema: z.object({
215
+ ok: z.literal(true),
216
+ item: notificationTypeItemSchema,
217
+ }),
218
+ },
219
+ },
220
+ },
221
+ 400: {
222
+ description: 'Invalid request body',
223
+ content: { 'application/json': { schema: errorResponseSchema } },
224
+ },
225
+ 401: {
226
+ description: 'Unauthorized',
227
+ content: { 'application/json': { schema: errorResponseSchema } },
228
+ },
229
+ 404: {
230
+ description: 'Unknown notification type',
231
+ content: { 'application/json': { schema: errorResponseSchema } },
232
+ },
233
+ 409: {
234
+ description: 'Optimistic-lock conflict — another operator saved a newer override',
235
+ content: { 'application/json': { schema: errorResponseSchema } },
236
+ },
237
+ },
238
+ },
239
+ }
@@ -13,6 +13,7 @@ import {
13
13
  buildNotificationReadScopeWhere,
14
14
  getNotificationReadScopeTagOrganizationIds,
15
15
  } from '../../lib/notificationScope'
16
+ import { inAppVisibleFilter } from '../../lib/notificationVisibility'
16
17
 
17
18
  export const metadata = {
18
19
  GET: { requireAuth: true },
@@ -75,7 +76,10 @@ export async function GET(req: Request) {
75
76
  recipientUserId: userId,
76
77
  tenantId: scope.tenantId,
77
78
  status: 'unread',
78
- ...buildNotificationReadScopeWhere(scope),
79
+ $and: [
80
+ buildNotificationReadScopeWhere(scope),
81
+ inAppVisibleFilter(),
82
+ ],
79
83
  })
80
84
 
81
85
  if (cache && cacheKey) {
@@ -0,0 +1,26 @@
1
+ import React from 'react'
2
+
3
+ const usersCogIcon = React.createElement(
4
+ 'svg',
5
+ { width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' },
6
+ React.createElement('path', { d: 'M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2' }),
7
+ React.createElement('circle', { cx: 9, cy: 7, r: 4 }),
8
+ React.createElement('circle', { cx: 19, cy: 16, r: 2 }),
9
+ React.createElement('path', { d: 'M19 12v1' }),
10
+ React.createElement('path', { d: 'M19 19v1' }),
11
+ )
12
+
13
+ export const metadata = {
14
+ requireAuth: true,
15
+ requireFeatures: ['notifications.manage_user_preferences'],
16
+ pageTitle: 'User Notification Preferences',
17
+ pageTitleKey: 'notifications.preferences.admin.pageTitle',
18
+ pageGroup: 'Auth',
19
+ pageGroupKey: 'settings.sections.auth',
20
+ pageOrder: 5,
21
+ icon: usersCogIcon,
22
+ pageContext: 'settings' as const,
23
+ breadcrumb: [
24
+ { label: 'User Notification Preferences', labelKey: 'notifications.preferences.admin.pageTitle' },
25
+ ],
26
+ } as const
@@ -0,0 +1,12 @@
1
+ import { Page, PageBody } from '@open-mercato/ui/backend/Page'
2
+ import { NotificationUserPreferencesAdminPageClient } from '../../../frontend/NotificationUserPreferencesAdminPageClient'
3
+
4
+ export default async function NotificationUserPreferencesAdminPage() {
5
+ return (
6
+ <Page>
7
+ <PageBody>
8
+ <NotificationUserPreferencesAdminPageClient />
9
+ </PageBody>
10
+ </Page>
11
+ )
12
+ }
@@ -0,0 +1,31 @@
1
+ import React from 'react'
2
+
3
+ const slidersIcon = React.createElement(
4
+ 'svg',
5
+ { width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' },
6
+ React.createElement('line', { x1: 4, y1: 21, x2: 4, y2: 14 }),
7
+ React.createElement('line', { x1: 4, y1: 10, x2: 4, y2: 3 }),
8
+ React.createElement('line', { x1: 12, y1: 21, x2: 12, y2: 12 }),
9
+ React.createElement('line', { x1: 12, y1: 8, x2: 12, y2: 3 }),
10
+ React.createElement('line', { x1: 20, y1: 21, x2: 20, y2: 16 }),
11
+ React.createElement('line', { x1: 20, y1: 12, x2: 20, y2: 3 }),
12
+ React.createElement('line', { x1: 1, y1: 14, x2: 7, y2: 14 }),
13
+ React.createElement('line', { x1: 9, y1: 8, x2: 15, y2: 8 }),
14
+ React.createElement('line', { x1: 17, y1: 16, x2: 23, y2: 16 }),
15
+ )
16
+
17
+ export const metadata = {
18
+ requireAuth: true,
19
+ requireFeatures: ['notifications.manage_preferences'],
20
+ pageTitle: 'Notification Preferences',
21
+ pageTitleKey: 'notifications.preferences.pageTitle',
22
+ pageGroup: 'Profile',
23
+ pageGroupKey: 'notifications.preferences.profileGroup',
24
+ pageOrder: 30,
25
+ icon: slidersIcon,
26
+ pageContext: 'profile' as const,
27
+ breadcrumb: [
28
+ { label: 'Profile', labelKey: 'notifications.preferences.profileGroup' },
29
+ { label: 'Notification Preferences', labelKey: 'notifications.preferences.pageTitle' },
30
+ ],
31
+ } as const
@@ -0,0 +1,12 @@
1
+ import { Page, PageBody } from '@open-mercato/ui/backend/Page'
2
+ import { NotificationPreferencesPageClient } from '../../../frontend/NotificationPreferencesPageClient'
3
+
4
+ export default async function NotificationPreferencesProfilePage() {
5
+ return (
6
+ <Page>
7
+ <PageBody>
8
+ <NotificationPreferencesPageClient />
9
+ </PageBody>
10
+ </Page>
11
+ )
12
+ }