@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.
- package/agentic/standalone-guide.md +235 -0
- package/dist/generated/entities/customer_role/index.js +27 -0
- package/dist/generated/entities/customer_role/index.js.map +7 -0
- package/dist/generated/entities/customer_role_acl/index.js +19 -0
- package/dist/generated/entities/customer_role_acl/index.js.map +7 -0
- package/dist/generated/entities/customer_user/index.js +37 -0
- package/dist/generated/entities/customer_user/index.js.map +7 -0
- package/dist/generated/entities/customer_user_acl/index.js +19 -0
- package/dist/generated/entities/customer_user_acl/index.js.map +7 -0
- package/dist/generated/entities/customer_user_email_verification/index.js +17 -0
- package/dist/generated/entities/customer_user_email_verification/index.js.map +7 -0
- package/dist/generated/entities/customer_user_invitation/index.js +33 -0
- package/dist/generated/entities/customer_user_invitation/index.js.map +7 -0
- package/dist/generated/entities/customer_user_password_reset/index.js +15 -0
- package/dist/generated/entities/customer_user_password_reset/index.js.map +7 -0
- package/dist/generated/entities/customer_user_role/index.js +13 -0
- package/dist/generated/entities/customer_user_role/index.js.map +7 -0
- package/dist/generated/entities/customer_user_session/index.js +21 -0
- package/dist/generated/entities/customer_user_session/index.js.map +7 -0
- package/dist/generated/entities/organization/index.js +2 -0
- package/dist/generated/entities/organization/index.js.map +2 -2
- package/dist/generated/entities.ids.generated.js +14 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +18 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/auth/services/rbacService.js +3 -9
- package/dist/modules/auth/services/rbacService.js.map +2 -2
- package/dist/modules/customer_accounts/acl.js +12 -0
- package/dist/modules/customer_accounts/acl.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +87 -0
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/roles/[id].js +216 -0
- package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/roles.js +189 -0
- package/dist/modules/customer_accounts/api/admin/roles.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/users/[id]/reset-password.js +69 -0
- package/dist/modules/customer_accounts/api/admin/users/[id]/reset-password.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/users/[id]/verify-email.js +64 -0
- package/dist/modules/customer_accounts/api/admin/users/[id]/verify-email.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/users/[id].js +253 -0
- package/dist/modules/customer_accounts/api/admin/users/[id].js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/users-invite.js +78 -0
- package/dist/modules/customer_accounts/api/admin/users-invite.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/users.js +251 -0
- package/dist/modules/customer_accounts/api/admin/users.js.map +7 -0
- package/dist/modules/customer_accounts/api/email/verify.js +59 -0
- package/dist/modules/customer_accounts/api/email/verify.js.map +7 -0
- package/dist/modules/customer_accounts/api/interceptors.js +5 -0
- package/dist/modules/customer_accounts/api/interceptors.js.map +7 -0
- package/dist/modules/customer_accounts/api/invitations/accept.js +114 -0
- package/dist/modules/customer_accounts/api/invitations/accept.js.map +7 -0
- package/dist/modules/customer_accounts/api/login.js +143 -0
- package/dist/modules/customer_accounts/api/login.js.map +7 -0
- package/dist/modules/customer_accounts/api/magic-link/request.js +78 -0
- package/dist/modules/customer_accounts/api/magic-link/request.js.map +7 -0
- package/dist/modules/customer_accounts/api/magic-link/verify.js +114 -0
- package/dist/modules/customer_accounts/api/magic-link/verify.js.map +7 -0
- package/dist/modules/customer_accounts/api/password/reset-confirm.js +59 -0
- package/dist/modules/customer_accounts/api/password/reset-confirm.js.map +7 -0
- package/dist/modules/customer_accounts/api/password/reset-request.js +77 -0
- package/dist/modules/customer_accounts/api/password/reset-request.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/events/stream.js +163 -0
- package/dist/modules/customer_accounts/api/portal/events/stream.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/feature-check.js +57 -0
- package/dist/modules/customer_accounts/api/portal/feature-check.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/logout.js +64 -0
- package/dist/modules/customer_accounts/api/portal/logout.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/notifications/[id]/dismiss.js +49 -0
- package/dist/modules/customer_accounts/api/portal/notifications/[id]/dismiss.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/notifications/[id]/read.js +49 -0
- package/dist/modules/customer_accounts/api/portal/notifications/[id]/read.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/notifications/mark-all-read.js +46 -0
- package/dist/modules/customer_accounts/api/portal/notifications/mark-all-read.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/notifications/unread-count.js +42 -0
- package/dist/modules/customer_accounts/api/portal/notifications/unread-count.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/notifications.js +105 -0
- package/dist/modules/customer_accounts/api/portal/notifications.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/password-change.js +57 -0
- package/dist/modules/customer_accounts/api/portal/password-change.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/profile.js +135 -0
- package/dist/modules/customer_accounts/api/portal/profile.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/sessions/[id].js +62 -0
- package/dist/modules/customer_accounts/api/portal/sessions/[id].js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/sessions-refresh.js +75 -0
- package/dist/modules/customer_accounts/api/portal/sessions-refresh.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/sessions.js +77 -0
- package/dist/modules/customer_accounts/api/portal/sessions.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +90 -0
- package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/users/[id].js +71 -0
- package/dist/modules/customer_accounts/api/portal/users/[id].js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/users-invite.js +92 -0
- package/dist/modules/customer_accounts/api/portal/users-invite.js.map +7 -0
- package/dist/modules/customer_accounts/api/portal/users.js +79 -0
- package/dist/modules/customer_accounts/api/portal/users.js.map +7 -0
- package/dist/modules/customer_accounts/api/signup.js +121 -0
- package/dist/modules/customer_accounts/api/signup.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.js +491 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.meta.js +15 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.meta.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +343 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.meta.js +16 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.meta.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.js +180 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.meta.js +16 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.meta.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +176 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.meta.js +33 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.meta.js.map +7 -0
- package/dist/modules/customer_accounts/backend/page.js +466 -0
- package/dist/modules/customer_accounts/backend/page.js.map +7 -0
- package/dist/modules/customer_accounts/backend/page.meta.js +35 -0
- package/dist/modules/customer_accounts/backend/page.meta.js.map +7 -0
- package/dist/modules/customer_accounts/ce.js +26 -0
- package/dist/modules/customer_accounts/ce.js.map +7 -0
- package/dist/modules/customer_accounts/data/enrichers.js +85 -0
- package/dist/modules/customer_accounts/data/enrichers.js.map +7 -0
- package/dist/modules/customer_accounts/data/entities.js +377 -0
- package/dist/modules/customer_accounts/data/entities.js.map +7 -0
- package/dist/modules/customer_accounts/data/extensions.js +8 -0
- package/dist/modules/customer_accounts/data/extensions.js.map +7 -0
- package/dist/modules/customer_accounts/data/validators.js +111 -0
- package/dist/modules/customer_accounts/data/validators.js.map +7 -0
- package/dist/modules/customer_accounts/di.js +17 -0
- package/dist/modules/customer_accounts/di.js.map +7 -0
- package/dist/modules/customer_accounts/events.js +28 -0
- package/dist/modules/customer_accounts/events.js.map +7 -0
- package/dist/modules/customer_accounts/index.js +15 -0
- package/dist/modules/customer_accounts/index.js.map +7 -0
- package/dist/modules/customer_accounts/lib/customerAuth.js +71 -0
- package/dist/modules/customer_accounts/lib/customerAuth.js.map +7 -0
- package/dist/modules/customer_accounts/lib/customerAuthServer.js +29 -0
- package/dist/modules/customer_accounts/lib/customerAuthServer.js.map +7 -0
- package/dist/modules/customer_accounts/lib/rateLimiter.js +63 -0
- package/dist/modules/customer_accounts/lib/rateLimiter.js.map +7 -0
- package/dist/modules/customer_accounts/lib/tokenGenerator.js +12 -0
- package/dist/modules/customer_accounts/lib/tokenGenerator.js.map +7 -0
- package/dist/modules/customer_accounts/migrations/Migration20260313222043.js +49 -0
- package/dist/modules/customer_accounts/migrations/Migration20260313222043.js.map +7 -0
- package/dist/modules/customer_accounts/notifications.client.js +47 -0
- package/dist/modules/customer_accounts/notifications.client.js.map +7 -0
- package/dist/modules/customer_accounts/notifications.js +46 -0
- package/dist/modules/customer_accounts/notifications.js.map +7 -0
- package/dist/modules/customer_accounts/search.js +120 -0
- package/dist/modules/customer_accounts/search.js.map +7 -0
- package/dist/modules/customer_accounts/services/customerInvitationService.js +87 -0
- package/dist/modules/customer_accounts/services/customerInvitationService.js.map +7 -0
- package/dist/modules/customer_accounts/services/customerRbacService.js +109 -0
- package/dist/modules/customer_accounts/services/customerRbacService.js.map +7 -0
- package/dist/modules/customer_accounts/services/customerSessionService.js +75 -0
- package/dist/modules/customer_accounts/services/customerSessionService.js.map +7 -0
- package/dist/modules/customer_accounts/services/customerTokenService.js +91 -0
- package/dist/modules/customer_accounts/services/customerTokenService.js.map +7 -0
- package/dist/modules/customer_accounts/services/customerUserService.js +92 -0
- package/dist/modules/customer_accounts/services/customerUserService.js.map +7 -0
- package/dist/modules/customer_accounts/setup.js +179 -0
- package/dist/modules/customer_accounts/setup.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/autoLinkCrm.js +54 -0
- package/dist/modules/customer_accounts/subscribers/autoLinkCrm.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/autoLinkCrmReverse.js +68 -0
- package/dist/modules/customer_accounts/subscribers/autoLinkCrmReverse.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/notifyStaffOnSignup.js +29 -0
- package/dist/modules/customer_accounts/subscribers/notifyStaffOnSignup.js.map +7 -0
- package/dist/modules/customer_accounts/translations.js +9 -0
- package/dist/modules/customer_accounts/translations.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +63 -0
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.js +17 -0
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js +55 -0
- package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection/company-users/widget.js +17 -0
- package/dist/modules/customer_accounts/widgets/injection/company-users/widget.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection-table.js +26 -0
- package/dist/modules/customer_accounts/widgets/injection-table.js.map +7 -0
- package/dist/modules/customer_accounts/workers/cleanupExpiredSessions.js +23 -0
- package/dist/modules/customer_accounts/workers/cleanupExpiredSessions.js.map +7 -0
- package/dist/modules/customer_accounts/workers/cleanupExpiredTokens.js +38 -0
- package/dist/modules/customer_accounts/workers/cleanupExpiredTokens.js.map +7 -0
- package/dist/modules/directory/api/get/organizations/lookup.js +83 -0
- package/dist/modules/directory/api/get/organizations/lookup.js.map +7 -0
- package/dist/modules/directory/commands/organizations.js +32 -1
- package/dist/modules/directory/commands/organizations.js.map +2 -2
- package/dist/modules/directory/data/entities.js +6 -2
- package/dist/modules/directory/data/entities.js.map +2 -2
- package/dist/modules/directory/data/validators.js +3 -0
- package/dist/modules/directory/data/validators.js.map +2 -2
- package/dist/modules/directory/migrations/Migration20260314143323.js +15 -0
- package/dist/modules/directory/migrations/Migration20260314143323.js.map +7 -0
- package/dist/modules/directory/setup.js +36 -0
- package/dist/modules/directory/setup.js.map +2 -2
- package/dist/modules/payment_gateways/migrations/Migration20260313222043.js +15 -0
- package/dist/modules/payment_gateways/migrations/Migration20260313222043.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +131 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +96 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/page.js +94 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/page.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js +89 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js +104 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js.map +7 -0
- package/dist/modules/portal/index.js +11 -0
- package/dist/modules/portal/index.js.map +7 -0
- package/dist/modules/portal/setup.js +23 -0
- package/dist/modules/portal/setup.js.map +7 -0
- package/generated/entities/customer_role/index.ts +12 -0
- package/generated/entities/customer_role_acl/index.ts +8 -0
- package/generated/entities/customer_user/index.ts +17 -0
- package/generated/entities/customer_user_acl/index.ts +8 -0
- package/generated/entities/customer_user_email_verification/index.ts +7 -0
- package/generated/entities/customer_user_invitation/index.ts +15 -0
- package/generated/entities/customer_user_password_reset/index.ts +6 -0
- package/generated/entities/customer_user_role/index.ts +5 -0
- package/generated/entities/customer_user_session/index.ts +9 -0
- package/generated/entities/organization/index.ts +1 -0
- package/generated/entities.ids.generated.ts +14 -1
- package/generated/entity-fields-registry.ts +18 -0
- package/package.json +3 -3
- package/src/modules/auth/services/rbacService.ts +3 -9
- package/src/modules/customer_accounts/AGENTS.md +377 -0
- package/src/modules/customer_accounts/acl.ts +8 -0
- package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +98 -0
- package/src/modules/customer_accounts/api/admin/roles/[id].ts +246 -0
- package/src/modules/customer_accounts/api/admin/roles.ts +212 -0
- package/src/modules/customer_accounts/api/admin/users/[id]/reset-password.ts +78 -0
- package/src/modules/customer_accounts/api/admin/users/[id]/verify-email.ts +72 -0
- package/src/modules/customer_accounts/api/admin/users/[id].ts +289 -0
- package/src/modules/customer_accounts/api/admin/users-invite.ts +86 -0
- package/src/modules/customer_accounts/api/admin/users.ts +280 -0
- package/src/modules/customer_accounts/api/email/verify.ts +66 -0
- package/src/modules/customer_accounts/api/interceptors.ts +3 -0
- package/src/modules/customer_accounts/api/invitations/accept.ts +128 -0
- package/src/modules/customer_accounts/api/login.ts +163 -0
- package/src/modules/customer_accounts/api/magic-link/request.ts +87 -0
- package/src/modules/customer_accounts/api/magic-link/verify.ts +132 -0
- package/src/modules/customer_accounts/api/password/reset-confirm.ts +69 -0
- package/src/modules/customer_accounts/api/password/reset-request.ts +87 -0
- package/src/modules/customer_accounts/api/portal/events/stream.ts +209 -0
- package/src/modules/customer_accounts/api/portal/feature-check.ts +60 -0
- package/src/modules/customer_accounts/api/portal/logout.ts +71 -0
- package/src/modules/customer_accounts/api/portal/notifications/[id]/dismiss.ts +54 -0
- package/src/modules/customer_accounts/api/portal/notifications/[id]/read.ts +54 -0
- package/src/modules/customer_accounts/api/portal/notifications/mark-all-read.ts +49 -0
- package/src/modules/customer_accounts/api/portal/notifications/unread-count.ts +45 -0
- package/src/modules/customer_accounts/api/portal/notifications.ts +115 -0
- package/src/modules/customer_accounts/api/portal/password-change.ts +65 -0
- package/src/modules/customer_accounts/api/portal/profile.ts +151 -0
- package/src/modules/customer_accounts/api/portal/sessions/[id].ts +70 -0
- package/src/modules/customer_accounts/api/portal/sessions-refresh.ts +87 -0
- package/src/modules/customer_accounts/api/portal/sessions.ts +84 -0
- package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +106 -0
- package/src/modules/customer_accounts/api/portal/users/[id].ts +81 -0
- package/src/modules/customer_accounts/api/portal/users-invite.ts +103 -0
- package/src/modules/customer_accounts/api/portal/users.ts +86 -0
- package/src/modules/customer_accounts/api/signup.ts +136 -0
- package/src/modules/customer_accounts/backend/customer_accounts/[id]/page.meta.ts +11 -0
- package/src/modules/customer_accounts/backend/customer_accounts/[id]/page.tsx +607 -0
- package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.meta.ts +12 -0
- package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +385 -0
- package/src/modules/customer_accounts/backend/customer_accounts/roles/create/page.meta.ts +12 -0
- package/src/modules/customer_accounts/backend/customer_accounts/roles/create/page.tsx +203 -0
- package/src/modules/customer_accounts/backend/customer_accounts/roles/page.meta.ts +31 -0
- package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +217 -0
- package/src/modules/customer_accounts/backend/page.meta.ts +33 -0
- package/src/modules/customer_accounts/backend/page.tsx +535 -0
- package/src/modules/customer_accounts/ce.ts +22 -0
- package/src/modules/customer_accounts/data/enrichers.ts +117 -0
- package/src/modules/customer_accounts/data/entities.ts +302 -0
- package/src/modules/customer_accounts/data/extensions.ts +4 -0
- package/src/modules/customer_accounts/data/validators.ts +128 -0
- package/src/modules/customer_accounts/di.ts +15 -0
- package/src/modules/customer_accounts/events.ts +28 -0
- package/src/modules/customer_accounts/i18n/de.json +176 -0
- package/src/modules/customer_accounts/i18n/en.json +176 -0
- package/src/modules/customer_accounts/i18n/es.json +176 -0
- package/src/modules/customer_accounts/i18n/pl.json +176 -0
- package/src/modules/customer_accounts/index.ts +13 -0
- package/src/modules/customer_accounts/lib/customerAuth.ts +85 -0
- package/src/modules/customer_accounts/lib/customerAuthServer.ts +54 -0
- package/src/modules/customer_accounts/lib/rateLimiter.ts +36 -0
- package/src/modules/customer_accounts/lib/tokenGenerator.ts +9 -0
- package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +1255 -0
- package/src/modules/customer_accounts/migrations/Migration20260313222043.ts +62 -0
- package/src/modules/customer_accounts/notifications.client.ts +46 -0
- package/src/modules/customer_accounts/notifications.ts +44 -0
- package/src/modules/customer_accounts/search.ts +134 -0
- package/src/modules/customer_accounts/services/customerInvitationService.ts +109 -0
- package/src/modules/customer_accounts/services/customerRbacService.ts +144 -0
- package/src/modules/customer_accounts/services/customerSessionService.ts +90 -0
- package/src/modules/customer_accounts/services/customerTokenService.ts +98 -0
- package/src/modules/customer_accounts/services/customerUserService.ts +105 -0
- package/src/modules/customer_accounts/setup.ts +212 -0
- package/src/modules/customer_accounts/subscribers/autoLinkCrm.ts +65 -0
- package/src/modules/customer_accounts/subscribers/autoLinkCrmReverse.ts +78 -0
- package/src/modules/customer_accounts/subscribers/notifyStaffOnSignup.ts +32 -0
- package/src/modules/customer_accounts/translations.ts +5 -0
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +89 -0
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.ts +16 -0
- package/src/modules/customer_accounts/widgets/injection/company-users/widget.client.tsx +78 -0
- package/src/modules/customer_accounts/widgets/injection/company-users/widget.ts +16 -0
- package/src/modules/customer_accounts/widgets/injection-table.ts +24 -0
- package/src/modules/customer_accounts/workers/cleanupExpiredSessions.ts +33 -0
- package/src/modules/customer_accounts/workers/cleanupExpiredTokens.ts +51 -0
- package/src/modules/directory/api/get/organizations/lookup.ts +92 -0
- package/src/modules/directory/commands/organizations.ts +34 -1
- package/src/modules/directory/data/entities.ts +5 -1
- package/src/modules/directory/data/validators.ts +4 -0
- package/src/modules/directory/migrations/.snapshot-open-mercato.json +20 -1
- package/src/modules/directory/migrations/Migration20260314143323.ts +15 -0
- package/src/modules/directory/setup.ts +41 -0
- package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +4 -1
- package/src/modules/payment_gateways/migrations/Migration20260313222043.ts +17 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +158 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +120 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/page.tsx +118 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/profile/page.tsx +112 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/signup/page.tsx +138 -0
- package/src/modules/portal/i18n/de.json +93 -0
- package/src/modules/portal/i18n/en.json +93 -0
- package/src/modules/portal/i18n/es.json +93 -0
- package/src/modules/portal/i18n/pl.json +93 -0
- package/src/modules/portal/index.ts +9 -0
- package/src/modules/portal/setup.ts +23 -0
- 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
|
+
}
|