@open-mercato/core 0.4.8-develop-28cee031d6 → 0.4.8-develop-15259be22b

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 (333) hide show
  1. package/agentic/standalone-guide.md +235 -0
  2. package/dist/generated/entities/customer_role/index.js +27 -0
  3. package/dist/generated/entities/customer_role/index.js.map +7 -0
  4. package/dist/generated/entities/customer_role_acl/index.js +19 -0
  5. package/dist/generated/entities/customer_role_acl/index.js.map +7 -0
  6. package/dist/generated/entities/customer_user/index.js +37 -0
  7. package/dist/generated/entities/customer_user/index.js.map +7 -0
  8. package/dist/generated/entities/customer_user_acl/index.js +19 -0
  9. package/dist/generated/entities/customer_user_acl/index.js.map +7 -0
  10. package/dist/generated/entities/customer_user_email_verification/index.js +17 -0
  11. package/dist/generated/entities/customer_user_email_verification/index.js.map +7 -0
  12. package/dist/generated/entities/customer_user_invitation/index.js +33 -0
  13. package/dist/generated/entities/customer_user_invitation/index.js.map +7 -0
  14. package/dist/generated/entities/customer_user_password_reset/index.js +15 -0
  15. package/dist/generated/entities/customer_user_password_reset/index.js.map +7 -0
  16. package/dist/generated/entities/customer_user_role/index.js +13 -0
  17. package/dist/generated/entities/customer_user_role/index.js.map +7 -0
  18. package/dist/generated/entities/customer_user_session/index.js +21 -0
  19. package/dist/generated/entities/customer_user_session/index.js.map +7 -0
  20. package/dist/generated/entities/organization/index.js +2 -0
  21. package/dist/generated/entities/organization/index.js.map +2 -2
  22. package/dist/generated/entities.ids.generated.js +14 -1
  23. package/dist/generated/entities.ids.generated.js.map +2 -2
  24. package/dist/generated/entity-fields-registry.js +18 -0
  25. package/dist/generated/entity-fields-registry.js.map +2 -2
  26. package/dist/modules/auth/services/rbacService.js +3 -9
  27. package/dist/modules/auth/services/rbacService.js.map +2 -2
  28. package/dist/modules/customer_accounts/acl.js +12 -0
  29. package/dist/modules/customer_accounts/acl.js.map +7 -0
  30. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +87 -0
  31. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +7 -0
  32. package/dist/modules/customer_accounts/api/admin/roles/[id].js +216 -0
  33. package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +7 -0
  34. package/dist/modules/customer_accounts/api/admin/roles.js +189 -0
  35. package/dist/modules/customer_accounts/api/admin/roles.js.map +7 -0
  36. package/dist/modules/customer_accounts/api/admin/users/[id]/reset-password.js +69 -0
  37. package/dist/modules/customer_accounts/api/admin/users/[id]/reset-password.js.map +7 -0
  38. package/dist/modules/customer_accounts/api/admin/users/[id]/verify-email.js +64 -0
  39. package/dist/modules/customer_accounts/api/admin/users/[id]/verify-email.js.map +7 -0
  40. package/dist/modules/customer_accounts/api/admin/users/[id].js +253 -0
  41. package/dist/modules/customer_accounts/api/admin/users/[id].js.map +7 -0
  42. package/dist/modules/customer_accounts/api/admin/users-invite.js +78 -0
  43. package/dist/modules/customer_accounts/api/admin/users-invite.js.map +7 -0
  44. package/dist/modules/customer_accounts/api/admin/users.js +251 -0
  45. package/dist/modules/customer_accounts/api/admin/users.js.map +7 -0
  46. package/dist/modules/customer_accounts/api/email/verify.js +59 -0
  47. package/dist/modules/customer_accounts/api/email/verify.js.map +7 -0
  48. package/dist/modules/customer_accounts/api/interceptors.js +5 -0
  49. package/dist/modules/customer_accounts/api/interceptors.js.map +7 -0
  50. package/dist/modules/customer_accounts/api/invitations/accept.js +114 -0
  51. package/dist/modules/customer_accounts/api/invitations/accept.js.map +7 -0
  52. package/dist/modules/customer_accounts/api/login.js +143 -0
  53. package/dist/modules/customer_accounts/api/login.js.map +7 -0
  54. package/dist/modules/customer_accounts/api/magic-link/request.js +78 -0
  55. package/dist/modules/customer_accounts/api/magic-link/request.js.map +7 -0
  56. package/dist/modules/customer_accounts/api/magic-link/verify.js +114 -0
  57. package/dist/modules/customer_accounts/api/magic-link/verify.js.map +7 -0
  58. package/dist/modules/customer_accounts/api/password/reset-confirm.js +59 -0
  59. package/dist/modules/customer_accounts/api/password/reset-confirm.js.map +7 -0
  60. package/dist/modules/customer_accounts/api/password/reset-request.js +77 -0
  61. package/dist/modules/customer_accounts/api/password/reset-request.js.map +7 -0
  62. package/dist/modules/customer_accounts/api/portal/events/stream.js +163 -0
  63. package/dist/modules/customer_accounts/api/portal/events/stream.js.map +7 -0
  64. package/dist/modules/customer_accounts/api/portal/feature-check.js +57 -0
  65. package/dist/modules/customer_accounts/api/portal/feature-check.js.map +7 -0
  66. package/dist/modules/customer_accounts/api/portal/logout.js +64 -0
  67. package/dist/modules/customer_accounts/api/portal/logout.js.map +7 -0
  68. package/dist/modules/customer_accounts/api/portal/notifications/[id]/dismiss.js +49 -0
  69. package/dist/modules/customer_accounts/api/portal/notifications/[id]/dismiss.js.map +7 -0
  70. package/dist/modules/customer_accounts/api/portal/notifications/[id]/read.js +49 -0
  71. package/dist/modules/customer_accounts/api/portal/notifications/[id]/read.js.map +7 -0
  72. package/dist/modules/customer_accounts/api/portal/notifications/mark-all-read.js +46 -0
  73. package/dist/modules/customer_accounts/api/portal/notifications/mark-all-read.js.map +7 -0
  74. package/dist/modules/customer_accounts/api/portal/notifications/unread-count.js +42 -0
  75. package/dist/modules/customer_accounts/api/portal/notifications/unread-count.js.map +7 -0
  76. package/dist/modules/customer_accounts/api/portal/notifications.js +105 -0
  77. package/dist/modules/customer_accounts/api/portal/notifications.js.map +7 -0
  78. package/dist/modules/customer_accounts/api/portal/password-change.js +57 -0
  79. package/dist/modules/customer_accounts/api/portal/password-change.js.map +7 -0
  80. package/dist/modules/customer_accounts/api/portal/profile.js +135 -0
  81. package/dist/modules/customer_accounts/api/portal/profile.js.map +7 -0
  82. package/dist/modules/customer_accounts/api/portal/sessions/[id].js +62 -0
  83. package/dist/modules/customer_accounts/api/portal/sessions/[id].js.map +7 -0
  84. package/dist/modules/customer_accounts/api/portal/sessions-refresh.js +75 -0
  85. package/dist/modules/customer_accounts/api/portal/sessions-refresh.js.map +7 -0
  86. package/dist/modules/customer_accounts/api/portal/sessions.js +77 -0
  87. package/dist/modules/customer_accounts/api/portal/sessions.js.map +7 -0
  88. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +90 -0
  89. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +7 -0
  90. package/dist/modules/customer_accounts/api/portal/users/[id].js +71 -0
  91. package/dist/modules/customer_accounts/api/portal/users/[id].js.map +7 -0
  92. package/dist/modules/customer_accounts/api/portal/users-invite.js +92 -0
  93. package/dist/modules/customer_accounts/api/portal/users-invite.js.map +7 -0
  94. package/dist/modules/customer_accounts/api/portal/users.js +79 -0
  95. package/dist/modules/customer_accounts/api/portal/users.js.map +7 -0
  96. package/dist/modules/customer_accounts/api/signup.js +121 -0
  97. package/dist/modules/customer_accounts/api/signup.js.map +7 -0
  98. package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.js +491 -0
  99. package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.js.map +7 -0
  100. package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.meta.js +15 -0
  101. package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.meta.js.map +7 -0
  102. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +343 -0
  103. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +7 -0
  104. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.meta.js +16 -0
  105. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.meta.js.map +7 -0
  106. package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.js +180 -0
  107. package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.js.map +7 -0
  108. package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.meta.js +16 -0
  109. package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.meta.js.map +7 -0
  110. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +176 -0
  111. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +7 -0
  112. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.meta.js +33 -0
  113. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.meta.js.map +7 -0
  114. package/dist/modules/customer_accounts/backend/page.js +466 -0
  115. package/dist/modules/customer_accounts/backend/page.js.map +7 -0
  116. package/dist/modules/customer_accounts/backend/page.meta.js +35 -0
  117. package/dist/modules/customer_accounts/backend/page.meta.js.map +7 -0
  118. package/dist/modules/customer_accounts/ce.js +26 -0
  119. package/dist/modules/customer_accounts/ce.js.map +7 -0
  120. package/dist/modules/customer_accounts/data/enrichers.js +85 -0
  121. package/dist/modules/customer_accounts/data/enrichers.js.map +7 -0
  122. package/dist/modules/customer_accounts/data/entities.js +377 -0
  123. package/dist/modules/customer_accounts/data/entities.js.map +7 -0
  124. package/dist/modules/customer_accounts/data/extensions.js +8 -0
  125. package/dist/modules/customer_accounts/data/extensions.js.map +7 -0
  126. package/dist/modules/customer_accounts/data/validators.js +111 -0
  127. package/dist/modules/customer_accounts/data/validators.js.map +7 -0
  128. package/dist/modules/customer_accounts/di.js +17 -0
  129. package/dist/modules/customer_accounts/di.js.map +7 -0
  130. package/dist/modules/customer_accounts/events.js +28 -0
  131. package/dist/modules/customer_accounts/events.js.map +7 -0
  132. package/dist/modules/customer_accounts/index.js +15 -0
  133. package/dist/modules/customer_accounts/index.js.map +7 -0
  134. package/dist/modules/customer_accounts/lib/customerAuth.js +71 -0
  135. package/dist/modules/customer_accounts/lib/customerAuth.js.map +7 -0
  136. package/dist/modules/customer_accounts/lib/customerAuthServer.js +29 -0
  137. package/dist/modules/customer_accounts/lib/customerAuthServer.js.map +7 -0
  138. package/dist/modules/customer_accounts/lib/rateLimiter.js +63 -0
  139. package/dist/modules/customer_accounts/lib/rateLimiter.js.map +7 -0
  140. package/dist/modules/customer_accounts/lib/tokenGenerator.js +12 -0
  141. package/dist/modules/customer_accounts/lib/tokenGenerator.js.map +7 -0
  142. package/dist/modules/customer_accounts/migrations/Migration20260313222043.js +49 -0
  143. package/dist/modules/customer_accounts/migrations/Migration20260313222043.js.map +7 -0
  144. package/dist/modules/customer_accounts/notifications.client.js +47 -0
  145. package/dist/modules/customer_accounts/notifications.client.js.map +7 -0
  146. package/dist/modules/customer_accounts/notifications.js +46 -0
  147. package/dist/modules/customer_accounts/notifications.js.map +7 -0
  148. package/dist/modules/customer_accounts/search.js +120 -0
  149. package/dist/modules/customer_accounts/search.js.map +7 -0
  150. package/dist/modules/customer_accounts/services/customerInvitationService.js +87 -0
  151. package/dist/modules/customer_accounts/services/customerInvitationService.js.map +7 -0
  152. package/dist/modules/customer_accounts/services/customerRbacService.js +109 -0
  153. package/dist/modules/customer_accounts/services/customerRbacService.js.map +7 -0
  154. package/dist/modules/customer_accounts/services/customerSessionService.js +75 -0
  155. package/dist/modules/customer_accounts/services/customerSessionService.js.map +7 -0
  156. package/dist/modules/customer_accounts/services/customerTokenService.js +91 -0
  157. package/dist/modules/customer_accounts/services/customerTokenService.js.map +7 -0
  158. package/dist/modules/customer_accounts/services/customerUserService.js +92 -0
  159. package/dist/modules/customer_accounts/services/customerUserService.js.map +7 -0
  160. package/dist/modules/customer_accounts/setup.js +179 -0
  161. package/dist/modules/customer_accounts/setup.js.map +7 -0
  162. package/dist/modules/customer_accounts/subscribers/autoLinkCrm.js +54 -0
  163. package/dist/modules/customer_accounts/subscribers/autoLinkCrm.js.map +7 -0
  164. package/dist/modules/customer_accounts/subscribers/autoLinkCrmReverse.js +68 -0
  165. package/dist/modules/customer_accounts/subscribers/autoLinkCrmReverse.js.map +7 -0
  166. package/dist/modules/customer_accounts/subscribers/notifyStaffOnSignup.js +29 -0
  167. package/dist/modules/customer_accounts/subscribers/notifyStaffOnSignup.js.map +7 -0
  168. package/dist/modules/customer_accounts/translations.js +9 -0
  169. package/dist/modules/customer_accounts/translations.js.map +7 -0
  170. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +63 -0
  171. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +7 -0
  172. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.js +17 -0
  173. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.js.map +7 -0
  174. package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js +55 -0
  175. package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js.map +7 -0
  176. package/dist/modules/customer_accounts/widgets/injection/company-users/widget.js +17 -0
  177. package/dist/modules/customer_accounts/widgets/injection/company-users/widget.js.map +7 -0
  178. package/dist/modules/customer_accounts/widgets/injection-table.js +26 -0
  179. package/dist/modules/customer_accounts/widgets/injection-table.js.map +7 -0
  180. package/dist/modules/customer_accounts/workers/cleanupExpiredSessions.js +23 -0
  181. package/dist/modules/customer_accounts/workers/cleanupExpiredSessions.js.map +7 -0
  182. package/dist/modules/customer_accounts/workers/cleanupExpiredTokens.js +38 -0
  183. package/dist/modules/customer_accounts/workers/cleanupExpiredTokens.js.map +7 -0
  184. package/dist/modules/customers/components/AddressTiles.js +1 -1
  185. package/dist/modules/customers/components/AddressTiles.js.map +2 -2
  186. package/dist/modules/directory/api/get/organizations/lookup.js +83 -0
  187. package/dist/modules/directory/api/get/organizations/lookup.js.map +7 -0
  188. package/dist/modules/directory/commands/organizations.js +32 -1
  189. package/dist/modules/directory/commands/organizations.js.map +2 -2
  190. package/dist/modules/directory/data/entities.js +6 -2
  191. package/dist/modules/directory/data/entities.js.map +2 -2
  192. package/dist/modules/directory/data/validators.js +3 -0
  193. package/dist/modules/directory/data/validators.js.map +2 -2
  194. package/dist/modules/directory/migrations/Migration20260314143323.js +15 -0
  195. package/dist/modules/directory/migrations/Migration20260314143323.js.map +7 -0
  196. package/dist/modules/directory/setup.js +36 -0
  197. package/dist/modules/directory/setup.js.map +2 -2
  198. package/dist/modules/payment_gateways/migrations/Migration20260313222043.js +15 -0
  199. package/dist/modules/payment_gateways/migrations/Migration20260313222043.js.map +7 -0
  200. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +131 -0
  201. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +7 -0
  202. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +96 -0
  203. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +7 -0
  204. package/dist/modules/portal/frontend/[orgSlug]/portal/page.js +94 -0
  205. package/dist/modules/portal/frontend/[orgSlug]/portal/page.js.map +7 -0
  206. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js +89 -0
  207. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js.map +7 -0
  208. package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js +104 -0
  209. package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js.map +7 -0
  210. package/dist/modules/portal/index.js +11 -0
  211. package/dist/modules/portal/index.js.map +7 -0
  212. package/dist/modules/portal/setup.js +23 -0
  213. package/dist/modules/portal/setup.js.map +7 -0
  214. package/generated/entities/customer_role/index.ts +12 -0
  215. package/generated/entities/customer_role_acl/index.ts +8 -0
  216. package/generated/entities/customer_user/index.ts +17 -0
  217. package/generated/entities/customer_user_acl/index.ts +8 -0
  218. package/generated/entities/customer_user_email_verification/index.ts +7 -0
  219. package/generated/entities/customer_user_invitation/index.ts +15 -0
  220. package/generated/entities/customer_user_password_reset/index.ts +6 -0
  221. package/generated/entities/customer_user_role/index.ts +5 -0
  222. package/generated/entities/customer_user_session/index.ts +9 -0
  223. package/generated/entities/organization/index.ts +1 -0
  224. package/generated/entities.ids.generated.ts +14 -1
  225. package/generated/entity-fields-registry.ts +18 -0
  226. package/package.json +3 -3
  227. package/src/modules/auth/services/rbacService.ts +3 -9
  228. package/src/modules/customer_accounts/AGENTS.md +377 -0
  229. package/src/modules/customer_accounts/acl.ts +8 -0
  230. package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +98 -0
  231. package/src/modules/customer_accounts/api/admin/roles/[id].ts +246 -0
  232. package/src/modules/customer_accounts/api/admin/roles.ts +212 -0
  233. package/src/modules/customer_accounts/api/admin/users/[id]/reset-password.ts +78 -0
  234. package/src/modules/customer_accounts/api/admin/users/[id]/verify-email.ts +72 -0
  235. package/src/modules/customer_accounts/api/admin/users/[id].ts +289 -0
  236. package/src/modules/customer_accounts/api/admin/users-invite.ts +86 -0
  237. package/src/modules/customer_accounts/api/admin/users.ts +280 -0
  238. package/src/modules/customer_accounts/api/email/verify.ts +66 -0
  239. package/src/modules/customer_accounts/api/interceptors.ts +3 -0
  240. package/src/modules/customer_accounts/api/invitations/accept.ts +128 -0
  241. package/src/modules/customer_accounts/api/login.ts +163 -0
  242. package/src/modules/customer_accounts/api/magic-link/request.ts +87 -0
  243. package/src/modules/customer_accounts/api/magic-link/verify.ts +132 -0
  244. package/src/modules/customer_accounts/api/password/reset-confirm.ts +69 -0
  245. package/src/modules/customer_accounts/api/password/reset-request.ts +87 -0
  246. package/src/modules/customer_accounts/api/portal/events/stream.ts +209 -0
  247. package/src/modules/customer_accounts/api/portal/feature-check.ts +60 -0
  248. package/src/modules/customer_accounts/api/portal/logout.ts +71 -0
  249. package/src/modules/customer_accounts/api/portal/notifications/[id]/dismiss.ts +54 -0
  250. package/src/modules/customer_accounts/api/portal/notifications/[id]/read.ts +54 -0
  251. package/src/modules/customer_accounts/api/portal/notifications/mark-all-read.ts +49 -0
  252. package/src/modules/customer_accounts/api/portal/notifications/unread-count.ts +45 -0
  253. package/src/modules/customer_accounts/api/portal/notifications.ts +115 -0
  254. package/src/modules/customer_accounts/api/portal/password-change.ts +65 -0
  255. package/src/modules/customer_accounts/api/portal/profile.ts +151 -0
  256. package/src/modules/customer_accounts/api/portal/sessions/[id].ts +70 -0
  257. package/src/modules/customer_accounts/api/portal/sessions-refresh.ts +87 -0
  258. package/src/modules/customer_accounts/api/portal/sessions.ts +84 -0
  259. package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +106 -0
  260. package/src/modules/customer_accounts/api/portal/users/[id].ts +81 -0
  261. package/src/modules/customer_accounts/api/portal/users-invite.ts +103 -0
  262. package/src/modules/customer_accounts/api/portal/users.ts +86 -0
  263. package/src/modules/customer_accounts/api/signup.ts +136 -0
  264. package/src/modules/customer_accounts/backend/customer_accounts/[id]/page.meta.ts +11 -0
  265. package/src/modules/customer_accounts/backend/customer_accounts/[id]/page.tsx +607 -0
  266. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.meta.ts +12 -0
  267. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +385 -0
  268. package/src/modules/customer_accounts/backend/customer_accounts/roles/create/page.meta.ts +12 -0
  269. package/src/modules/customer_accounts/backend/customer_accounts/roles/create/page.tsx +203 -0
  270. package/src/modules/customer_accounts/backend/customer_accounts/roles/page.meta.ts +31 -0
  271. package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +217 -0
  272. package/src/modules/customer_accounts/backend/page.meta.ts +33 -0
  273. package/src/modules/customer_accounts/backend/page.tsx +535 -0
  274. package/src/modules/customer_accounts/ce.ts +22 -0
  275. package/src/modules/customer_accounts/data/enrichers.ts +117 -0
  276. package/src/modules/customer_accounts/data/entities.ts +302 -0
  277. package/src/modules/customer_accounts/data/extensions.ts +4 -0
  278. package/src/modules/customer_accounts/data/validators.ts +128 -0
  279. package/src/modules/customer_accounts/di.ts +15 -0
  280. package/src/modules/customer_accounts/events.ts +28 -0
  281. package/src/modules/customer_accounts/i18n/de.json +176 -0
  282. package/src/modules/customer_accounts/i18n/en.json +176 -0
  283. package/src/modules/customer_accounts/i18n/es.json +176 -0
  284. package/src/modules/customer_accounts/i18n/pl.json +176 -0
  285. package/src/modules/customer_accounts/index.ts +13 -0
  286. package/src/modules/customer_accounts/lib/customerAuth.ts +85 -0
  287. package/src/modules/customer_accounts/lib/customerAuthServer.ts +54 -0
  288. package/src/modules/customer_accounts/lib/rateLimiter.ts +36 -0
  289. package/src/modules/customer_accounts/lib/tokenGenerator.ts +9 -0
  290. package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +1255 -0
  291. package/src/modules/customer_accounts/migrations/Migration20260313222043.ts +62 -0
  292. package/src/modules/customer_accounts/notifications.client.ts +46 -0
  293. package/src/modules/customer_accounts/notifications.ts +44 -0
  294. package/src/modules/customer_accounts/search.ts +134 -0
  295. package/src/modules/customer_accounts/services/customerInvitationService.ts +109 -0
  296. package/src/modules/customer_accounts/services/customerRbacService.ts +144 -0
  297. package/src/modules/customer_accounts/services/customerSessionService.ts +90 -0
  298. package/src/modules/customer_accounts/services/customerTokenService.ts +98 -0
  299. package/src/modules/customer_accounts/services/customerUserService.ts +105 -0
  300. package/src/modules/customer_accounts/setup.ts +212 -0
  301. package/src/modules/customer_accounts/subscribers/autoLinkCrm.ts +65 -0
  302. package/src/modules/customer_accounts/subscribers/autoLinkCrmReverse.ts +78 -0
  303. package/src/modules/customer_accounts/subscribers/notifyStaffOnSignup.ts +32 -0
  304. package/src/modules/customer_accounts/translations.ts +5 -0
  305. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +89 -0
  306. package/src/modules/customer_accounts/widgets/injection/account-status/widget.ts +16 -0
  307. package/src/modules/customer_accounts/widgets/injection/company-users/widget.client.tsx +78 -0
  308. package/src/modules/customer_accounts/widgets/injection/company-users/widget.ts +16 -0
  309. package/src/modules/customer_accounts/widgets/injection-table.ts +24 -0
  310. package/src/modules/customer_accounts/workers/cleanupExpiredSessions.ts +33 -0
  311. package/src/modules/customer_accounts/workers/cleanupExpiredTokens.ts +51 -0
  312. package/src/modules/customers/components/AddressTiles.tsx +1 -1
  313. package/src/modules/directory/api/get/organizations/lookup.ts +92 -0
  314. package/src/modules/directory/commands/organizations.ts +34 -1
  315. package/src/modules/directory/data/entities.ts +5 -1
  316. package/src/modules/directory/data/validators.ts +4 -0
  317. package/src/modules/directory/migrations/.snapshot-open-mercato.json +20 -1
  318. package/src/modules/directory/migrations/Migration20260314143323.ts +15 -0
  319. package/src/modules/directory/setup.ts +41 -0
  320. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +4 -1
  321. package/src/modules/payment_gateways/migrations/Migration20260313222043.ts +17 -0
  322. package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +158 -0
  323. package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +120 -0
  324. package/src/modules/portal/frontend/[orgSlug]/portal/page.tsx +118 -0
  325. package/src/modules/portal/frontend/[orgSlug]/portal/profile/page.tsx +112 -0
  326. package/src/modules/portal/frontend/[orgSlug]/portal/signup/page.tsx +138 -0
  327. package/src/modules/portal/i18n/de.json +93 -0
  328. package/src/modules/portal/i18n/en.json +93 -0
  329. package/src/modules/portal/i18n/es.json +93 -0
  330. package/src/modules/portal/i18n/pl.json +93 -0
  331. package/src/modules/portal/index.ts +9 -0
  332. package/src/modules/portal/setup.ts +23 -0
  333. package/src/modules/shipping_carriers/migrations/.snapshot-open-mercato.json +226 -0
