@open-mercato/core 0.4.2-canary-ba1d84349b → 0.4.2-canary-07dbc98202

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 (235) hide show
  1. package/dist/generated/entities/notification/index.js +57 -0
  2. package/dist/generated/entities/notification/index.js.map +7 -0
  3. package/dist/generated/entities.ids.generated.js +63 -59
  4. package/dist/generated/entities.ids.generated.js.map +2 -2
  5. package/dist/generated/entity-fields-registry.js +2 -0
  6. package/dist/generated/entity-fields-registry.js.map +2 -2
  7. package/dist/modules/api_docs/frontend/docs/api/page.js +3 -2
  8. package/dist/modules/api_docs/frontend/docs/api/page.js.map +2 -2
  9. package/dist/modules/auth/api/admin/nav.js +4 -3
  10. package/dist/modules/auth/api/admin/nav.js.map +2 -2
  11. package/dist/modules/auth/api/profile/route.js +155 -0
  12. package/dist/modules/auth/api/profile/route.js.map +7 -0
  13. package/dist/modules/auth/api/reset/confirm.js +25 -2
  14. package/dist/modules/auth/api/reset/confirm.js.map +2 -2
  15. package/dist/modules/auth/api/reset.js +23 -0
  16. package/dist/modules/auth/api/reset.js.map +2 -2
  17. package/dist/modules/auth/api/sidebar/preferences/route.js +14 -9
  18. package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
  19. package/dist/modules/auth/backend/auth/profile/page.js +99 -0
  20. package/dist/modules/auth/backend/auth/profile/page.js.map +7 -0
  21. package/dist/modules/auth/backend/auth/profile/page.meta.js +12 -0
  22. package/dist/modules/auth/backend/auth/profile/page.meta.js.map +7 -0
  23. package/dist/modules/auth/commands/users.js +55 -0
  24. package/dist/modules/auth/commands/users.js.map +2 -2
  25. package/dist/modules/auth/lib/setup-app.js +1 -0
  26. package/dist/modules/auth/lib/setup-app.js.map +2 -2
  27. package/dist/modules/auth/notifications.js +112 -0
  28. package/dist/modules/auth/notifications.js.map +7 -0
  29. package/dist/modules/auth/services/authService.js +3 -3
  30. package/dist/modules/auth/services/authService.js.map +2 -2
  31. package/dist/modules/business_rules/notifications.js +28 -0
  32. package/dist/modules/business_rules/notifications.js.map +7 -0
  33. package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js +37 -0
  34. package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js.map +7 -0
  35. package/dist/modules/catalog/notifications.js +28 -0
  36. package/dist/modules/catalog/notifications.js.map +7 -0
  37. package/dist/modules/catalog/subscribers/low-stock-notification.js +38 -0
  38. package/dist/modules/catalog/subscribers/low-stock-notification.js.map +7 -0
  39. package/dist/modules/configs/cli.js +6 -0
  40. package/dist/modules/configs/cli.js.map +2 -2
  41. package/dist/modules/customers/commands/deals.js +31 -0
  42. package/dist/modules/customers/commands/deals.js.map +2 -2
  43. package/dist/modules/customers/notifications.js +48 -0
  44. package/dist/modules/customers/notifications.js.map +7 -0
  45. package/dist/modules/notifications/acl.js +11 -0
  46. package/dist/modules/notifications/acl.js.map +7 -0
  47. package/dist/modules/notifications/api/[id]/action/route.js +74 -0
  48. package/dist/modules/notifications/api/[id]/action/route.js.map +7 -0
  49. package/dist/modules/notifications/api/[id]/dismiss/route.js +15 -0
  50. package/dist/modules/notifications/api/[id]/dismiss/route.js.map +7 -0
  51. package/dist/modules/notifications/api/[id]/read/route.js +15 -0
  52. package/dist/modules/notifications/api/[id]/read/route.js.map +7 -0
  53. package/dist/modules/notifications/api/[id]/restore/route.js +53 -0
  54. package/dist/modules/notifications/api/[id]/restore/route.js.map +7 -0
  55. package/dist/modules/notifications/api/batch/route.js +17 -0
  56. package/dist/modules/notifications/api/batch/route.js.map +7 -0
  57. package/dist/modules/notifications/api/feature/route.js +17 -0
  58. package/dist/modules/notifications/api/feature/route.js.map +7 -0
  59. package/dist/modules/notifications/api/mark-all-read/route.js +35 -0
  60. package/dist/modules/notifications/api/mark-all-read/route.js.map +7 -0
  61. package/dist/modules/notifications/api/openapi.js +76 -0
  62. package/dist/modules/notifications/api/openapi.js.map +7 -0
  63. package/dist/modules/notifications/api/role/route.js +17 -0
  64. package/dist/modules/notifications/api/role/route.js.map +7 -0
  65. package/dist/modules/notifications/api/route.js +85 -0
  66. package/dist/modules/notifications/api/route.js.map +7 -0
  67. package/dist/modules/notifications/api/settings/route.js +155 -0
  68. package/dist/modules/notifications/api/settings/route.js.map +7 -0
  69. package/dist/modules/notifications/api/unread-count/route.js +38 -0
  70. package/dist/modules/notifications/api/unread-count/route.js.map +7 -0
  71. package/dist/modules/notifications/backend/config/notifications/page.js +10 -0
  72. package/dist/modules/notifications/backend/config/notifications/page.js.map +7 -0
  73. package/dist/modules/notifications/backend/config/notifications/page.meta.js +24 -0
  74. package/dist/modules/notifications/backend/config/notifications/page.meta.js.map +7 -0
  75. package/dist/modules/notifications/cli.js +16 -0
  76. package/dist/modules/notifications/cli.js.map +7 -0
  77. package/dist/modules/notifications/data/entities.js +112 -0
  78. package/dist/modules/notifications/data/entities.js.map +7 -0
  79. package/dist/modules/notifications/data/validators.js +94 -0
  80. package/dist/modules/notifications/data/validators.js.map +7 -0
  81. package/dist/modules/notifications/di.js +13 -0
  82. package/dist/modules/notifications/di.js.map +7 -0
  83. package/dist/modules/notifications/emails/NotificationEmail.js +58 -0
  84. package/dist/modules/notifications/emails/NotificationEmail.js.map +7 -0
  85. package/dist/modules/notifications/frontend/NotificationInboxPageClient.js +44 -0
  86. package/dist/modules/notifications/frontend/NotificationInboxPageClient.js.map +7 -0
  87. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +219 -0
  88. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +7 -0
  89. package/dist/modules/notifications/index.js +14 -0
  90. package/dist/modules/notifications/index.js.map +7 -0
  91. package/dist/modules/notifications/lib/deliveryConfig.js +105 -0
  92. package/dist/modules/notifications/lib/deliveryConfig.js.map +7 -0
  93. package/dist/modules/notifications/lib/events.js +12 -0
  94. package/dist/modules/notifications/lib/events.js.map +7 -0
  95. package/dist/modules/notifications/lib/notificationBuilder.js +66 -0
  96. package/dist/modules/notifications/lib/notificationBuilder.js.map +7 -0
  97. package/dist/modules/notifications/lib/notificationFactory.js +54 -0
  98. package/dist/modules/notifications/lib/notificationFactory.js.map +7 -0
  99. package/dist/modules/notifications/lib/notificationMapper.js +34 -0
  100. package/dist/modules/notifications/lib/notificationMapper.js.map +7 -0
  101. package/dist/modules/notifications/lib/notificationRecipients.js +35 -0
  102. package/dist/modules/notifications/lib/notificationRecipients.js.map +7 -0
  103. package/dist/modules/notifications/lib/notificationService.js +279 -0
  104. package/dist/modules/notifications/lib/notificationService.js.map +7 -0
  105. package/dist/modules/notifications/lib/routeHelpers.js +101 -0
  106. package/dist/modules/notifications/lib/routeHelpers.js.map +7 -0
  107. package/dist/modules/notifications/lib/safeHref.js +24 -0
  108. package/dist/modules/notifications/lib/safeHref.js.map +7 -0
  109. package/dist/modules/notifications/migrations/Migration20260123000001.js +70 -0
  110. package/dist/modules/notifications/migrations/Migration20260123000001.js.map +7 -0
  111. package/dist/modules/notifications/migrations/Migration20260126150000.js +37 -0
  112. package/dist/modules/notifications/migrations/Migration20260126150000.js.map +7 -0
  113. package/dist/modules/notifications/subscribers/deliver-notification.js +139 -0
  114. package/dist/modules/notifications/subscribers/deliver-notification.js.map +7 -0
  115. package/dist/modules/notifications/workers/create-notification.worker.js +70 -0
  116. package/dist/modules/notifications/workers/create-notification.worker.js.map +7 -0
  117. package/dist/modules/sales/commands/documents.js +53 -0
  118. package/dist/modules/sales/commands/documents.js.map +2 -2
  119. package/dist/modules/sales/commands/payments.js +26 -0
  120. package/dist/modules/sales/commands/payments.js.map +2 -2
  121. package/dist/modules/sales/notifications.client.js +51 -0
  122. package/dist/modules/sales/notifications.client.js.map +7 -0
  123. package/dist/modules/sales/notifications.js +88 -0
  124. package/dist/modules/sales/notifications.js.map +7 -0
  125. package/dist/modules/sales/subscribers/quote-expiring-notification.js +38 -0
  126. package/dist/modules/sales/subscribers/quote-expiring-notification.js.map +7 -0
  127. package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js +137 -0
  128. package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js.map +7 -0
  129. package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js +137 -0
  130. package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js.map +7 -0
  131. package/dist/modules/sales/widgets/notifications/index.js +7 -0
  132. package/dist/modules/sales/widgets/notifications/index.js.map +7 -0
  133. package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js +60 -0
  134. package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js.map +7 -0
  135. package/dist/modules/staff/commands/leave-requests.js +79 -0
  136. package/dist/modules/staff/commands/leave-requests.js.map +2 -2
  137. package/dist/modules/staff/notifications.js +75 -0
  138. package/dist/modules/staff/notifications.js.map +7 -0
  139. package/dist/modules/workflows/notifications.js +28 -0
  140. package/dist/modules/workflows/notifications.js.map +7 -0
  141. package/dist/modules/workflows/subscribers/task-assigned-notification.js +38 -0
  142. package/dist/modules/workflows/subscribers/task-assigned-notification.js.map +7 -0
  143. package/generated/entities/notification/index.ts +27 -0
  144. package/generated/entities.ids.generated.ts +63 -59
  145. package/generated/entity-fields-registry.ts +2 -0
  146. package/package.json +2 -2
  147. package/src/modules/api_docs/frontend/docs/api/page.tsx +3 -2
  148. package/src/modules/auth/api/admin/nav.ts +10 -6
  149. package/src/modules/auth/api/profile/route.ts +160 -0
  150. package/src/modules/auth/api/reset/confirm.ts +25 -2
  151. package/src/modules/auth/api/reset.ts +23 -0
  152. package/src/modules/auth/api/sidebar/preferences/route.ts +21 -12
  153. package/src/modules/auth/backend/auth/profile/page.meta.ts +8 -0
  154. package/src/modules/auth/backend/auth/profile/page.tsx +127 -0
  155. package/src/modules/auth/commands/users.ts +68 -0
  156. package/src/modules/auth/i18n/de.json +29 -1
  157. package/src/modules/auth/i18n/en.json +29 -1
  158. package/src/modules/auth/i18n/es.json +29 -1
  159. package/src/modules/auth/i18n/pl.json +29 -1
  160. package/src/modules/auth/lib/setup-app.ts +1 -0
  161. package/src/modules/auth/notifications.ts +109 -0
  162. package/src/modules/auth/services/authService.ts +4 -4
  163. package/src/modules/business_rules/i18n/en.json +3 -1
  164. package/src/modules/business_rules/notifications.ts +25 -0
  165. package/src/modules/business_rules/subscribers/rule-execution-failed-notification.ts +50 -0
  166. package/src/modules/catalog/i18n/en.json +3 -1
  167. package/src/modules/catalog/notifications.ts +25 -0
  168. package/src/modules/catalog/subscribers/low-stock-notification.ts +52 -0
  169. package/src/modules/configs/cli.ts +6 -0
  170. package/src/modules/customers/commands/deals.ts +39 -0
  171. package/src/modules/customers/i18n/en.json +5 -1
  172. package/src/modules/customers/notifications.ts +44 -0
  173. package/src/modules/notifications/acl.ts +7 -0
  174. package/src/modules/notifications/api/[id]/action/route.ts +75 -0
  175. package/src/modules/notifications/api/[id]/dismiss/route.ts +12 -0
  176. package/src/modules/notifications/api/[id]/read/route.ts +12 -0
  177. package/src/modules/notifications/api/[id]/restore/route.ts +53 -0
  178. package/src/modules/notifications/api/batch/route.ts +14 -0
  179. package/src/modules/notifications/api/feature/route.ts +14 -0
  180. package/src/modules/notifications/api/mark-all-read/route.ts +34 -0
  181. package/src/modules/notifications/api/openapi.ts +76 -0
  182. package/src/modules/notifications/api/role/route.ts +14 -0
  183. package/src/modules/notifications/api/route.ts +92 -0
  184. package/src/modules/notifications/api/settings/route.ts +157 -0
  185. package/src/modules/notifications/api/unread-count/route.ts +38 -0
  186. package/src/modules/notifications/backend/config/notifications/page.meta.ts +22 -0
  187. package/src/modules/notifications/backend/config/notifications/page.tsx +12 -0
  188. package/src/modules/notifications/cli.ts +18 -0
  189. package/src/modules/notifications/data/entities.ts +99 -0
  190. package/src/modules/notifications/data/validators.ts +110 -0
  191. package/src/modules/notifications/di.ts +11 -0
  192. package/src/modules/notifications/emails/NotificationEmail.tsx +98 -0
  193. package/src/modules/notifications/frontend/NotificationInboxPageClient.tsx +42 -0
  194. package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +231 -0
  195. package/src/modules/notifications/i18n/de.json +50 -0
  196. package/src/modules/notifications/i18n/en.json +50 -0
  197. package/src/modules/notifications/i18n/es.json +50 -0
  198. package/src/modules/notifications/i18n/pl.json +50 -0
  199. package/src/modules/notifications/index.ts +12 -0
  200. package/src/modules/notifications/lib/deliveryConfig.ts +145 -0
  201. package/src/modules/notifications/lib/events.ts +48 -0
  202. package/src/modules/notifications/lib/notificationBuilder.ts +121 -0
  203. package/src/modules/notifications/lib/notificationFactory.ts +76 -0
  204. package/src/modules/notifications/lib/notificationMapper.ts +33 -0
  205. package/src/modules/notifications/lib/notificationRecipients.ts +83 -0
  206. package/src/modules/notifications/lib/notificationService.ts +414 -0
  207. package/src/modules/notifications/lib/routeHelpers.ts +151 -0
  208. package/src/modules/notifications/lib/safeHref.ts +29 -0
  209. package/src/modules/notifications/migrations/.snapshot-open-mercato.json +300 -0
  210. package/src/modules/notifications/migrations/Migration20260123000001.ts +73 -0
  211. package/src/modules/notifications/migrations/Migration20260126150000.ts +39 -0
  212. package/src/modules/notifications/subscribers/deliver-notification.ts +175 -0
  213. package/src/modules/notifications/workers/create-notification.worker.ts +122 -0
  214. package/src/modules/sales/commands/documents.ts +65 -0
  215. package/src/modules/sales/commands/payments.ts +33 -0
  216. package/src/modules/sales/i18n/de.json +20 -0
  217. package/src/modules/sales/i18n/en.json +25 -1
  218. package/src/modules/sales/i18n/es.json +20 -0
  219. package/src/modules/sales/i18n/pl.json +20 -0
  220. package/src/modules/sales/notifications.client.ts +65 -0
  221. package/src/modules/sales/notifications.ts +82 -0
  222. package/src/modules/sales/subscribers/quote-expiring-notification.ts +53 -0
  223. package/src/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.tsx +156 -0
  224. package/src/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.tsx +156 -0
  225. package/src/modules/sales/widgets/notifications/index.ts +2 -0
  226. package/src/modules/sales/widgets/notifications/useSalesDocumentTotals.ts +81 -0
  227. package/src/modules/staff/commands/leave-requests.ts +94 -0
  228. package/src/modules/staff/i18n/de.json +4 -0
  229. package/src/modules/staff/i18n/en.json +9 -1
  230. package/src/modules/staff/i18n/es.json +4 -0
  231. package/src/modules/staff/i18n/pl.json +4 -0
  232. package/src/modules/staff/notifications.ts +71 -0
  233. package/src/modules/workflows/i18n/en.json +3 -1
  234. package/src/modules/workflows/notifications.ts +25 -0
  235. package/src/modules/workflows/subscribers/task-assigned-notification.ts +53 -0
