@open-mercato/core 0.4.8-develop-6b37dabfa2 → 0.4.8-develop-84f3678a58

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 (330) 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/directory/api/get/organizations/lookup.js +83 -0
  185. package/dist/modules/directory/api/get/organizations/lookup.js.map +7 -0
  186. package/dist/modules/directory/commands/organizations.js +32 -1
  187. package/dist/modules/directory/commands/organizations.js.map +2 -2
  188. package/dist/modules/directory/data/entities.js +6 -2
  189. package/dist/modules/directory/data/entities.js.map +2 -2
  190. package/dist/modules/directory/data/validators.js +3 -0
  191. package/dist/modules/directory/data/validators.js.map +2 -2
  192. package/dist/modules/directory/migrations/Migration20260314143323.js +15 -0
  193. package/dist/modules/directory/migrations/Migration20260314143323.js.map +7 -0
  194. package/dist/modules/directory/setup.js +36 -0
  195. package/dist/modules/directory/setup.js.map +2 -2
  196. package/dist/modules/payment_gateways/migrations/Migration20260313222043.js +15 -0
  197. package/dist/modules/payment_gateways/migrations/Migration20260313222043.js.map +7 -0
  198. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +131 -0
  199. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +7 -0
  200. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +96 -0
  201. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +7 -0
  202. package/dist/modules/portal/frontend/[orgSlug]/portal/page.js +94 -0
  203. package/dist/modules/portal/frontend/[orgSlug]/portal/page.js.map +7 -0
  204. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js +89 -0
  205. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js.map +7 -0
  206. package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js +104 -0
  207. package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js.map +7 -0
  208. package/dist/modules/portal/index.js +11 -0
  209. package/dist/modules/portal/index.js.map +7 -0
  210. package/dist/modules/portal/setup.js +23 -0
  211. package/dist/modules/portal/setup.js.map +7 -0
  212. package/generated/entities/customer_role/index.ts +12 -0
  213. package/generated/entities/customer_role_acl/index.ts +8 -0
  214. package/generated/entities/customer_user/index.ts +17 -0
  215. package/generated/entities/customer_user_acl/index.ts +8 -0
  216. package/generated/entities/customer_user_email_verification/index.ts +7 -0
  217. package/generated/entities/customer_user_invitation/index.ts +15 -0
  218. package/generated/entities/customer_user_password_reset/index.ts +6 -0
  219. package/generated/entities/customer_user_role/index.ts +5 -0
  220. package/generated/entities/customer_user_session/index.ts +9 -0
  221. package/generated/entities/organization/index.ts +1 -0
  222. package/generated/entities.ids.generated.ts +14 -1
  223. package/generated/entity-fields-registry.ts +18 -0
  224. package/package.json +3 -3
  225. package/src/modules/auth/services/rbacService.ts +3 -9
  226. package/src/modules/customer_accounts/AGENTS.md +377 -0
  227. package/src/modules/customer_accounts/acl.ts +8 -0
  228. package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +98 -0
  229. package/src/modules/customer_accounts/api/admin/roles/[id].ts +246 -0
  230. package/src/modules/customer_accounts/api/admin/roles.ts +212 -0
  231. package/src/modules/customer_accounts/api/admin/users/[id]/reset-password.ts +78 -0
  232. package/src/modules/customer_accounts/api/admin/users/[id]/verify-email.ts +72 -0
  233. package/src/modules/customer_accounts/api/admin/users/[id].ts +289 -0
  234. package/src/modules/customer_accounts/api/admin/users-invite.ts +86 -0
  235. package/src/modules/customer_accounts/api/admin/users.ts +280 -0
  236. package/src/modules/customer_accounts/api/email/verify.ts +66 -0
  237. package/src/modules/customer_accounts/api/interceptors.ts +3 -0
  238. package/src/modules/customer_accounts/api/invitations/accept.ts +128 -0
  239. package/src/modules/customer_accounts/api/login.ts +163 -0
  240. package/src/modules/customer_accounts/api/magic-link/request.ts +87 -0
  241. package/src/modules/customer_accounts/api/magic-link/verify.ts +132 -0
  242. package/src/modules/customer_accounts/api/password/reset-confirm.ts +69 -0
  243. package/src/modules/customer_accounts/api/password/reset-request.ts +87 -0
  244. package/src/modules/customer_accounts/api/portal/events/stream.ts +209 -0
  245. package/src/modules/customer_accounts/api/portal/feature-check.ts +60 -0
  246. package/src/modules/customer_accounts/api/portal/logout.ts +71 -0
  247. package/src/modules/customer_accounts/api/portal/notifications/[id]/dismiss.ts +54 -0
  248. package/src/modules/customer_accounts/api/portal/notifications/[id]/read.ts +54 -0
  249. package/src/modules/customer_accounts/api/portal/notifications/mark-all-read.ts +49 -0
  250. package/src/modules/customer_accounts/api/portal/notifications/unread-count.ts +45 -0
  251. package/src/modules/customer_accounts/api/portal/notifications.ts +115 -0
  252. package/src/modules/customer_accounts/api/portal/password-change.ts +65 -0
  253. package/src/modules/customer_accounts/api/portal/profile.ts +151 -0
  254. package/src/modules/customer_accounts/api/portal/sessions/[id].ts +70 -0
  255. package/src/modules/customer_accounts/api/portal/sessions-refresh.ts +87 -0
  256. package/src/modules/customer_accounts/api/portal/sessions.ts +84 -0
  257. package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +106 -0
  258. package/src/modules/customer_accounts/api/portal/users/[id].ts +81 -0
  259. package/src/modules/customer_accounts/api/portal/users-invite.ts +103 -0
  260. package/src/modules/customer_accounts/api/portal/users.ts +86 -0
  261. package/src/modules/customer_accounts/api/signup.ts +136 -0
  262. package/src/modules/customer_accounts/backend/customer_accounts/[id]/page.meta.ts +11 -0
  263. package/src/modules/customer_accounts/backend/customer_accounts/[id]/page.tsx +607 -0
  264. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.meta.ts +12 -0
  265. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +385 -0
  266. package/src/modules/customer_accounts/backend/customer_accounts/roles/create/page.meta.ts +12 -0
  267. package/src/modules/customer_accounts/backend/customer_accounts/roles/create/page.tsx +203 -0
  268. package/src/modules/customer_accounts/backend/customer_accounts/roles/page.meta.ts +31 -0
  269. package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +217 -0
  270. package/src/modules/customer_accounts/backend/page.meta.ts +33 -0
  271. package/src/modules/customer_accounts/backend/page.tsx +535 -0
  272. package/src/modules/customer_accounts/ce.ts +22 -0
  273. package/src/modules/customer_accounts/data/enrichers.ts +117 -0
  274. package/src/modules/customer_accounts/data/entities.ts +302 -0
  275. package/src/modules/customer_accounts/data/extensions.ts +4 -0
  276. package/src/modules/customer_accounts/data/validators.ts +128 -0
  277. package/src/modules/customer_accounts/di.ts +15 -0
  278. package/src/modules/customer_accounts/events.ts +28 -0
  279. package/src/modules/customer_accounts/i18n/de.json +176 -0
  280. package/src/modules/customer_accounts/i18n/en.json +176 -0
  281. package/src/modules/customer_accounts/i18n/es.json +176 -0
  282. package/src/modules/customer_accounts/i18n/pl.json +176 -0
  283. package/src/modules/customer_accounts/index.ts +13 -0
  284. package/src/modules/customer_accounts/lib/customerAuth.ts +85 -0
  285. package/src/modules/customer_accounts/lib/customerAuthServer.ts +54 -0
  286. package/src/modules/customer_accounts/lib/rateLimiter.ts +36 -0
  287. package/src/modules/customer_accounts/lib/tokenGenerator.ts +9 -0
  288. package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +1255 -0
  289. package/src/modules/customer_accounts/migrations/Migration20260313222043.ts +62 -0
  290. package/src/modules/customer_accounts/notifications.client.ts +46 -0
  291. package/src/modules/customer_accounts/notifications.ts +44 -0
  292. package/src/modules/customer_accounts/search.ts +134 -0
  293. package/src/modules/customer_accounts/services/customerInvitationService.ts +109 -0
  294. package/src/modules/customer_accounts/services/customerRbacService.ts +144 -0
  295. package/src/modules/customer_accounts/services/customerSessionService.ts +90 -0
  296. package/src/modules/customer_accounts/services/customerTokenService.ts +98 -0
  297. package/src/modules/customer_accounts/services/customerUserService.ts +105 -0
  298. package/src/modules/customer_accounts/setup.ts +212 -0
  299. package/src/modules/customer_accounts/subscribers/autoLinkCrm.ts +65 -0
  300. package/src/modules/customer_accounts/subscribers/autoLinkCrmReverse.ts +78 -0
  301. package/src/modules/customer_accounts/subscribers/notifyStaffOnSignup.ts +32 -0
  302. package/src/modules/customer_accounts/translations.ts +5 -0
  303. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +89 -0
  304. package/src/modules/customer_accounts/widgets/injection/account-status/widget.ts +16 -0
  305. package/src/modules/customer_accounts/widgets/injection/company-users/widget.client.tsx +78 -0
  306. package/src/modules/customer_accounts/widgets/injection/company-users/widget.ts +16 -0
  307. package/src/modules/customer_accounts/widgets/injection-table.ts +24 -0
  308. package/src/modules/customer_accounts/workers/cleanupExpiredSessions.ts +33 -0
  309. package/src/modules/customer_accounts/workers/cleanupExpiredTokens.ts +51 -0
  310. package/src/modules/directory/api/get/organizations/lookup.ts +92 -0
  311. package/src/modules/directory/commands/organizations.ts +34 -1
  312. package/src/modules/directory/data/entities.ts +5 -1
  313. package/src/modules/directory/data/validators.ts +4 -0
  314. package/src/modules/directory/migrations/.snapshot-open-mercato.json +20 -1
  315. package/src/modules/directory/migrations/Migration20260314143323.ts +15 -0
  316. package/src/modules/directory/setup.ts +41 -0
  317. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +4 -1
  318. package/src/modules/payment_gateways/migrations/Migration20260313222043.ts +17 -0
  319. package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +158 -0
  320. package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +120 -0
  321. package/src/modules/portal/frontend/[orgSlug]/portal/page.tsx +118 -0
  322. package/src/modules/portal/frontend/[orgSlug]/portal/profile/page.tsx +112 -0
  323. package/src/modules/portal/frontend/[orgSlug]/portal/signup/page.tsx +138 -0
  324. package/src/modules/portal/i18n/de.json +93 -0
  325. package/src/modules/portal/i18n/en.json +93 -0
  326. package/src/modules/portal/i18n/es.json +93 -0
  327. package/src/modules/portal/i18n/pl.json +93 -0
  328. package/src/modules/portal/index.ts +9 -0
  329. package/src/modules/portal/setup.ts +23 -0
  330. package/src/modules/shipping_carriers/migrations/.snapshot-open-mercato.json +226 -0
