@open-mercato/core 0.4.8-develop-28cee031d6 → 0.4.8-develop-15259be22b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/customers/components/AddressTiles.js +1 -1
- package/dist/modules/customers/components/AddressTiles.js.map +2 -2
- 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/customers/components/AddressTiles.tsx +1 -1
- 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,71 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
5
|
+
import { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'
|
|
6
|
+
|
|
7
|
+
export const metadata: { path?: string } = {}
|
|
8
|
+
|
|
9
|
+
function readCookieFromHeader(header: string | null | undefined, name: string): string | undefined {
|
|
10
|
+
if (!header) return undefined
|
|
11
|
+
const parts = header.split(';')
|
|
12
|
+
for (const part of parts) {
|
|
13
|
+
const trimmed = part.trim()
|
|
14
|
+
if (trimmed.startsWith(`${name}=`)) {
|
|
15
|
+
return trimmed.slice(name.length + 1)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function POST(req: Request) {
|
|
22
|
+
const cookieHeader = req.headers.get('cookie') || ''
|
|
23
|
+
const sessionToken = readCookieFromHeader(cookieHeader, 'customer_session_token')
|
|
24
|
+
|
|
25
|
+
if (sessionToken) {
|
|
26
|
+
try {
|
|
27
|
+
const decodedToken = decodeURIComponent(sessionToken)
|
|
28
|
+
const container = await createRequestContainer()
|
|
29
|
+
const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService
|
|
30
|
+
const session = await customerSessionService.findByToken(decodedToken)
|
|
31
|
+
if (session) {
|
|
32
|
+
await customerSessionService.revokeSession(session.id)
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
// Best effort — clear cookies regardless
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const res = NextResponse.json({ ok: true })
|
|
40
|
+
|
|
41
|
+
res.cookies.set('customer_auth_token', '', {
|
|
42
|
+
httpOnly: true,
|
|
43
|
+
path: '/',
|
|
44
|
+
sameSite: 'lax',
|
|
45
|
+
secure: process.env.NODE_ENV === 'production',
|
|
46
|
+
maxAge: 0,
|
|
47
|
+
})
|
|
48
|
+
res.cookies.set('customer_session_token', '', {
|
|
49
|
+
httpOnly: true,
|
|
50
|
+
path: '/',
|
|
51
|
+
sameSite: 'lax',
|
|
52
|
+
secure: process.env.NODE_ENV === 'production',
|
|
53
|
+
maxAge: 0,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
return res
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const successSchema = z.object({ ok: z.literal(true) })
|
|
60
|
+
|
|
61
|
+
const methodDoc: OpenApiMethodDoc = {
|
|
62
|
+
summary: 'Customer logout',
|
|
63
|
+
description: 'Revokes the current session and clears authentication cookies.',
|
|
64
|
+
tags: ['Customer Portal'],
|
|
65
|
+
responses: [{ status: 200, description: 'Logged out', schema: successSchema }],
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const openApi: OpenApiRouteDoc = {
|
|
69
|
+
summary: 'Customer logout',
|
|
70
|
+
methods: { POST: methodDoc },
|
|
71
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import { Notification } from '@open-mercato/core/modules/notifications/data/entities'
|
|
7
|
+
|
|
8
|
+
export const metadata: { path?: string } = {}
|
|
9
|
+
|
|
10
|
+
export async function PUT(req: Request, { params }: { params: { id: string } }) {
|
|
11
|
+
const auth = await getCustomerAuthFromRequest(req)
|
|
12
|
+
if (!auth) {
|
|
13
|
+
return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const container = await createRequestContainer()
|
|
17
|
+
const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
|
|
18
|
+
|
|
19
|
+
const notification = await em.findOne(Notification, {
|
|
20
|
+
id: params.id,
|
|
21
|
+
recipientUserId: auth.sub,
|
|
22
|
+
tenantId: auth.tenantId,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
if (!notification) {
|
|
26
|
+
return NextResponse.json({ ok: false, error: 'Notification not found' }, { status: 404 })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
notification.status = 'dismissed'
|
|
30
|
+
notification.dismissedAt = new Date()
|
|
31
|
+
await em.flush()
|
|
32
|
+
|
|
33
|
+
return NextResponse.json({ ok: true })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const successSchema = z.object({ ok: z.literal(true) })
|
|
37
|
+
const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
|
|
38
|
+
|
|
39
|
+
const methodDoc: OpenApiMethodDoc = {
|
|
40
|
+
summary: 'Dismiss notification',
|
|
41
|
+
description: 'Dismisses a single notification for the authenticated customer user.',
|
|
42
|
+
tags: ['Customer Portal'],
|
|
43
|
+
responses: [{ status: 200, description: 'Notification dismissed', schema: successSchema }],
|
|
44
|
+
errors: [
|
|
45
|
+
{ status: 401, description: 'Not authenticated', schema: errorSchema },
|
|
46
|
+
{ status: 404, description: 'Notification not found', schema: errorSchema },
|
|
47
|
+
],
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const openApi: OpenApiRouteDoc = {
|
|
51
|
+
summary: 'Dismiss customer notification',
|
|
52
|
+
pathParams: z.object({ id: z.string().uuid() }),
|
|
53
|
+
methods: { PUT: methodDoc },
|
|
54
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import { Notification } from '@open-mercato/core/modules/notifications/data/entities'
|
|
7
|
+
|
|
8
|
+
export const metadata: { path?: string } = {}
|
|
9
|
+
|
|
10
|
+
export async function PUT(req: Request, { params }: { params: { id: string } }) {
|
|
11
|
+
const auth = await getCustomerAuthFromRequest(req)
|
|
12
|
+
if (!auth) {
|
|
13
|
+
return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const container = await createRequestContainer()
|
|
17
|
+
const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
|
|
18
|
+
|
|
19
|
+
const notification = await em.findOne(Notification, {
|
|
20
|
+
id: params.id,
|
|
21
|
+
recipientUserId: auth.sub,
|
|
22
|
+
tenantId: auth.tenantId,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
if (!notification) {
|
|
26
|
+
return NextResponse.json({ ok: false, error: 'Notification not found' }, { status: 404 })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
notification.status = 'read'
|
|
30
|
+
notification.readAt = new Date()
|
|
31
|
+
await em.flush()
|
|
32
|
+
|
|
33
|
+
return NextResponse.json({ ok: true })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const successSchema = z.object({ ok: z.literal(true) })
|
|
37
|
+
const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
|
|
38
|
+
|
|
39
|
+
const methodDoc: OpenApiMethodDoc = {
|
|
40
|
+
summary: 'Mark notification as read',
|
|
41
|
+
description: 'Marks a single notification as read for the authenticated customer user.',
|
|
42
|
+
tags: ['Customer Portal'],
|
|
43
|
+
responses: [{ status: 200, description: 'Notification marked as read', schema: successSchema }],
|
|
44
|
+
errors: [
|
|
45
|
+
{ status: 401, description: 'Not authenticated', schema: errorSchema },
|
|
46
|
+
{ status: 404, description: 'Notification not found', schema: errorSchema },
|
|
47
|
+
],
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const openApi: OpenApiRouteDoc = {
|
|
51
|
+
summary: 'Mark notification as read',
|
|
52
|
+
pathParams: z.object({ id: z.string().uuid() }),
|
|
53
|
+
methods: { PUT: methodDoc },
|
|
54
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import { Notification } from '@open-mercato/core/modules/notifications/data/entities'
|
|
7
|
+
|
|
8
|
+
export const metadata: { path?: string } = {}
|
|
9
|
+
|
|
10
|
+
export async function PUT(req: Request) {
|
|
11
|
+
const auth = await getCustomerAuthFromRequest(req)
|
|
12
|
+
if (!auth) {
|
|
13
|
+
return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const container = await createRequestContainer()
|
|
17
|
+
const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
|
|
18
|
+
|
|
19
|
+
const now = new Date()
|
|
20
|
+
const count = await em.nativeUpdate(Notification, {
|
|
21
|
+
recipientUserId: auth.sub,
|
|
22
|
+
tenantId: auth.tenantId,
|
|
23
|
+
status: 'unread',
|
|
24
|
+
}, {
|
|
25
|
+
status: 'read',
|
|
26
|
+
readAt: now,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return NextResponse.json({ ok: true, count })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const successSchema = z.object({
|
|
33
|
+
ok: z.literal(true),
|
|
34
|
+
count: z.number(),
|
|
35
|
+
})
|
|
36
|
+
const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
|
|
37
|
+
|
|
38
|
+
const methodDoc: OpenApiMethodDoc = {
|
|
39
|
+
summary: 'Mark all notifications as read',
|
|
40
|
+
description: 'Marks all unread notifications as read for the authenticated customer user.',
|
|
41
|
+
tags: ['Customer Portal'],
|
|
42
|
+
responses: [{ status: 200, description: 'All notifications marked as read', schema: successSchema }],
|
|
43
|
+
errors: [{ status: 401, description: 'Not authenticated', schema: errorSchema }],
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const openApi: OpenApiRouteDoc = {
|
|
47
|
+
summary: 'Mark all customer notifications as read',
|
|
48
|
+
methods: { PUT: methodDoc },
|
|
49
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import { Notification } from '@open-mercato/core/modules/notifications/data/entities'
|
|
7
|
+
|
|
8
|
+
export const metadata: { path?: string } = {}
|
|
9
|
+
|
|
10
|
+
export async function GET(req: Request) {
|
|
11
|
+
const auth = await getCustomerAuthFromRequest(req)
|
|
12
|
+
if (!auth) {
|
|
13
|
+
return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const container = await createRequestContainer()
|
|
17
|
+
const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
|
|
18
|
+
|
|
19
|
+
const unreadCount = await em.count(Notification, {
|
|
20
|
+
recipientUserId: auth.sub,
|
|
21
|
+
tenantId: auth.tenantId,
|
|
22
|
+
status: 'unread',
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return NextResponse.json({ ok: true, unreadCount })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const successSchema = z.object({
|
|
29
|
+
ok: z.literal(true),
|
|
30
|
+
unreadCount: z.number(),
|
|
31
|
+
})
|
|
32
|
+
const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
|
|
33
|
+
|
|
34
|
+
const methodDoc: OpenApiMethodDoc = {
|
|
35
|
+
summary: 'Get unread notification count',
|
|
36
|
+
description: 'Returns the number of unread notifications for the authenticated customer user.',
|
|
37
|
+
tags: ['Customer Portal'],
|
|
38
|
+
responses: [{ status: 200, description: 'Unread count', schema: successSchema }],
|
|
39
|
+
errors: [{ status: 401, description: 'Not authenticated', schema: errorSchema }],
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const openApi: OpenApiRouteDoc = {
|
|
43
|
+
summary: 'Customer unread notification count',
|
|
44
|
+
methods: { GET: methodDoc },
|
|
45
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import { Notification } from '@open-mercato/core/modules/notifications/data/entities'
|
|
7
|
+
import { toNotificationDto } from '@open-mercato/core/modules/notifications/lib/notificationMapper'
|
|
8
|
+
|
|
9
|
+
export const metadata: { path?: string } = {}
|
|
10
|
+
|
|
11
|
+
export async function GET(req: Request) {
|
|
12
|
+
const auth = await getCustomerAuthFromRequest(req)
|
|
13
|
+
if (!auth) {
|
|
14
|
+
return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const url = new URL(req.url)
|
|
18
|
+
const page = Math.max(1, parseInt(url.searchParams.get('page') || '1', 10) || 1)
|
|
19
|
+
const rawPageSize = parseInt(url.searchParams.get('pageSize') || '50', 10) || 50
|
|
20
|
+
const pageSize = Math.min(Math.max(1, rawPageSize), 100)
|
|
21
|
+
const status = url.searchParams.get('status') || undefined
|
|
22
|
+
const since = url.searchParams.get('since') || undefined
|
|
23
|
+
|
|
24
|
+
const container = await createRequestContainer()
|
|
25
|
+
const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
|
|
26
|
+
|
|
27
|
+
const where: Record<string, unknown> = {
|
|
28
|
+
recipientUserId: auth.sub,
|
|
29
|
+
tenantId: auth.tenantId,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (status) {
|
|
33
|
+
where.status = status
|
|
34
|
+
} else {
|
|
35
|
+
where.status = { $ne: 'dismissed' }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (since) {
|
|
39
|
+
const sinceDate = new Date(since)
|
|
40
|
+
if (!isNaN(sinceDate.getTime())) {
|
|
41
|
+
where.createdAt = { $gte: sinceDate }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const offset = (page - 1) * pageSize
|
|
46
|
+
|
|
47
|
+
const [items, total] = await Promise.all([
|
|
48
|
+
em.find(Notification, where, {
|
|
49
|
+
orderBy: { createdAt: 'DESC' },
|
|
50
|
+
limit: pageSize,
|
|
51
|
+
offset,
|
|
52
|
+
}),
|
|
53
|
+
em.count(Notification, where),
|
|
54
|
+
])
|
|
55
|
+
|
|
56
|
+
return NextResponse.json({
|
|
57
|
+
ok: true,
|
|
58
|
+
items: items.map(toNotificationDto),
|
|
59
|
+
total,
|
|
60
|
+
page,
|
|
61
|
+
pageSize,
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const notificationDtoSchema = z.object({
|
|
66
|
+
id: z.string().uuid(),
|
|
67
|
+
type: z.string(),
|
|
68
|
+
title: z.string(),
|
|
69
|
+
body: z.string().nullable().optional(),
|
|
70
|
+
titleKey: z.string().nullable().optional(),
|
|
71
|
+
bodyKey: z.string().nullable().optional(),
|
|
72
|
+
titleVariables: z.record(z.string(), z.string()).nullable().optional(),
|
|
73
|
+
bodyVariables: z.record(z.string(), z.string()).nullable().optional(),
|
|
74
|
+
icon: z.string().nullable().optional(),
|
|
75
|
+
severity: z.enum(['info', 'warning', 'success', 'error']),
|
|
76
|
+
status: z.enum(['unread', 'read', 'actioned', 'dismissed']),
|
|
77
|
+
actions: z.array(z.object({
|
|
78
|
+
id: z.string(),
|
|
79
|
+
label: z.string(),
|
|
80
|
+
labelKey: z.string().optional(),
|
|
81
|
+
variant: z.string().optional(),
|
|
82
|
+
icon: z.string().optional(),
|
|
83
|
+
})),
|
|
84
|
+
primaryActionId: z.string().optional(),
|
|
85
|
+
sourceModule: z.string().nullable().optional(),
|
|
86
|
+
sourceEntityType: z.string().nullable().optional(),
|
|
87
|
+
sourceEntityId: z.string().nullable().optional(),
|
|
88
|
+
linkHref: z.string().nullable().optional(),
|
|
89
|
+
createdAt: z.string().datetime(),
|
|
90
|
+
readAt: z.string().datetime().nullable(),
|
|
91
|
+
actionTaken: z.string().nullable().optional(),
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const listResponseSchema = z.object({
|
|
95
|
+
ok: z.literal(true),
|
|
96
|
+
items: z.array(notificationDtoSchema),
|
|
97
|
+
total: z.number(),
|
|
98
|
+
page: z.number(),
|
|
99
|
+
pageSize: z.number(),
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
|
|
103
|
+
|
|
104
|
+
const methodDoc: OpenApiMethodDoc = {
|
|
105
|
+
summary: 'List customer notifications',
|
|
106
|
+
description: 'Returns paginated notifications for the authenticated customer user. Dismissed notifications are excluded by default unless ?status=dismissed is specified.',
|
|
107
|
+
tags: ['Customer Portal'],
|
|
108
|
+
responses: [{ status: 200, description: 'Notification list', schema: listResponseSchema }],
|
|
109
|
+
errors: [{ status: 401, description: 'Not authenticated', schema: errorSchema }],
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const openApi: OpenApiRouteDoc = {
|
|
113
|
+
summary: 'Customer notifications',
|
|
114
|
+
methods: { GET: methodDoc },
|
|
115
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'
|
|
7
|
+
import { passwordChangeSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'
|
|
8
|
+
|
|
9
|
+
export const metadata: { path?: string } = {}
|
|
10
|
+
|
|
11
|
+
export async function POST(req: Request) {
|
|
12
|
+
const auth = await getCustomerAuthFromRequest(req)
|
|
13
|
+
if (!auth) {
|
|
14
|
+
return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let body: unknown
|
|
18
|
+
try {
|
|
19
|
+
body = await req.json()
|
|
20
|
+
} catch {
|
|
21
|
+
return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const parsed = passwordChangeSchema.safeParse(body)
|
|
25
|
+
if (!parsed.success) {
|
|
26
|
+
return NextResponse.json({ ok: false, error: 'Validation failed' }, { status: 400 })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const container = await createRequestContainer()
|
|
30
|
+
const customerUserService = container.resolve('customerUserService') as CustomerUserService
|
|
31
|
+
|
|
32
|
+
const user = await customerUserService.findById(auth.sub, auth.tenantId)
|
|
33
|
+
if (!user) {
|
|
34
|
+
return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const currentValid = await customerUserService.verifyPassword(user, parsed.data.currentPassword)
|
|
38
|
+
if (!currentValid) {
|
|
39
|
+
return NextResponse.json({ ok: false, error: 'Current password is incorrect' }, { status: 400 })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await customerUserService.updatePassword(user, parsed.data.newPassword)
|
|
43
|
+
|
|
44
|
+
return NextResponse.json({ ok: true })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const successSchema = z.object({ ok: z.literal(true) })
|
|
48
|
+
const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
|
|
49
|
+
|
|
50
|
+
const methodDoc: OpenApiMethodDoc = {
|
|
51
|
+
summary: 'Change customer password',
|
|
52
|
+
description: 'Changes the authenticated customer user password after verifying the current password.',
|
|
53
|
+
tags: ['Customer Portal'],
|
|
54
|
+
requestBody: { schema: passwordChangeSchema },
|
|
55
|
+
responses: [{ status: 200, description: 'Password changed', schema: successSchema }],
|
|
56
|
+
errors: [
|
|
57
|
+
{ status: 400, description: 'Current password incorrect or validation failed', schema: errorSchema },
|
|
58
|
+
{ status: 401, description: 'Not authenticated', schema: errorSchema },
|
|
59
|
+
],
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const openApi: OpenApiRouteDoc = {
|
|
63
|
+
summary: 'Change customer password',
|
|
64
|
+
methods: { POST: methodDoc },
|
|
65
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { getCustomerAuthFromRequest, requireCustomerFeature } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'
|
|
7
|
+
import { CustomerRbacService } from '@open-mercato/core/modules/customer_accounts/services/customerRbacService'
|
|
8
|
+
import { CustomerUserRole } from '@open-mercato/core/modules/customer_accounts/data/entities'
|
|
9
|
+
import { profileUpdateSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'
|
|
10
|
+
|
|
11
|
+
export const metadata: { path?: string } = {}
|
|
12
|
+
|
|
13
|
+
export async function GET(req: Request) {
|
|
14
|
+
const auth = await getCustomerAuthFromRequest(req)
|
|
15
|
+
if (!auth) {
|
|
16
|
+
return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const container = await createRequestContainer()
|
|
20
|
+
const customerUserService = container.resolve('customerUserService') as CustomerUserService
|
|
21
|
+
const customerRbacService = container.resolve('customerRbacService') as CustomerRbacService
|
|
22
|
+
const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
|
|
23
|
+
|
|
24
|
+
const user = await customerUserService.findById(auth.sub, auth.tenantId)
|
|
25
|
+
if (!user) {
|
|
26
|
+
return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const acl = await customerRbacService.loadAcl(user.id, { tenantId: user.tenantId, organizationId: user.organizationId })
|
|
30
|
+
|
|
31
|
+
const userRoles = await em.find(CustomerUserRole, {
|
|
32
|
+
user: user.id as any,
|
|
33
|
+
deletedAt: null,
|
|
34
|
+
}, { populate: ['role'] })
|
|
35
|
+
const roles = userRoles.map((ur) => ({
|
|
36
|
+
id: (ur.role as any).id,
|
|
37
|
+
name: (ur.role as any).name,
|
|
38
|
+
slug: (ur.role as any).slug,
|
|
39
|
+
}))
|
|
40
|
+
|
|
41
|
+
return NextResponse.json({
|
|
42
|
+
ok: true,
|
|
43
|
+
user: {
|
|
44
|
+
id: user.id,
|
|
45
|
+
email: user.email,
|
|
46
|
+
displayName: user.displayName,
|
|
47
|
+
emailVerified: !!user.emailVerifiedAt,
|
|
48
|
+
customerEntityId: user.customerEntityId,
|
|
49
|
+
personEntityId: user.personEntityId,
|
|
50
|
+
isActive: user.isActive,
|
|
51
|
+
lastLoginAt: user.lastLoginAt,
|
|
52
|
+
createdAt: user.createdAt,
|
|
53
|
+
},
|
|
54
|
+
roles,
|
|
55
|
+
resolvedFeatures: acl.features,
|
|
56
|
+
isPortalAdmin: acl.isPortalAdmin,
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function PUT(req: Request) {
|
|
61
|
+
const auth = await getCustomerAuthFromRequest(req)
|
|
62
|
+
if (!auth) {
|
|
63
|
+
return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
requireCustomerFeature(auth, ['portal.account.manage'])
|
|
68
|
+
} catch (response) {
|
|
69
|
+
return response as NextResponse
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let body: unknown
|
|
73
|
+
try {
|
|
74
|
+
body = await req.json()
|
|
75
|
+
} catch {
|
|
76
|
+
return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const parsed = profileUpdateSchema.safeParse(body)
|
|
80
|
+
if (!parsed.success) {
|
|
81
|
+
return NextResponse.json({ ok: false, error: 'Validation failed' }, { status: 400 })
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const container = await createRequestContainer()
|
|
85
|
+
const customerUserService = container.resolve('customerUserService') as CustomerUserService
|
|
86
|
+
|
|
87
|
+
const user = await customerUserService.findById(auth.sub, auth.tenantId)
|
|
88
|
+
if (!user) {
|
|
89
|
+
return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
await customerUserService.updateProfile(user, parsed.data)
|
|
93
|
+
|
|
94
|
+
return NextResponse.json({
|
|
95
|
+
ok: true,
|
|
96
|
+
user: {
|
|
97
|
+
id: user.id,
|
|
98
|
+
email: user.email,
|
|
99
|
+
displayName: user.displayName,
|
|
100
|
+
},
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const profileSchema = z.object({
|
|
105
|
+
ok: z.literal(true),
|
|
106
|
+
user: z.object({
|
|
107
|
+
id: z.string().uuid(),
|
|
108
|
+
email: z.string(),
|
|
109
|
+
displayName: z.string(),
|
|
110
|
+
emailVerified: z.boolean(),
|
|
111
|
+
customerEntityId: z.string().uuid().nullable(),
|
|
112
|
+
personEntityId: z.string().uuid().nullable(),
|
|
113
|
+
isActive: z.boolean(),
|
|
114
|
+
lastLoginAt: z.string().datetime().nullable(),
|
|
115
|
+
createdAt: z.string().datetime(),
|
|
116
|
+
}),
|
|
117
|
+
roles: z.array(z.object({ id: z.string().uuid(), name: z.string(), slug: z.string() })),
|
|
118
|
+
resolvedFeatures: z.array(z.string()),
|
|
119
|
+
isPortalAdmin: z.boolean(),
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const putSuccessSchema = z.object({
|
|
123
|
+
ok: z.literal(true),
|
|
124
|
+
user: z.object({ id: z.string().uuid(), email: z.string(), displayName: z.string() }),
|
|
125
|
+
})
|
|
126
|
+
const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
|
|
127
|
+
|
|
128
|
+
const getMethodDoc: OpenApiMethodDoc = {
|
|
129
|
+
summary: 'Get customer profile',
|
|
130
|
+
description: 'Returns the authenticated customer user profile with roles and permissions.',
|
|
131
|
+
tags: ['Customer Portal'],
|
|
132
|
+
responses: [{ status: 200, description: 'Profile data', schema: profileSchema }],
|
|
133
|
+
errors: [{ status: 401, description: 'Not authenticated', schema: errorSchema }],
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const putMethodDoc: OpenApiMethodDoc = {
|
|
137
|
+
summary: 'Update customer profile',
|
|
138
|
+
description: 'Updates the authenticated customer user profile.',
|
|
139
|
+
tags: ['Customer Portal'],
|
|
140
|
+
requestBody: { schema: profileUpdateSchema },
|
|
141
|
+
responses: [{ status: 200, description: 'Profile updated', schema: putSuccessSchema }],
|
|
142
|
+
errors: [
|
|
143
|
+
{ status: 401, description: 'Not authenticated', schema: errorSchema },
|
|
144
|
+
{ status: 403, description: 'Insufficient permissions', schema: errorSchema },
|
|
145
|
+
],
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const openApi: OpenApiRouteDoc = {
|
|
149
|
+
summary: 'Customer profile',
|
|
150
|
+
methods: { GET: getMethodDoc, PUT: putMethodDoc },
|
|
151
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { getCustomerAuthFromRequest, readCookieFromHeader } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'
|
|
7
|
+
import { CustomerUserSession } from '@open-mercato/core/modules/customer_accounts/data/entities'
|
|
8
|
+
import { hashToken } from '@open-mercato/core/modules/customer_accounts/lib/tokenGenerator'
|
|
9
|
+
|
|
10
|
+
export const metadata: { path?: string } = {}
|
|
11
|
+
|
|
12
|
+
export async function DELETE(req: Request, { params }: { params: { id: string } }) {
|
|
13
|
+
const auth = await getCustomerAuthFromRequest(req)
|
|
14
|
+
if (!auth) {
|
|
15
|
+
return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const sessionId = params.id
|
|
19
|
+
const container = await createRequestContainer()
|
|
20
|
+
const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
|
|
21
|
+
const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService
|
|
22
|
+
|
|
23
|
+
// Verify session belongs to this user
|
|
24
|
+
const session = await em.findOne(CustomerUserSession, {
|
|
25
|
+
id: sessionId,
|
|
26
|
+
user: auth.sub as any,
|
|
27
|
+
deletedAt: null,
|
|
28
|
+
})
|
|
29
|
+
if (!session) {
|
|
30
|
+
return NextResponse.json({ ok: false, error: 'Session not found' }, { status: 404 })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Prevent revoking current session
|
|
34
|
+
const cookieHeader = req.headers.get('cookie') || ''
|
|
35
|
+
const sessionToken = readCookieFromHeader(cookieHeader, 'customer_session_token')
|
|
36
|
+
if (sessionToken) {
|
|
37
|
+
try {
|
|
38
|
+
const currentHash = hashToken(decodeURIComponent(sessionToken))
|
|
39
|
+
if (session.tokenHash === currentHash) {
|
|
40
|
+
return NextResponse.json({ ok: false, error: 'Cannot revoke current session. Use logout instead.' }, { status: 400 })
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
// Malformed cookie value — proceed with revocation since we can't confirm it's the current session
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await customerSessionService.revokeSession(sessionId)
|
|
48
|
+
return NextResponse.json({ ok: true })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const successSchema = z.object({ ok: z.literal(true) })
|
|
52
|
+
const errorSchema = z.object({ ok: z.literal(false), error: z.string() })
|
|
53
|
+
|
|
54
|
+
const methodDoc: OpenApiMethodDoc = {
|
|
55
|
+
summary: 'Revoke a customer session',
|
|
56
|
+
description: 'Revokes a specific session (not the current one).',
|
|
57
|
+
tags: ['Customer Portal'],
|
|
58
|
+
responses: [{ status: 200, description: 'Session revoked', schema: successSchema }],
|
|
59
|
+
errors: [
|
|
60
|
+
{ status: 400, description: 'Cannot revoke current session', schema: errorSchema },
|
|
61
|
+
{ status: 401, description: 'Not authenticated', schema: errorSchema },
|
|
62
|
+
{ status: 404, description: 'Session not found', schema: errorSchema },
|
|
63
|
+
],
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const openApi: OpenApiRouteDoc = {
|
|
67
|
+
summary: 'Revoke customer session',
|
|
68
|
+
pathParams: z.object({ id: z.string().uuid() }),
|
|
69
|
+
methods: { DELETE: methodDoc },
|
|
70
|
+
}
|