@@ -0,0 +1,300 @@
1
+ {
2
+ "namespaces": [
3
+ "public"
4
+ ],
5
+ "name": "public",
6
+ "tables": [
7
+ {
8
+ "columns": {
9
+ "id": {
10
+ "name": "id",
11
+ "type": "uuid",
12
+ "unsigned": false,
13
+ "autoincrement": false,
14
+ "primary": false,
15
+ "nullable": false,
16
+ "default": "gen_random_uuid()",
17
+ "mappedType": "uuid"
18
+ },
19
+ "recipient_user_id": {
20
+ "name": "recipient_user_id",
21
+ "type": "uuid",
22
+ "unsigned": false,
23
+ "autoincrement": false,
24
+ "primary": false,
25
+ "nullable": false,
26
+ "mappedType": "uuid"
27
+ },
28
+ "type": {
29
+ "name": "type",
30
+ "type": "text",
31
+ "unsigned": false,
32
+ "autoincrement": false,
33
+ "primary": false,
34
+ "nullable": false,
35
+ "mappedType": "text"
36
+ },
37
+ "title": {
38
+ "name": "title",
39
+ "type": "text",
40
+ "unsigned": false,
41
+ "autoincrement": false,
42
+ "primary": false,
43
+ "nullable": false,
44
+ "mappedType": "text"
45
+ },
46
+ "body": {
47
+ "name": "body",
48
+ "type": "text",
49
+ "unsigned": false,
50
+ "autoincrement": false,
51
+ "primary": false,
52
+ "nullable": true,
53
+ "mappedType": "text"
54
+ },
55
+ "icon": {
56
+ "name": "icon",
57
+ "type": "text",
58
+ "unsigned": false,
59
+ "autoincrement": false,
60
+ "primary": false,
61
+ "nullable": true,
62
+ "mappedType": "text"
63
+ },
64
+ "severity": {
65
+ "name": "severity",
66
+ "type": "text",
67
+ "unsigned": false,
68
+ "autoincrement": false,
69
+ "primary": false,
70
+ "nullable": false,
71
+ "default": "'info'",
72
+ "mappedType": "text"
73
+ },
74
+ "status": {
75
+ "name": "status",
76
+ "type": "text",
77
+ "unsigned": false,
78
+ "autoincrement": false,
79
+ "primary": false,
80
+ "nullable": false,
81
+ "default": "'unread'",
82
+ "mappedType": "text"
83
+ },
84
+ "action_data": {
85
+ "name": "action_data",
86
+ "type": "jsonb",
87
+ "unsigned": false,
88
+ "autoincrement": false,
89
+ "primary": false,
90
+ "nullable": true,
91
+ "mappedType": "json"
92
+ },
93
+ "action_result": {
94
+ "name": "action_result",
95
+ "type": "jsonb",
96
+ "unsigned": false,
97
+ "autoincrement": false,
98
+ "primary": false,
99
+ "nullable": true,
100
+ "mappedType": "json"
101
+ },
102
+ "action_taken": {
103
+ "name": "action_taken",
104
+ "type": "text",
105
+ "unsigned": false,
106
+ "autoincrement": false,
107
+ "primary": false,
108
+ "nullable": true,
109
+ "mappedType": "text"
110
+ },
111
+ "source_module": {
112
+ "name": "source_module",
113
+ "type": "text",
114
+ "unsigned": false,
115
+ "autoincrement": false,
116
+ "primary": false,
117
+ "nullable": true,
118
+ "mappedType": "text"
119
+ },
120
+ "source_entity_type": {
121
+ "name": "source_entity_type",
122
+ "type": "text",
123
+ "unsigned": false,
124
+ "autoincrement": false,
125
+ "primary": false,
126
+ "nullable": true,
127
+ "mappedType": "text"
128
+ },
129
+ "source_entity_id": {
130
+ "name": "source_entity_id",
131
+ "type": "uuid",
132
+ "unsigned": false,
133
+ "autoincrement": false,
134
+ "primary": false,
135
+ "nullable": true,
136
+ "mappedType": "uuid"
137
+ },
138
+ "link_href": {
139
+ "name": "link_href",
140
+ "type": "text",
141
+ "unsigned": false,
142
+ "autoincrement": false,
143
+ "primary": false,
144
+ "nullable": true,
145
+ "mappedType": "text"
146
+ },
147
+ "group_key": {
148
+ "name": "group_key",
149
+ "type": "text",
150
+ "unsigned": false,
151
+ "autoincrement": false,
152
+ "primary": false,
153
+ "nullable": true,
154
+ "mappedType": "text"
155
+ },
156
+ "created_at": {
157
+ "name": "created_at",
158
+ "type": "timestamptz",
159
+ "unsigned": false,
160
+ "autoincrement": false,
161
+ "primary": false,
162
+ "nullable": false,
163
+ "length": 6,
164
+ "mappedType": "datetime"
165
+ },
166
+ "read_at": {
167
+ "name": "read_at",
168
+ "type": "timestamptz",
169
+ "unsigned": false,
170
+ "autoincrement": false,
171
+ "primary": false,
172
+ "nullable": true,
173
+ "length": 6,
174
+ "mappedType": "datetime"
175
+ },
176
+ "actioned_at": {
177
+ "name": "actioned_at",
178
+ "type": "timestamptz",
179
+ "unsigned": false,
180
+ "autoincrement": false,
181
+ "primary": false,
182
+ "nullable": true,
183
+ "length": 6,
184
+ "mappedType": "datetime"
185
+ },
186
+ "dismissed_at": {
187
+ "name": "dismissed_at",
188
+ "type": "timestamptz",
189
+ "unsigned": false,
190
+ "autoincrement": false,
191
+ "primary": false,
192
+ "nullable": true,
193
+ "length": 6,
194
+ "mappedType": "datetime"
195
+ },
196
+ "expires_at": {
197
+ "name": "expires_at",
198
+ "type": "timestamptz",
199
+ "unsigned": false,
200
+ "autoincrement": false,
201
+ "primary": false,
202
+ "nullable": true,
203
+ "length": 6,
204
+ "mappedType": "datetime"
205
+ },
206
+ "tenant_id": {
207
+ "name": "tenant_id",
208
+ "type": "uuid",
209
+ "unsigned": false,
210
+ "autoincrement": false,
211
+ "primary": false,
212
+ "nullable": false,
213
+ "mappedType": "uuid"
214
+ },
215
+ "organization_id": {
216
+ "name": "organization_id",
217
+ "type": "uuid",
218
+ "unsigned": false,
219
+ "autoincrement": false,
220
+ "primary": false,
221
+ "nullable": true,
222
+ "mappedType": "uuid"
223
+ }
224
+ },
225
+ "name": "notifications",
226
+ "schema": "public",
227
+ "indexes": [
228
+ {
229
+ "keyName": "notifications_group_idx",
230
+ "columnNames": [
231
+ "group_key",
232
+ "recipient_user_id"
233
+ ],
234
+ "composite": true,
235
+ "constraint": false,
236
+ "primary": false,
237
+ "unique": false
238
+ },
239
+ {
240
+ "keyName": "notifications_expires_idx",
241
+ "columnNames": [
242
+ "expires_at"
243
+ ],
244
+ "composite": false,
245
+ "constraint": false,
246
+ "primary": false,
247
+ "unique": false
248
+ },
249
+ {
250
+ "keyName": "notifications_tenant_idx",
251
+ "columnNames": [
252
+ "tenant_id",
253
+ "organization_id"
254
+ ],
255
+ "composite": true,
256
+ "constraint": false,
257
+ "primary": false,
258
+ "unique": false
259
+ },
260
+ {
261
+ "keyName": "notifications_source_idx",
262
+ "columnNames": [
263
+ "source_entity_type",
264
+ "source_entity_id"
265
+ ],
266
+ "composite": true,
267
+ "constraint": false,
268
+ "primary": false,
269
+ "unique": false
270
+ },
271
+ {
272
+ "keyName": "notifications_recipient_status_idx",
273
+ "columnNames": [
274
+ "recipient_user_id",
275
+ "status",
276
+ "created_at"
277
+ ],
278
+ "composite": true,
279
+ "constraint": false,
280
+ "primary": false,
281
+ "unique": false
282
+ },
283
+ {
284
+ "keyName": "notifications_pkey",
285
+ "columnNames": [
286
+ "id"
287
+ ],
288
+ "composite": false,
289
+ "constraint": true,
290
+ "primary": true,
291
+ "unique": true
292
+ }
293
+ ],
294
+ "checks": [],
295
+ "foreignKeys": {},
296
+ "nativeEnums": {}
297
+ }
298
+ ],
299
+ "nativeEnums": {}
300
+ }
@@ -0,0 +1,73 @@
1
+ import { Migration } from '@mikro-orm/migrations'
2
+
3
+ export class Migration20260123000001 extends Migration {
4
+ async up(): Promise<void> {
5
+ this.addSql(`
6
+ CREATE TABLE notifications (
7
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
8
+
9
+ recipient_user_id UUID NOT NULL,
10
+
11
+ type TEXT NOT NULL,
12
+ title TEXT NOT NULL,
13
+ body TEXT,
14
+ icon TEXT,
15
+ severity TEXT NOT NULL DEFAULT 'info',
16
+
17
+ status TEXT NOT NULL DEFAULT 'unread',
18
+
19
+ action_data JSONB,
20
+ action_result JSONB,
21
+ action_taken TEXT,
22
+
23
+ source_module TEXT,
24
+ source_entity_type TEXT,
25
+ source_entity_id UUID,
26
+ link_href TEXT,
27
+
28
+ group_key TEXT,
29
+
30
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
31
+ read_at TIMESTAMPTZ,
32
+ actioned_at TIMESTAMPTZ,
33
+ dismissed_at TIMESTAMPTZ,
34
+ expires_at TIMESTAMPTZ,
35
+
36
+ tenant_id UUID NOT NULL,
37
+ organization_id UUID
38
+ );
39
+ `)
40
+
41
+ this.addSql(`
42
+ CREATE INDEX notifications_recipient_status_idx
43
+ ON notifications(recipient_user_id, status, created_at DESC);
44
+ `)
45
+
46
+ this.addSql(`
47
+ CREATE INDEX notifications_source_idx
48
+ ON notifications(source_entity_type, source_entity_id)
49
+ WHERE source_entity_id IS NOT NULL;
50
+ `)
51
+
52
+ this.addSql(`
53
+ CREATE INDEX notifications_tenant_idx
54
+ ON notifications(tenant_id, organization_id);
55
+ `)
56
+
57
+ this.addSql(`
58
+ CREATE INDEX notifications_expires_idx
59
+ ON notifications(expires_at)
60
+ WHERE expires_at IS NOT NULL AND status NOT IN ('actioned', 'dismissed');
61
+ `)
62
+
63
+ this.addSql(`
64
+ CREATE INDEX notifications_group_idx
65
+ ON notifications(group_key, recipient_user_id)
66
+ WHERE group_key IS NOT NULL;
67
+ `)
68
+ }
69
+
70
+ async down(): Promise<void> {
71
+ this.addSql('DROP TABLE IF EXISTS notifications CASCADE;')
72
+ }
73
+ }
@@ -0,0 +1,39 @@
1
+ import { Migration } from '@mikro-orm/migrations'
2
+
3
+ export class Migration20260126150000 extends Migration {
4
+ async up(): Promise<void> {
5
+ // Add i18n support fields to notifications table
6
+ this.addSql(`
7
+ alter table "notifications"
8
+ add column if not exists "title_key" text,
9
+ add column if not exists "body_key" text,
10
+ add column if not exists "title_variables" jsonb,
11
+ add column if not exists "body_variables" jsonb;
12
+ `)
13
+
14
+ // Add comments for clarity
15
+ this.addSql(`
16
+ comment on column "notifications"."title_key" is 'i18n key for notification title';
17
+ `)
18
+ this.addSql(`
19
+ comment on column "notifications"."body_key" is 'i18n key for notification body';
20
+ `)
21
+ this.addSql(`
22
+ comment on column "notifications"."title_variables" is 'Variables for i18n interpolation in title';
23
+ `)
24
+ this.addSql(`
25
+ comment on column "notifications"."body_variables" is 'Variables for i18n interpolation in body';
26
+ `)
27
+ }
28
+
29
+ async down(): Promise<void> {
30
+ // Remove i18n support fields
31
+ this.addSql(`
32
+ alter table "notifications"
33
+ drop column if exists "title_key",
34
+ drop column if exists "body_key",
35
+ drop column if exists "title_variables",
36
+ drop column if exists "body_variables";
37
+ `)
38
+ }
39
+ }
@@ -0,0 +1,175 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { Notification } from '../data/entities'
3
+ import { NOTIFICATION_EVENTS } from '../lib/events'
4
+ import { DEFAULT_NOTIFICATION_DELIVERY_CONFIG, resolveNotificationDeliveryConfig, resolveNotificationPanelUrl } from '../lib/deliveryConfig'
5
+ import { sendEmail } from '@open-mercato/shared/lib/email/send'
6
+ import NotificationEmail from '../emails/NotificationEmail'
7
+ import { loadDictionary } from '@open-mercato/shared/lib/i18n/server'
8
+ import { createFallbackTranslator } from '@open-mercato/shared/lib/i18n/translate'
9
+ import { defaultLocale } from '@open-mercato/shared/lib/i18n/config'
10
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
11
+ import { User } from '../../auth/data/entities'
12
+ import type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'
13
+
14
+ export const metadata = {
15
+ event: NOTIFICATION_EVENTS.CREATED,
16
+ persistent: true,
17
+ id: 'notifications:deliver',
18
+ }
19
+
20
+ const DEBUG = process.env.NOTIFICATIONS_DEBUG === 'true'
21
+
22
+ function debug(...args: unknown[]): void {
23
+ if (DEBUG) {
24
+ console.log('[notifications]', ...args)
25
+ }
26
+ }
27
+
28
+ type NotificationCreatedPayload = {
29
+ notificationId: string
30
+ recipientUserId: string
31
+ tenantId: string
32
+ organizationId?: string | null
33
+ }
34
+
35
+ type ResolverContext = {
36
+ resolve: <T = unknown>(name: string) => T
37
+ }
38
+
39
+ const buildPanelLink = (panelUrl: string, notificationId: string) => {
40
+ if (panelUrl.startsWith('http://') || panelUrl.startsWith('https://')) {
41
+ const url = new URL(panelUrl)
42
+ url.searchParams.set('notificationId', notificationId)
43
+ return url.toString()
44
+ }
45
+ const separator = panelUrl.includes('?') ? '&' : '?'
46
+ return `${panelUrl}${separator}notificationId=${encodeURIComponent(notificationId)}`
47
+ }
48
+
49
+ const resolveNotificationCopy = async (
50
+ notification: Notification
51
+ ) => {
52
+ const dict = await loadDictionary(defaultLocale)
53
+ const t = createFallbackTranslator(dict)
54
+
55
+ const title = notification.titleKey
56
+ ? t(notification.titleKey, notification.title ?? notification.titleKey, notification.titleVariables ?? undefined)
57
+ : notification.title
58
+
59
+ const body = notification.bodyKey
60
+ ? t(notification.bodyKey, notification.body ?? notification.bodyKey ?? '', notification.bodyVariables ?? undefined)
61
+ : notification.body ?? null
62
+
63
+ return { title, body, t }
64
+ }
65
+
66
+ const resolveRecipient = async (
67
+ em: EntityManager,
68
+ notification: Notification,
69
+ encryptionService?: TenantDataEncryptionService | null,
70
+ ) => {
71
+ const where: Partial<User> & { deletedAt?: null } = {
72
+ id: notification.recipientUserId,
73
+ tenantId: notification.tenantId,
74
+ deletedAt: null,
75
+ }
76
+ if (notification.organizationId) {
77
+ where.organizationId = notification.organizationId
78
+ }
79
+ const record = await findOneWithDecryption(
80
+ em,
81
+ User,
82
+ where,
83
+ undefined,
84
+ {
85
+ tenantId: notification.tenantId,
86
+ organizationId: notification.organizationId ?? null,
87
+ encryptionService: encryptionService ?? null,
88
+ },
89
+ )
90
+ if (!record) return null
91
+ return {
92
+ email: typeof record.email === 'string' ? record.email : null,
93
+ name: typeof record.name === 'string' ? record.name : null,
94
+ }
95
+ }
96
+
97
+
98
+ export default async function handle(payload: NotificationCreatedPayload, ctx: ResolverContext) {
99
+ debug('deliver notification event', payload)
100
+ const deliveryConfig = await resolveNotificationDeliveryConfig(ctx, { defaultValue: DEFAULT_NOTIFICATION_DELIVERY_CONFIG })
101
+ if (!deliveryConfig.strategies.email.enabled) {
102
+ debug('email delivery disabled')
103
+ return
104
+ }
105
+
106
+ const em = ctx.resolve('em') as EntityManager
107
+ const notification = await em.findOne(Notification, {
108
+ id: payload.notificationId,
109
+ tenantId: payload.tenantId,
110
+ organizationId: payload.organizationId ?? null,
111
+ })
112
+ if (!notification) {
113
+ debug('notification not found', payload.notificationId)
114
+ return
115
+ }
116
+
117
+ let encryptionService: TenantDataEncryptionService | null = null
118
+ try {
119
+ encryptionService = ctx.resolve<TenantDataEncryptionService>('tenantEncryptionService')
120
+ } catch {
121
+ encryptionService = null
122
+ }
123
+
124
+ const recipient = await resolveRecipient(em, notification, encryptionService)
125
+ if (!recipient?.email) {
126
+ debug('recipient has no email', notification.recipientUserId)
127
+ }
128
+ const { title, body, t } = await resolveNotificationCopy(notification)
129
+ const panelUrl = resolveNotificationPanelUrl(deliveryConfig)
130
+ if (!panelUrl) {
131
+ debug('missing panelUrl; check appUrl/panelPath settings')
132
+ return
133
+ }
134
+
135
+ const panelLink = buildPanelLink(panelUrl, notification.id)
136
+ const actionLinks = (notification.actionData?.actions ?? []).map((action) => ({
137
+ id: action.id,
138
+ label: action.labelKey ? t(action.labelKey, action.label) : action.label,
139
+ href: panelLink,
140
+ }))
141
+
142
+ if (deliveryConfig.strategies.email.enabled && recipient?.email) {
143
+ const subjectPrefix = deliveryConfig.strategies.email.subjectPrefix?.trim()
144
+ const subject = subjectPrefix ? `${subjectPrefix} ${title}` : title
145
+ const copy = {
146
+ preview: t('notifications.delivery.email.preview', 'New notification'),
147
+ heading: t('notifications.delivery.email.heading', 'You have a new notification'),
148
+ bodyIntro: t('notifications.delivery.email.bodyIntro', 'Review the notification details and take any required actions.'),
149
+ actionNotice: t('notifications.delivery.email.actionNotice', 'Actions are available in Open Mercato and are read-only in this email.'),
150
+ openCta: t('notifications.delivery.email.openCta', 'Open notification center'),
151
+ footer: t('notifications.delivery.email.footer', 'Open Mercato notifications'),
152
+ }
153
+
154
+ try {
155
+ debug('sending email', { to: recipient.email, from: deliveryConfig.strategies.email.from, subject })
156
+ await sendEmail({
157
+ to: recipient.email,
158
+ subject,
159
+ from: deliveryConfig.strategies.email.from,
160
+ replyTo: deliveryConfig.strategies.email.replyTo,
161
+ react: NotificationEmail({
162
+ title,
163
+ body,
164
+ actions: actionLinks,
165
+ panelUrl: panelLink,
166
+ copy,
167
+ }),
168
+ })
169
+ } catch (error) {
170
+ console.error('[notifications] email delivery failed', error)
171
+ }
172
+ }
173
+
174
+ return
175
+ }