@@ -0,0 +1,87 @@
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
+ import { CustomerRbacService } from '@open-mercato/core/modules/customer_accounts/services/customerRbacService'
7
+
8
+ export const metadata: { path?: string } = {}
9
+
10
+ function readCookieFromHeader(header: string | null | undefined, name: string): string | undefined {
11
+ if (!header) return undefined
12
+ const parts = header.split(';')
13
+ for (const part of parts) {
14
+ const trimmed = part.trim()
15
+ if (trimmed.startsWith(`${name}=`)) {
16
+ return trimmed.slice(name.length + 1)
17
+ }
18
+ }
19
+ return undefined
20
+ }
21
+
22
+ export async function POST(req: Request) {
23
+ const cookieHeader = req.headers.get('cookie') || ''
24
+ const sessionToken = readCookieFromHeader(cookieHeader, 'customer_session_token')
25
+ if (!sessionToken) {
26
+ return NextResponse.json({ ok: false, error: 'No session token' }, { status: 401 })
27
+ }
28
+
29
+ let decodedToken: string
30
+ try {
31
+ decodedToken = decodeURIComponent(sessionToken)
32
+ } catch {
33
+ decodedToken = sessionToken
34
+ }
35
+
36
+ const container = await createRequestContainer()
37
+ const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService
38
+ const customerRbacService = container.resolve('customerRbacService') as CustomerRbacService
39
+
40
+ const session = await customerSessionService.findByToken(decodedToken)
41
+ if (!session) {
42
+ return NextResponse.json({ ok: false, error: 'Invalid or expired session' }, { status: 401 })
43
+ }
44
+
45
+ const user = session.user as any
46
+ if (!user || user.deletedAt || !user.isActive) {
47
+ return NextResponse.json({ ok: false, error: 'Account not active' }, { status: 401 })
48
+ }
49
+
50
+ const acl = await customerRbacService.loadAcl(user.id, {
51
+ tenantId: user.tenantId,
52
+ organizationId: user.organizationId,
53
+ })
54
+
55
+ const result = await customerSessionService.refreshSession(decodedToken, acl.features)
56
+ if (!result) {
57
+ return NextResponse.json({ ok: false, error: 'Session refresh failed' }, { status: 401 })
58
+ }
59
+
60
+ const res = NextResponse.json({ ok: true, resolvedFeatures: acl.features })
61
+
62
+ res.cookies.set('customer_auth_token', result.jwt, {
63
+ httpOnly: true,
64
+ path: '/',
65
+ sameSite: 'lax',
66
+ secure: process.env.NODE_ENV === 'production',
67
+ maxAge: 60 * 60 * 8,
68
+ })
69
+
70
+ return res
71
+ }
72
+
73
+ const successSchema = z.object({ ok: z.literal(true), resolvedFeatures: z.array(z.string()) })
74
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
75
+
76
+ const methodDoc: OpenApiMethodDoc = {
77
+ summary: 'Refresh customer JWT from session token',
78
+ description: 'Uses the session cookie to issue a fresh JWT access token.',
79
+ tags: ['Customer Portal'],
80
+ responses: [{ status: 200, description: 'Token refreshed', schema: successSchema }],
81
+ errors: [{ status: 401, description: 'Invalid session', schema: errorSchema }],
82
+ }
83
+
84
+ export const openApi: OpenApiRouteDoc = {
85
+ summary: 'Refresh customer session',
86
+ methods: { POST: methodDoc },
87
+ }
@@ -0,0 +1,84 @@
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 { CustomerUserSession } from '@open-mercato/core/modules/customer_accounts/data/entities'
7
+ import { hashToken } from '@open-mercato/core/modules/customer_accounts/lib/tokenGenerator'
8
+
9
+ export const metadata: { path?: string } = {}
10
+
11
+ function readCookieFromHeader(header: string | null | undefined, name: string): string | undefined {
12
+ if (!header) return undefined
13
+ const parts = header.split(';')
14
+ for (const part of parts) {
15
+ const trimmed = part.trim()
16
+ if (trimmed.startsWith(`${name}=`)) {
17
+ return trimmed.slice(name.length + 1)
18
+ }
19
+ }
20
+ return undefined
21
+ }
22
+
23
+ export async function GET(req: Request) {
24
+ const auth = await getCustomerAuthFromRequest(req)
25
+ if (!auth) {
26
+ return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
27
+ }
28
+
29
+ const container = await createRequestContainer()
30
+ const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
31
+
32
+ const sessions = await em.find(CustomerUserSession, {
33
+ user: auth.sub as any,
34
+ deletedAt: null,
35
+ expiresAt: { $gt: new Date() },
36
+ }, { orderBy: { createdAt: 'DESC' } })
37
+
38
+ // Determine current session
39
+ const cookieHeader = req.headers.get('cookie') || ''
40
+ const sessionToken = readCookieFromHeader(cookieHeader, 'customer_session_token')
41
+ let currentSessionHash: string | null = null
42
+ if (sessionToken) {
43
+ try {
44
+ currentSessionHash = hashToken(decodeURIComponent(sessionToken))
45
+ } catch {
46
+ currentSessionHash = hashToken(sessionToken)
47
+ }
48
+ }
49
+
50
+ const items = sessions.map((s) => ({
51
+ id: s.id,
52
+ ipAddress: s.ipAddress,
53
+ userAgent: s.userAgent,
54
+ lastUsedAt: s.lastUsedAt,
55
+ createdAt: s.createdAt,
56
+ expiresAt: s.expiresAt,
57
+ isCurrent: currentSessionHash ? s.tokenHash === currentSessionHash : false,
58
+ }))
59
+
60
+ return NextResponse.json({ ok: true, sessions: items })
61
+ }
62
+
63
+ const sessionSchema = z.object({
64
+ id: z.string().uuid(),
65
+ ipAddress: z.string().nullable(),
66
+ userAgent: z.string().nullable(),
67
+ lastUsedAt: z.string().datetime().nullable(),
68
+ createdAt: z.string().datetime(),
69
+ expiresAt: z.string().datetime(),
70
+ isCurrent: z.boolean(),
71
+ })
72
+
73
+ const methodDoc: OpenApiMethodDoc = {
74
+ summary: 'List customer sessions',
75
+ description: 'Returns active sessions for the authenticated customer user.',
76
+ tags: ['Customer Portal'],
77
+ responses: [{ status: 200, description: 'Session list', schema: z.object({ ok: z.literal(true), sessions: z.array(sessionSchema) }) }],
78
+ errors: [{ status: 401, description: 'Not authenticated', schema: z.object({ ok: z.literal(false), error: z.string() }) }],
79
+ }
80
+
81
+ export const openApi: OpenApiRouteDoc = {
82
+ summary: 'Customer sessions',
83
+ methods: { GET: methodDoc },
84
+ }
@@ -0,0 +1,106 @@
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 { CustomerUser, CustomerUserRole, CustomerRole } from '@open-mercato/core/modules/customer_accounts/data/entities'
7
+ import { CustomerRbacService } from '@open-mercato/core/modules/customer_accounts/services/customerRbacService'
8
+ import { assignRolesSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'
9
+
10
+ export const metadata: { path?: string } = {}
11
+
12
+ export async function PUT(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
+ try {
19
+ requireCustomerFeature(auth, ['portal.users.roles.manage'])
20
+ } catch (response) {
21
+ return response as NextResponse
22
+ }
23
+
24
+ if (!auth.customerEntityId) {
25
+ return NextResponse.json({ ok: false, error: 'No company association' }, { status: 403 })
26
+ }
27
+
28
+ let body: unknown
29
+ try {
30
+ body = await req.json()
31
+ } catch {
32
+ return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })
33
+ }
34
+
35
+ const parsed = assignRolesSchema.safeParse(body)
36
+ if (!parsed.success) {
37
+ return NextResponse.json({ ok: false, error: 'Validation failed' }, { status: 400 })
38
+ }
39
+
40
+ const container = await createRequestContainer()
41
+ const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
42
+ const customerRbacService = container.resolve('customerRbacService') as CustomerRbacService
43
+
44
+ // Verify target user belongs to same company
45
+ const targetUser = await em.findOne(CustomerUser, {
46
+ id: params.id,
47
+ customerEntityId: auth.customerEntityId,
48
+ tenantId: auth.tenantId,
49
+ deletedAt: null,
50
+ })
51
+ if (!targetUser) {
52
+ return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })
53
+ }
54
+
55
+ // Validate all roles are customer_assignable
56
+ for (const roleId of parsed.data.roleIds) {
57
+ const role = await em.findOne(CustomerRole, { id: roleId, tenantId: auth.tenantId, deletedAt: null })
58
+ if (!role || !role.customerAssignable) {
59
+ return NextResponse.json({ ok: false, error: 'Role not found or not assignable' }, { status: 400 })
60
+ }
61
+ }
62
+
63
+ // Remove existing roles
64
+ await em.nativeDelete(CustomerUserRole, { user: targetUser.id as any })
65
+
66
+ // Assign new roles
67
+ for (const roleId of parsed.data.roleIds) {
68
+ const role = await em.findOne(CustomerRole, { id: roleId })
69
+ if (role) {
70
+ const userRole = em.create(CustomerUserRole, {
71
+ user: targetUser,
72
+ role,
73
+ createdAt: new Date(),
74
+ } as any)
75
+ em.persist(userRole)
76
+ }
77
+ }
78
+ await em.flush()
79
+
80
+ await customerRbacService.invalidateUserCache(targetUser.id)
81
+
82
+ return NextResponse.json({ ok: true })
83
+ }
84
+
85
+ const successSchema = z.object({ ok: z.literal(true) })
86
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
87
+
88
+ const methodDoc: OpenApiMethodDoc = {
89
+ summary: 'Update portal user roles',
90
+ description: 'Assigns new roles to a company portal user.',
91
+ tags: ['Customer Portal'],
92
+ requestBody: { schema: assignRolesSchema },
93
+ responses: [{ status: 200, description: 'Roles updated', schema: successSchema }],
94
+ errors: [
95
+ { status: 400, description: 'Validation failed', schema: errorSchema },
96
+ { status: 401, description: 'Not authenticated', schema: errorSchema },
97
+ { status: 403, description: 'Insufficient permissions', schema: errorSchema },
98
+ { status: 404, description: 'User not found', schema: errorSchema },
99
+ ],
100
+ }
101
+
102
+ export const openApi: OpenApiRouteDoc = {
103
+ summary: 'Update portal user roles',
104
+ pathParams: z.object({ id: z.string().uuid() }),
105
+ methods: { PUT: methodDoc },
106
+ }
@@ -0,0 +1,81 @@
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 { CustomerUser } from '@open-mercato/core/modules/customer_accounts/data/entities'
7
+ import { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'
8
+ import { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'
9
+ import { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'
10
+
11
+ export const metadata: { path?: string } = {}
12
+
13
+ export async function DELETE(req: Request, { params }: { params: { id: string } }) {
14
+ const auth = await getCustomerAuthFromRequest(req)
15
+ if (!auth) {
16
+ return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
17
+ }
18
+
19
+ try {
20
+ requireCustomerFeature(auth, ['portal.users.manage'])
21
+ } catch (response) {
22
+ return response as NextResponse
23
+ }
24
+
25
+ if (!auth.customerEntityId) {
26
+ return NextResponse.json({ ok: false, error: 'No company association' }, { status: 403 })
27
+ }
28
+
29
+ if (params.id === auth.sub) {
30
+ return NextResponse.json({ ok: false, error: 'Cannot delete your own account' }, { status: 400 })
31
+ }
32
+
33
+ const container = await createRequestContainer()
34
+ const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
35
+ const customerUserService = container.resolve('customerUserService') as CustomerUserService
36
+ const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService
37
+
38
+ const targetUser = await em.findOne(CustomerUser, {
39
+ id: params.id,
40
+ customerEntityId: auth.customerEntityId,
41
+ tenantId: auth.tenantId,
42
+ deletedAt: null,
43
+ })
44
+ if (!targetUser) {
45
+ return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })
46
+ }
47
+
48
+ await customerUserService.softDelete(targetUser.id)
49
+ await customerSessionService.revokeAllUserSessions(targetUser.id)
50
+
51
+ void emitCustomerAccountsEvent('customer_accounts.user.deleted', {
52
+ id: targetUser.id,
53
+ email: targetUser.email,
54
+ tenantId: auth.tenantId,
55
+ organizationId: auth.orgId,
56
+ }).catch(() => undefined)
57
+
58
+ return NextResponse.json({ ok: true })
59
+ }
60
+
61
+ const successSchema = z.object({ ok: z.literal(true) })
62
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
63
+
64
+ const methodDoc: OpenApiMethodDoc = {
65
+ summary: 'Delete a company portal user',
66
+ description: 'Soft deletes a portal user and revokes all their sessions.',
67
+ tags: ['Customer Portal'],
68
+ responses: [{ status: 200, description: 'User deleted', schema: successSchema }],
69
+ errors: [
70
+ { status: 400, description: 'Cannot delete self', schema: errorSchema },
71
+ { status: 401, description: 'Not authenticated', schema: errorSchema },
72
+ { status: 403, description: 'Insufficient permissions', schema: errorSchema },
73
+ { status: 404, description: 'User not found', schema: errorSchema },
74
+ ],
75
+ }
76
+
77
+ export const openApi: OpenApiRouteDoc = {
78
+ summary: 'Delete portal user',
79
+ pathParams: z.object({ id: z.string().uuid() }),
80
+ methods: { DELETE: methodDoc },
81
+ }
@@ -0,0 +1,103 @@
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 { CustomerInvitationService } from '@open-mercato/core/modules/customer_accounts/services/customerInvitationService'
7
+ import { CustomerRole } from '@open-mercato/core/modules/customer_accounts/data/entities'
8
+ import { inviteUserSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'
9
+
10
+ export const metadata: { path?: string } = {}
11
+
12
+ export async function POST(req: Request) {
13
+ const auth = await getCustomerAuthFromRequest(req)
14
+ if (!auth) {
15
+ return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
16
+ }
17
+
18
+ try {
19
+ requireCustomerFeature(auth, ['portal.users.manage'])
20
+ } catch (response) {
21
+ return response as NextResponse
22
+ }
23
+
24
+ if (!auth.customerEntityId) {
25
+ return NextResponse.json({ ok: false, error: 'No company association' }, { status: 403 })
26
+ }
27
+
28
+ let body: unknown
29
+ try {
30
+ body = await req.json()
31
+ } catch {
32
+ return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })
33
+ }
34
+
35
+ const parsed = inviteUserSchema.safeParse(body)
36
+ if (!parsed.success) {
37
+ return NextResponse.json({ ok: false, error: 'Validation failed', details: parsed.error.flatten().fieldErrors }, { status: 400 })
38
+ }
39
+
40
+ const container = await createRequestContainer()
41
+ const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
42
+
43
+ // Validate all roles are customer_assignable
44
+ for (const roleId of parsed.data.roleIds) {
45
+ const role = await em.findOne(CustomerRole, { id: roleId, tenantId: auth.tenantId, deletedAt: null })
46
+ if (!role) {
47
+ return NextResponse.json({ ok: false, error: `Role ${roleId} not found` }, { status: 400 })
48
+ }
49
+ if (!role.customerAssignable) {
50
+ return NextResponse.json({ ok: false, error: `Role "${role.name}" cannot be assigned by portal users` }, { status: 403 })
51
+ }
52
+ }
53
+
54
+ const customerInvitationService = container.resolve('customerInvitationService') as CustomerInvitationService
55
+
56
+ const { invitation } = await customerInvitationService.createInvitation(
57
+ parsed.data.email,
58
+ { tenantId: auth.tenantId, organizationId: auth.orgId },
59
+ {
60
+ customerEntityId: auth.customerEntityId,
61
+ roleIds: parsed.data.roleIds,
62
+ invitedByCustomerUserId: auth.sub,
63
+ displayName: parsed.data.displayName || null,
64
+ },
65
+ )
66
+
67
+ return NextResponse.json({
68
+ ok: true,
69
+ invitation: {
70
+ id: invitation.id,
71
+ email: invitation.email,
72
+ expiresAt: invitation.expiresAt,
73
+ },
74
+ }, { status: 201 })
75
+ }
76
+
77
+ const successSchema = z.object({
78
+ ok: z.literal(true),
79
+ invitation: z.object({
80
+ id: z.string().uuid(),
81
+ email: z.string(),
82
+ expiresAt: z.string().datetime(),
83
+ }),
84
+ })
85
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
86
+
87
+ const methodDoc: OpenApiMethodDoc = {
88
+ summary: 'Invite a user to the company portal',
89
+ description: 'Creates an invitation for a new user to join the company portal.',
90
+ tags: ['Customer Portal'],
91
+ requestBody: { schema: inviteUserSchema },
92
+ responses: [{ status: 201, description: 'Invitation created', schema: successSchema }],
93
+ errors: [
94
+ { status: 400, description: 'Validation failed', schema: errorSchema },
95
+ { status: 401, description: 'Not authenticated', schema: errorSchema },
96
+ { status: 403, description: 'Insufficient permissions or non-assignable role', schema: errorSchema },
97
+ ],
98
+ }
99
+
100
+ export const openApi: OpenApiRouteDoc = {
101
+ summary: 'Invite portal user',
102
+ methods: { POST: methodDoc },
103
+ }
@@ -0,0 +1,86 @@
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 { CustomerUser, CustomerUserRole } from '@open-mercato/core/modules/customer_accounts/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
+ try {
17
+ requireCustomerFeature(auth, ['portal.users.view'])
18
+ } catch (response) {
19
+ return response as NextResponse
20
+ }
21
+
22
+ if (!auth.customerEntityId) {
23
+ return NextResponse.json({ ok: false, error: 'No company association' }, { status: 403 })
24
+ }
25
+
26
+ const container = await createRequestContainer()
27
+ const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
28
+
29
+ const users = await em.find(CustomerUser, {
30
+ customerEntityId: auth.customerEntityId,
31
+ tenantId: auth.tenantId,
32
+ deletedAt: null,
33
+ }, { orderBy: { createdAt: 'DESC' } })
34
+
35
+ const items = await Promise.all(users.map(async (user) => {
36
+ const userRoles = await em.find(CustomerUserRole, {
37
+ user: user.id as any,
38
+ deletedAt: null,
39
+ }, { populate: ['role'] })
40
+ const roles = userRoles.map((ur) => ({
41
+ id: (ur.role as any).id,
42
+ name: (ur.role as any).name,
43
+ slug: (ur.role as any).slug,
44
+ }))
45
+
46
+ return {
47
+ id: user.id,
48
+ email: user.email,
49
+ displayName: user.displayName,
50
+ emailVerified: !!user.emailVerifiedAt,
51
+ isActive: user.isActive,
52
+ lastLoginAt: user.lastLoginAt,
53
+ createdAt: user.createdAt,
54
+ roles,
55
+ }
56
+ }))
57
+
58
+ return NextResponse.json({ ok: true, users: items })
59
+ }
60
+
61
+ const userSchema = z.object({
62
+ id: z.string().uuid(),
63
+ email: z.string(),
64
+ displayName: z.string(),
65
+ emailVerified: z.boolean(),
66
+ isActive: z.boolean(),
67
+ lastLoginAt: z.string().datetime().nullable(),
68
+ createdAt: z.string().datetime(),
69
+ roles: z.array(z.object({ id: z.string().uuid(), name: z.string(), slug: z.string() })),
70
+ })
71
+
72
+ const methodDoc: OpenApiMethodDoc = {
73
+ summary: 'List company portal users',
74
+ description: 'Lists all portal users associated with the same company.',
75
+ tags: ['Customer Portal'],
76
+ responses: [{ status: 200, description: 'User list', schema: z.object({ ok: z.literal(true), users: z.array(userSchema) }) }],
77
+ errors: [
78
+ { status: 401, description: 'Not authenticated', schema: z.object({ ok: z.literal(false), error: z.string() }) },
79
+ { status: 403, description: 'Insufficient permissions', schema: z.object({ ok: z.literal(false), error: z.string() }) },
80
+ ],
81
+ }
82
+
83
+ export const openApi: OpenApiRouteDoc = {
84
+ summary: 'List company portal users',
85
+ methods: { GET: methodDoc },
86
+ }
@@ -0,0 +1,136 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
4
+ import { signupSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'
5
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
+ import { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'
7
+ import { CustomerTokenService } from '@open-mercato/core/modules/customer_accounts/services/customerTokenService'
8
+ import { CustomerRole, CustomerUserRole } from '@open-mercato/core/modules/customer_accounts/data/entities'
9
+ import { Organization } from '@open-mercato/core/modules/directory/data/entities'
10
+ import { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'
11
+ import { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'
12
+ import {
13
+ checkAuthRateLimit,
14
+ customerSignupRateLimitConfig,
15
+ customerSignupIpRateLimitConfig,
16
+ } from '@open-mercato/core/modules/customer_accounts/lib/rateLimiter'
17
+
18
+ export const metadata: { path?: string } = {}
19
+
20
+ export async function POST(req: Request) {
21
+ const { error: rateLimitError } = await checkAuthRateLimit({
22
+ req,
23
+ ipConfig: customerSignupIpRateLimitConfig,
24
+ compoundConfig: customerSignupRateLimitConfig,
25
+ compoundIdentifier: '',
26
+ })
27
+ if (rateLimitError) return rateLimitError
28
+
29
+ let body: unknown
30
+ try {
31
+ body = await req.json()
32
+ } catch {
33
+ return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })
34
+ }
35
+
36
+ const parsed = signupSchema.safeParse(body)
37
+ if (!parsed.success) {
38
+ return NextResponse.json({ ok: false, error: 'Validation failed', details: parsed.error.flatten().fieldErrors }, { status: 400 })
39
+ }
40
+
41
+ const { email, password, displayName, tenantId, organizationId } = parsed.data
42
+ if (!tenantId || !organizationId) {
43
+ return NextResponse.json({ ok: false, error: 'tenantId and organizationId are required' }, { status: 400 })
44
+ }
45
+
46
+ const container = await createRequestContainer()
47
+ const customerUserService = container.resolve('customerUserService') as CustomerUserService
48
+ const customerTokenService = container.resolve('customerTokenService') as CustomerTokenService
49
+ const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
50
+
51
+ const org = await em.findOne(Organization, { id: organizationId, deletedAt: null })
52
+ if (!org) {
53
+ return NextResponse.json({ ok: false, error: 'Registration could not be completed' }, { status: 400 })
54
+ }
55
+
56
+ const existing = await customerUserService.findByEmail(email, tenantId)
57
+ if (existing) {
58
+ return NextResponse.json({ ok: false, error: 'Registration could not be completed' }, { status: 400 })
59
+ }
60
+
61
+ const user = await customerUserService.createUser(email, password, displayName, { tenantId, organizationId })
62
+
63
+ const defaultRole = await em.findOne(CustomerRole, {
64
+ tenantId,
65
+ isDefault: true,
66
+ deletedAt: null,
67
+ })
68
+ if (defaultRole) {
69
+ const userRole = em.create(CustomerUserRole, {
70
+ user,
71
+ role: defaultRole,
72
+ createdAt: new Date(),
73
+ } as any)
74
+ em.persist(userRole)
75
+ }
76
+
77
+ await em.persistAndFlush(user)
78
+
79
+ await customerTokenService.createEmailVerification(user.id, tenantId)
80
+
81
+ void emitCustomerAccountsEvent('customer_accounts.user.created', {
82
+ id: user.id,
83
+ email: user.email,
84
+ tenantId,
85
+ organizationId,
86
+ }).catch(() => undefined)
87
+
88
+ return NextResponse.json({
89
+ ok: true,
90
+ user: {
91
+ id: user.id,
92
+ email: user.email,
93
+ displayName: user.displayName,
94
+ emailVerified: false,
95
+ },
96
+ }, { status: 201 })
97
+ }
98
+
99
+ const signupSuccessSchema = z.object({
100
+ ok: z.literal(true),
101
+ user: z.object({
102
+ id: z.string().uuid(),
103
+ email: z.string().email(),
104
+ displayName: z.string(),
105
+ emailVerified: z.boolean(),
106
+ }),
107
+ })
108
+
109
+ const errorSchema = z.object({
110
+ ok: z.literal(false),
111
+ error: z.string(),
112
+ })
113
+
114
+ const methodDoc: OpenApiMethodDoc = {
115
+ summary: 'Register a new customer account',
116
+ description: 'Creates a new customer user account and sends an email verification token.',
117
+ tags: ['Customer Authentication'],
118
+ requestBody: {
119
+ schema: signupSchema,
120
+ description: 'Signup payload with email, password, and display name.',
121
+ },
122
+ responses: [
123
+ { status: 201, description: 'Account created successfully', schema: signupSuccessSchema },
124
+ ],
125
+ errors: [
126
+ { status: 400, description: 'Validation failed', schema: errorSchema },
127
+ { status: 409, description: 'Email already registered', schema: errorSchema },
128
+ { status: 429, description: 'Too many signup attempts', schema: rateLimitErrorSchema },
129
+ ],
130
+ }
131
+
132
+ export const openApi: OpenApiRouteDoc = {
133
+ summary: 'Customer account registration',
134
+ description: 'Handles customer self-registration.',
135
+ methods: { POST: methodDoc },
136
+ }