@@ -0,0 +1,71 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
4
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
+ import { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'
6
+
7
+ export const metadata: { path?: string } = {}
8
+
9
+ function readCookieFromHeader(header: string | null | undefined, name: string): string | undefined {
10
+ if (!header) return undefined
11
+ const parts = header.split(';')
12
+ for (const part of parts) {
13
+ const trimmed = part.trim()
14
+ if (trimmed.startsWith(`${name}=`)) {
15
+ return trimmed.slice(name.length + 1)
16
+ }
17
+ }
18
+ return undefined
19
+ }
20
+
21
+ export async function POST(req: Request) {
22
+ const cookieHeader = req.headers.get('cookie') || ''
23
+ const sessionToken = readCookieFromHeader(cookieHeader, 'customer_session_token')
24
+
25
+ if (sessionToken) {
26
+ try {
27
+ const decodedToken = decodeURIComponent(sessionToken)
28
+ const container = await createRequestContainer()
29
+ const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService
30
+ const session = await customerSessionService.findByToken(decodedToken)
31
+ if (session) {
32
+ await customerSessionService.revokeSession(session.id)
33
+ }
34
+ } catch {
35
+ // Best effort — clear cookies regardless
36
+ }
37
+ }
38
+
39
+ const res = NextResponse.json({ ok: true })
40
+
41
+ res.cookies.set('customer_auth_token', '', {
42
+ httpOnly: true,
43
+ path: '/',
44
+ sameSite: 'lax',
45
+ secure: process.env.NODE_ENV === 'production',
46
+ maxAge: 0,
47
+ })
48
+ res.cookies.set('customer_session_token', '', {
49
+ httpOnly: true,
50
+ path: '/',
51
+ sameSite: 'lax',
52
+ secure: process.env.NODE_ENV === 'production',
53
+ maxAge: 0,
54
+ })
55
+
56
+ return res
57
+ }
58
+
59
+ const successSchema = z.object({ ok: z.literal(true) })
60
+
61
+ const methodDoc: OpenApiMethodDoc = {
62
+ summary: 'Customer logout',
63
+ description: 'Revokes the current session and clears authentication cookies.',
64
+ tags: ['Customer Portal'],
65
+ responses: [{ status: 200, description: 'Logged out', schema: successSchema }],
66
+ }
67
+
68
+ export const openApi: OpenApiRouteDoc = {
69
+ summary: 'Customer logout',
70
+ methods: { POST: methodDoc },
71
+ }
@@ -0,0 +1,54 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
4
+ import { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
5
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
+ import { Notification } from '@open-mercato/core/modules/notifications/data/entities'
7
+
8
+ export const metadata: { path?: string } = {}
9
+
10
+ export async function PUT(req: Request, { params }: { params: { id: string } }) {
11
+ const auth = await getCustomerAuthFromRequest(req)
12
+ if (!auth) {
13
+ return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
14
+ }
15
+
16
+ const container = await createRequestContainer()
17
+ const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
18
+
19
+ const notification = await em.findOne(Notification, {
20
+ id: params.id,
21
+ recipientUserId: auth.sub,
22
+ tenantId: auth.tenantId,
23
+ })
24
+
25
+ if (!notification) {
26
+ return NextResponse.json({ ok: false, error: 'Notification not found' }, { status: 404 })
27
+ }
28
+
29
+ notification.status = 'dismissed'
30
+ notification.dismissedAt = new Date()
31
+ await em.flush()
32
+
33
+ return NextResponse.json({ ok: true })
34
+ }
35
+
36
+ const successSchema = z.object({ ok: z.literal(true) })
37
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
38
+
39
+ const methodDoc: OpenApiMethodDoc = {
40
+ summary: 'Dismiss notification',
41
+ description: 'Dismisses a single notification for the authenticated customer user.',
42
+ tags: ['Customer Portal'],
43
+ responses: [{ status: 200, description: 'Notification dismissed', schema: successSchema }],
44
+ errors: [
45
+ { status: 401, description: 'Not authenticated', schema: errorSchema },
46
+ { status: 404, description: 'Notification not found', schema: errorSchema },
47
+ ],
48
+ }
49
+
50
+ export const openApi: OpenApiRouteDoc = {
51
+ summary: 'Dismiss customer notification',
52
+ pathParams: z.object({ id: z.string().uuid() }),
53
+ methods: { PUT: methodDoc },
54
+ }
@@ -0,0 +1,54 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
4
+ import { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
5
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
+ import { Notification } from '@open-mercato/core/modules/notifications/data/entities'
7
+
8
+ export const metadata: { path?: string } = {}
9
+
10
+ export async function PUT(req: Request, { params }: { params: { id: string } }) {
11
+ const auth = await getCustomerAuthFromRequest(req)
12
+ if (!auth) {
13
+ return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
14
+ }
15
+
16
+ const container = await createRequestContainer()
17
+ const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
18
+
19
+ const notification = await em.findOne(Notification, {
20
+ id: params.id,
21
+ recipientUserId: auth.sub,
22
+ tenantId: auth.tenantId,
23
+ })
24
+
25
+ if (!notification) {
26
+ return NextResponse.json({ ok: false, error: 'Notification not found' }, { status: 404 })
27
+ }
28
+
29
+ notification.status = 'read'
30
+ notification.readAt = new Date()
31
+ await em.flush()
32
+
33
+ return NextResponse.json({ ok: true })
34
+ }
35
+
36
+ const successSchema = z.object({ ok: z.literal(true) })
37
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
38
+
39
+ const methodDoc: OpenApiMethodDoc = {
40
+ summary: 'Mark notification as read',
41
+ description: 'Marks a single notification as read for the authenticated customer user.',
42
+ tags: ['Customer Portal'],
43
+ responses: [{ status: 200, description: 'Notification marked as read', schema: successSchema }],
44
+ errors: [
45
+ { status: 401, description: 'Not authenticated', schema: errorSchema },
46
+ { status: 404, description: 'Notification not found', schema: errorSchema },
47
+ ],
48
+ }
49
+
50
+ export const openApi: OpenApiRouteDoc = {
51
+ summary: 'Mark notification as read',
52
+ pathParams: z.object({ id: z.string().uuid() }),
53
+ methods: { PUT: methodDoc },
54
+ }
@@ -0,0 +1,49 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
4
+ import { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
5
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
+ import { Notification } from '@open-mercato/core/modules/notifications/data/entities'
7
+
8
+ export const metadata: { path?: string } = {}
9
+
10
+ export async function PUT(req: Request) {
11
+ const auth = await getCustomerAuthFromRequest(req)
12
+ if (!auth) {
13
+ return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
14
+ }
15
+
16
+ const container = await createRequestContainer()
17
+ const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
18
+
19
+ const now = new Date()
20
+ const count = await em.nativeUpdate(Notification, {
21
+ recipientUserId: auth.sub,
22
+ tenantId: auth.tenantId,
23
+ status: 'unread',
24
+ }, {
25
+ status: 'read',
26
+ readAt: now,
27
+ })
28
+
29
+ return NextResponse.json({ ok: true, count })
30
+ }
31
+
32
+ const successSchema = z.object({
33
+ ok: z.literal(true),
34
+ count: z.number(),
35
+ })
36
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
37
+
38
+ const methodDoc: OpenApiMethodDoc = {
39
+ summary: 'Mark all notifications as read',
40
+ description: 'Marks all unread notifications as read for the authenticated customer user.',
41
+ tags: ['Customer Portal'],
42
+ responses: [{ status: 200, description: 'All notifications marked as read', schema: successSchema }],
43
+ errors: [{ status: 401, description: 'Not authenticated', schema: errorSchema }],
44
+ }
45
+
46
+ export const openApi: OpenApiRouteDoc = {
47
+ summary: 'Mark all customer notifications as read',
48
+ methods: { PUT: methodDoc },
49
+ }
@@ -0,0 +1,45 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
4
+ import { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
5
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
+ import { Notification } from '@open-mercato/core/modules/notifications/data/entities'
7
+
8
+ export const metadata: { path?: string } = {}
9
+
10
+ export async function GET(req: Request) {
11
+ const auth = await getCustomerAuthFromRequest(req)
12
+ if (!auth) {
13
+ return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
14
+ }
15
+
16
+ const container = await createRequestContainer()
17
+ const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
18
+
19
+ const unreadCount = await em.count(Notification, {
20
+ recipientUserId: auth.sub,
21
+ tenantId: auth.tenantId,
22
+ status: 'unread',
23
+ })
24
+
25
+ return NextResponse.json({ ok: true, unreadCount })
26
+ }
27
+
28
+ const successSchema = z.object({
29
+ ok: z.literal(true),
30
+ unreadCount: z.number(),
31
+ })
32
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
33
+
34
+ const methodDoc: OpenApiMethodDoc = {
35
+ summary: 'Get unread notification count',
36
+ description: 'Returns the number of unread notifications for the authenticated customer user.',
37
+ tags: ['Customer Portal'],
38
+ responses: [{ status: 200, description: 'Unread count', schema: successSchema }],
39
+ errors: [{ status: 401, description: 'Not authenticated', schema: errorSchema }],
40
+ }
41
+
42
+ export const openApi: OpenApiRouteDoc = {
43
+ summary: 'Customer unread notification count',
44
+ methods: { GET: methodDoc },
45
+ }
@@ -0,0 +1,115 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
4
+ import { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
5
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
+ import { Notification } from '@open-mercato/core/modules/notifications/data/entities'
7
+ import { toNotificationDto } from '@open-mercato/core/modules/notifications/lib/notificationMapper'
8
+
9
+ export const metadata: { path?: string } = {}
10
+
11
+ export async function GET(req: Request) {
12
+ const auth = await getCustomerAuthFromRequest(req)
13
+ if (!auth) {
14
+ return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
15
+ }
16
+
17
+ const url = new URL(req.url)
18
+ const page = Math.max(1, parseInt(url.searchParams.get('page') || '1', 10) || 1)
19
+ const rawPageSize = parseInt(url.searchParams.get('pageSize') || '50', 10) || 50
20
+ const pageSize = Math.min(Math.max(1, rawPageSize), 100)
21
+ const status = url.searchParams.get('status') || undefined
22
+ const since = url.searchParams.get('since') || undefined
23
+
24
+ const container = await createRequestContainer()
25
+ const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
26
+
27
+ const where: Record<string, unknown> = {
28
+ recipientUserId: auth.sub,
29
+ tenantId: auth.tenantId,
30
+ }
31
+
32
+ if (status) {
33
+ where.status = status
34
+ } else {
35
+ where.status = { $ne: 'dismissed' }
36
+ }
37
+
38
+ if (since) {
39
+ const sinceDate = new Date(since)
40
+ if (!isNaN(sinceDate.getTime())) {
41
+ where.createdAt = { $gte: sinceDate }
42
+ }
43
+ }
44
+
45
+ const offset = (page - 1) * pageSize
46
+
47
+ const [items, total] = await Promise.all([
48
+ em.find(Notification, where, {
49
+ orderBy: { createdAt: 'DESC' },
50
+ limit: pageSize,
51
+ offset,
52
+ }),
53
+ em.count(Notification, where),
54
+ ])
55
+
56
+ return NextResponse.json({
57
+ ok: true,
58
+ items: items.map(toNotificationDto),
59
+ total,
60
+ page,
61
+ pageSize,
62
+ })
63
+ }
64
+
65
+ const notificationDtoSchema = z.object({
66
+ id: z.string().uuid(),
67
+ type: z.string(),
68
+ title: z.string(),
69
+ body: z.string().nullable().optional(),
70
+ titleKey: z.string().nullable().optional(),
71
+ bodyKey: z.string().nullable().optional(),
72
+ titleVariables: z.record(z.string(), z.string()).nullable().optional(),
73
+ bodyVariables: z.record(z.string(), z.string()).nullable().optional(),
74
+ icon: z.string().nullable().optional(),
75
+ severity: z.enum(['info', 'warning', 'success', 'error']),
76
+ status: z.enum(['unread', 'read', 'actioned', 'dismissed']),
77
+ actions: z.array(z.object({
78
+ id: z.string(),
79
+ label: z.string(),
80
+ labelKey: z.string().optional(),
81
+ variant: z.string().optional(),
82
+ icon: z.string().optional(),
83
+ })),
84
+ primaryActionId: z.string().optional(),
85
+ sourceModule: z.string().nullable().optional(),
86
+ sourceEntityType: z.string().nullable().optional(),
87
+ sourceEntityId: z.string().nullable().optional(),
88
+ linkHref: z.string().nullable().optional(),
89
+ createdAt: z.string().datetime(),
90
+ readAt: z.string().datetime().nullable(),
91
+ actionTaken: z.string().nullable().optional(),
92
+ })
93
+
94
+ const listResponseSchema = z.object({
95
+ ok: z.literal(true),
96
+ items: z.array(notificationDtoSchema),
97
+ total: z.number(),
98
+ page: z.number(),
99
+ pageSize: z.number(),
100
+ })
101
+
102
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
103
+
104
+ const methodDoc: OpenApiMethodDoc = {
105
+ summary: 'List customer notifications',
106
+ description: 'Returns paginated notifications for the authenticated customer user. Dismissed notifications are excluded by default unless ?status=dismissed is specified.',
107
+ tags: ['Customer Portal'],
108
+ responses: [{ status: 200, description: 'Notification list', schema: listResponseSchema }],
109
+ errors: [{ status: 401, description: 'Not authenticated', schema: errorSchema }],
110
+ }
111
+
112
+ export const openApi: OpenApiRouteDoc = {
113
+ summary: 'Customer notifications',
114
+ methods: { GET: methodDoc },
115
+ }
@@ -0,0 +1,65 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
4
+ import { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
5
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
+ import { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'
7
+ import { passwordChangeSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'
8
+
9
+ export const metadata: { path?: string } = {}
10
+
11
+ export async function POST(req: Request) {
12
+ const auth = await getCustomerAuthFromRequest(req)
13
+ if (!auth) {
14
+ return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
15
+ }
16
+
17
+ let body: unknown
18
+ try {
19
+ body = await req.json()
20
+ } catch {
21
+ return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })
22
+ }
23
+
24
+ const parsed = passwordChangeSchema.safeParse(body)
25
+ if (!parsed.success) {
26
+ return NextResponse.json({ ok: false, error: 'Validation failed' }, { status: 400 })
27
+ }
28
+
29
+ const container = await createRequestContainer()
30
+ const customerUserService = container.resolve('customerUserService') as CustomerUserService
31
+
32
+ const user = await customerUserService.findById(auth.sub, auth.tenantId)
33
+ if (!user) {
34
+ return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })
35
+ }
36
+
37
+ const currentValid = await customerUserService.verifyPassword(user, parsed.data.currentPassword)
38
+ if (!currentValid) {
39
+ return NextResponse.json({ ok: false, error: 'Current password is incorrect' }, { status: 400 })
40
+ }
41
+
42
+ await customerUserService.updatePassword(user, parsed.data.newPassword)
43
+
44
+ return NextResponse.json({ ok: true })
45
+ }
46
+
47
+ const successSchema = z.object({ ok: z.literal(true) })
48
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
49
+
50
+ const methodDoc: OpenApiMethodDoc = {
51
+ summary: 'Change customer password',
52
+ description: 'Changes the authenticated customer user password after verifying the current password.',
53
+ tags: ['Customer Portal'],
54
+ requestBody: { schema: passwordChangeSchema },
55
+ responses: [{ status: 200, description: 'Password changed', schema: successSchema }],
56
+ errors: [
57
+ { status: 400, description: 'Current password incorrect or validation failed', schema: errorSchema },
58
+ { status: 401, description: 'Not authenticated', schema: errorSchema },
59
+ ],
60
+ }
61
+
62
+ export const openApi: OpenApiRouteDoc = {
63
+ summary: 'Change customer password',
64
+ methods: { POST: methodDoc },
65
+ }
@@ -0,0 +1,151 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
4
+ import { getCustomerAuthFromRequest, requireCustomerFeature } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
5
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
+ import { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'
7
+ import { CustomerRbacService } from '@open-mercato/core/modules/customer_accounts/services/customerRbacService'
8
+ import { CustomerUserRole } from '@open-mercato/core/modules/customer_accounts/data/entities'
9
+ import { profileUpdateSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'
10
+
11
+ export const metadata: { path?: string } = {}
12
+
13
+ export async function GET(req: Request) {
14
+ const auth = await getCustomerAuthFromRequest(req)
15
+ if (!auth) {
16
+ return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
17
+ }
18
+
19
+ const container = await createRequestContainer()
20
+ const customerUserService = container.resolve('customerUserService') as CustomerUserService
21
+ const customerRbacService = container.resolve('customerRbacService') as CustomerRbacService
22
+ const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
23
+
24
+ const user = await customerUserService.findById(auth.sub, auth.tenantId)
25
+ if (!user) {
26
+ return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })
27
+ }
28
+
29
+ const acl = await customerRbacService.loadAcl(user.id, { tenantId: user.tenantId, organizationId: user.organizationId })
30
+
31
+ const userRoles = await em.find(CustomerUserRole, {
32
+ user: user.id as any,
33
+ deletedAt: null,
34
+ }, { populate: ['role'] })
35
+ const roles = userRoles.map((ur) => ({
36
+ id: (ur.role as any).id,
37
+ name: (ur.role as any).name,
38
+ slug: (ur.role as any).slug,
39
+ }))
40
+
41
+ return NextResponse.json({
42
+ ok: true,
43
+ user: {
44
+ id: user.id,
45
+ email: user.email,
46
+ displayName: user.displayName,
47
+ emailVerified: !!user.emailVerifiedAt,
48
+ customerEntityId: user.customerEntityId,
49
+ personEntityId: user.personEntityId,
50
+ isActive: user.isActive,
51
+ lastLoginAt: user.lastLoginAt,
52
+ createdAt: user.createdAt,
53
+ },
54
+ roles,
55
+ resolvedFeatures: acl.features,
56
+ isPortalAdmin: acl.isPortalAdmin,
57
+ })
58
+ }
59
+
60
+ export async function PUT(req: Request) {
61
+ const auth = await getCustomerAuthFromRequest(req)
62
+ if (!auth) {
63
+ return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
64
+ }
65
+
66
+ try {
67
+ requireCustomerFeature(auth, ['portal.account.manage'])
68
+ } catch (response) {
69
+ return response as NextResponse
70
+ }
71
+
72
+ let body: unknown
73
+ try {
74
+ body = await req.json()
75
+ } catch {
76
+ return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })
77
+ }
78
+
79
+ const parsed = profileUpdateSchema.safeParse(body)
80
+ if (!parsed.success) {
81
+ return NextResponse.json({ ok: false, error: 'Validation failed' }, { status: 400 })
82
+ }
83
+
84
+ const container = await createRequestContainer()
85
+ const customerUserService = container.resolve('customerUserService') as CustomerUserService
86
+
87
+ const user = await customerUserService.findById(auth.sub, auth.tenantId)
88
+ if (!user) {
89
+ return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })
90
+ }
91
+
92
+ await customerUserService.updateProfile(user, parsed.data)
93
+
94
+ return NextResponse.json({
95
+ ok: true,
96
+ user: {
97
+ id: user.id,
98
+ email: user.email,
99
+ displayName: user.displayName,
100
+ },
101
+ })
102
+ }
103
+
104
+ const profileSchema = z.object({
105
+ ok: z.literal(true),
106
+ user: z.object({
107
+ id: z.string().uuid(),
108
+ email: z.string(),
109
+ displayName: z.string(),
110
+ emailVerified: z.boolean(),
111
+ customerEntityId: z.string().uuid().nullable(),
112
+ personEntityId: z.string().uuid().nullable(),
113
+ isActive: z.boolean(),
114
+ lastLoginAt: z.string().datetime().nullable(),
115
+ createdAt: z.string().datetime(),
116
+ }),
117
+ roles: z.array(z.object({ id: z.string().uuid(), name: z.string(), slug: z.string() })),
118
+ resolvedFeatures: z.array(z.string()),
119
+ isPortalAdmin: z.boolean(),
120
+ })
121
+
122
+ const putSuccessSchema = z.object({
123
+ ok: z.literal(true),
124
+ user: z.object({ id: z.string().uuid(), email: z.string(), displayName: z.string() }),
125
+ })
126
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
127
+
128
+ const getMethodDoc: OpenApiMethodDoc = {
129
+ summary: 'Get customer profile',
130
+ description: 'Returns the authenticated customer user profile with roles and permissions.',
131
+ tags: ['Customer Portal'],
132
+ responses: [{ status: 200, description: 'Profile data', schema: profileSchema }],
133
+ errors: [{ status: 401, description: 'Not authenticated', schema: errorSchema }],
134
+ }
135
+
136
+ const putMethodDoc: OpenApiMethodDoc = {
137
+ summary: 'Update customer profile',
138
+ description: 'Updates the authenticated customer user profile.',
139
+ tags: ['Customer Portal'],
140
+ requestBody: { schema: profileUpdateSchema },
141
+ responses: [{ status: 200, description: 'Profile updated', schema: putSuccessSchema }],
142
+ errors: [
143
+ { status: 401, description: 'Not authenticated', schema: errorSchema },
144
+ { status: 403, description: 'Insufficient permissions', schema: errorSchema },
145
+ ],
146
+ }
147
+
148
+ export const openApi: OpenApiRouteDoc = {
149
+ summary: 'Customer profile',
150
+ methods: { GET: getMethodDoc, PUT: putMethodDoc },
151
+ }
@@ -0,0 +1,70 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
4
+ import { getCustomerAuthFromRequest, readCookieFromHeader } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
5
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
+ import { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'
7
+ import { CustomerUserSession } from '@open-mercato/core/modules/customer_accounts/data/entities'
8
+ import { hashToken } from '@open-mercato/core/modules/customer_accounts/lib/tokenGenerator'
9
+
10
+ export const metadata: { path?: string } = {}
11
+
12
+ export async function DELETE(req: Request, { params }: { params: { id: string } }) {
13
+ const auth = await getCustomerAuthFromRequest(req)
14
+ if (!auth) {
15
+ return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
16
+ }
17
+
18
+ const sessionId = params.id
19
+ const container = await createRequestContainer()
20
+ const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
21
+ const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService
22
+
23
+ // Verify session belongs to this user
24
+ const session = await em.findOne(CustomerUserSession, {
25
+ id: sessionId,
26
+ user: auth.sub as any,
27
+ deletedAt: null,
28
+ })
29
+ if (!session) {
30
+ return NextResponse.json({ ok: false, error: 'Session not found' }, { status: 404 })
31
+ }
32
+
33
+ // Prevent revoking current session
34
+ const cookieHeader = req.headers.get('cookie') || ''
35
+ const sessionToken = readCookieFromHeader(cookieHeader, 'customer_session_token')
36
+ if (sessionToken) {
37
+ try {
38
+ const currentHash = hashToken(decodeURIComponent(sessionToken))
39
+ if (session.tokenHash === currentHash) {
40
+ return NextResponse.json({ ok: false, error: 'Cannot revoke current session. Use logout instead.' }, { status: 400 })
41
+ }
42
+ } catch {
43
+ // Malformed cookie value — proceed with revocation since we can't confirm it's the current session
44
+ }
45
+ }
46
+
47
+ await customerSessionService.revokeSession(sessionId)
48
+ return NextResponse.json({ ok: true })
49
+ }
50
+
51
+ const successSchema = z.object({ ok: z.literal(true) })
52
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
53
+
54
+ const methodDoc: OpenApiMethodDoc = {
55
+ summary: 'Revoke a customer session',
56
+ description: 'Revokes a specific session (not the current one).',
57
+ tags: ['Customer Portal'],
58
+ responses: [{ status: 200, description: 'Session revoked', schema: successSchema }],
59
+ errors: [
60
+ { status: 400, description: 'Cannot revoke current session', schema: errorSchema },
61
+ { status: 401, description: 'Not authenticated', schema: errorSchema },
62
+ { status: 404, description: 'Session not found', schema: errorSchema },
63
+ ],
64
+ }
65
+
66
+ export const openApi: OpenApiRouteDoc = {
67
+ summary: 'Revoke customer session',
68
+ pathParams: z.object({ id: z.string().uuid() }),
69
+ methods: { DELETE: methodDoc },
70
+ }