From 80a597bc10557d16f28897f30a77f3a8f6e950fd Mon Sep 17 00:00:00 2001 From: root Date: Wed, 10 Jun 2026 00:40:19 -0400 Subject: [PATCH] fix architecture and write new tests --- .env.docker.dev | 12 +- .env.docker.production.example | 12 +- .env.docker.production.generated | 8 +- .env.docker.test | 4 +- .env.example | 22 +- Dockerfile.production | 19 +- apps/admin/next-env.d.ts | 3 +- apps/admin/next.config.js | 31 + apps/admin/package.json | 21 +- apps/admin/src/app/auth-redirect/page.tsx | 8 +- .../src/app/dashboard/admin-users/page.tsx | 7 +- .../src/app/dashboard/audit-logs/page.tsx | 3 +- apps/admin/src/app/dashboard/billing/page.tsx | 3 +- .../src/app/dashboard/companies/[id]/page.tsx | 22 +- .../src/app/dashboard/companies/page.tsx | 7 +- .../src/app/dashboard/containers/page.tsx | 11 +- apps/admin/src/app/dashboard/layout.tsx | 26 +- .../app/dashboard/menu-management/page.tsx | 155 +- .../src/app/dashboard/notifications/page.tsx | 3 +- apps/admin/src/app/dashboard/page.tsx | 3 +- apps/admin/src/app/dashboard/pricing/page.tsx | 14 +- apps/admin/src/app/dashboard/renters/page.tsx | 6 +- .../src/app/dashboard/site-config/page.tsx | 9 +- apps/admin/src/app/favicon.ico/route.test.ts | 30 + apps/admin/src/app/forgot-password/page.tsx | 1 + apps/admin/src/app/login/page.tsx | 4 +- apps/admin/src/app/reset-password/page.tsx | 1 + apps/admin/src/app/root-redirects.test.ts | 18 + apps/admin/src/components/PublicShell.test.ts | 31 + apps/admin/src/lib/api.test.ts | 47 + apps/admin/src/lib/api.ts | 4 +- apps/admin/src/lib/appUrls.test.ts | 38 + apps/admin/src/middleware.ts | 17 + apps/admin/tsconfig.json | 30 +- apps/admin/vitest.config.ts | 17 + apps/api/.env.test | 4 +- apps/api/package.json | 10 +- apps/api/src/app.ts | 52 +- .../src/http/errors/errorMiddleware.test.ts | 99 + apps/api/src/http/errors/errorMiddleware.ts | 41 +- apps/api/src/http/respond/respond.test.ts | 49 + apps/api/src/http/upload/index.ts | 63 +- apps/api/src/http/upload/upload.test.ts | 41 + apps/api/src/http/validate/validate.test.ts | 27 + apps/api/src/http/webhooks.ts | 18 + apps/api/src/index.ts | 33 +- apps/api/src/lib/emailTranslations.test.ts | 70 + .../api/src/lib/isDatabaseUnavailable.test.ts | 19 + apps/api/src/lib/storage.test.ts | 62 + apps/api/src/lib/storage.ts | 41 +- apps/api/src/lib/storage/.gitkeep | 0 .../1959e058bf32f7f672cbea6b7683f88b.jpg | Bin 427537 -> 0 bytes .../d9b8c7164544f5d971a1ea09dba1ba38.jpg | Bin 170635 -> 0 bytes .../eae81e21ca0017a9d22eb92b64ff0c42.jpg | Bin 170635 -> 0 bytes .../cmp1s6doc0003wims028due25/brand/logo.jpg | Bin 7735 -> 0 bytes .../02d49acd5dfd85c60ee1758a14dbef07.jpg | Bin 23274 -> 0 bytes .../c2c92f69332b855e21ffeb95229a4add.jpg | Bin 23274 -> 0 bytes apps/api/src/middleware/authHelpers.test.ts | 68 + apps/api/src/middleware/authHelpers.ts | 13 + apps/api/src/middleware/rateLimiter.test.ts | 69 + apps/api/src/middleware/rateLimiter.ts | 77 +- apps/api/src/middleware/requestId.ts | 10 + .../src/middleware/requireAdminAuth.test.ts | 152 + apps/api/src/middleware/requireAdminAuth.ts | 51 +- apps/api/src/middleware/requireApiKey.test.ts | 159 + apps/api/src/middleware/requireApiKey.ts | 24 +- .../src/middleware/requireCompanyAuth.test.ts | 119 + apps/api/src/middleware/requireCompanyAuth.ts | 23 +- .../src/middleware/requireCompanyPolicy.ts | 22 + .../src/middleware/requireRenterAuth.test.ts | 107 + apps/api/src/middleware/requireRenterAuth.ts | 24 +- apps/api/src/middleware/requireRole.test.ts | 54 + .../middleware/requireSubscription.test.ts | 72 + apps/api/src/middleware/requireTenant.test.ts | 67 + .../admin/admin.billing.schemas.test.ts | 53 + .../modules/admin/admin.billing.service.ts | 50 +- .../modules/admin/admin.menu.schemas.test.ts | 52 + .../src/modules/admin/admin.presenter.test.ts | 34 + .../src/modules/admin/admin.repo.edge.test.ts | 72 + apps/api/src/modules/admin/admin.repo.ts | 24 +- apps/api/src/modules/admin/admin.routes.ts | 73 +- apps/api/src/modules/admin/admin.schemas.ts | 1 + apps/api/src/modules/admin/admin.service.ts | 106 +- .../analytics/analytics.schemas.edge.test.ts | 23 + .../analytics/analytics.service.test.ts | 150 + .../modules/auth/auth.company.repo.test.ts | 112 + .../modules/auth/auth.company.service.test.ts | 138 + .../src/modules/auth/auth.company.service.ts | 2 +- .../auth/auth.employee.repo.edge.test.ts | 72 + .../src/modules/auth/auth.employee.routes.ts | 10 +- .../auth/auth.employee.service.edge.test.ts | 140 + .../src/modules/auth/auth.employee.service.ts | 22 +- .../src/modules/auth/auth.presenter.test.ts | 89 + .../modules/auth/auth.renter.service.test.ts | 77 + .../src/modules/auth/auth.renter.service.ts | 2 +- .../auth/auth.role-specific-schemas.test.ts | 74 + .../api/src/modules/auth/auth.schemas.test.ts | 73 + .../companies/company.repo.edge.test.ts | 83 + .../api/src/modules/companies/company.repo.ts | 30 +- .../src/modules/companies/company.routes.ts | 9 +- .../companies/company.schemas.edge.test.ts | 37 + .../companies/company.service.edge.test.ts | 132 + .../complaints/complaint.repo.edge.test.ts | 71 + .../complaints/complaint.schemas.edge.test.ts | 35 + .../complaints/complaint.service.test.ts | 86 + .../modules/customers/customer.edge.test.ts | 119 + .../customers/customer.presenter.test.ts | 40 + .../customers/customer.repo.boundary.test.ts | 61 + .../src/modules/customers/customer.repo.ts | 8 +- .../customer.schemas.contract.test.ts | 54 + .../customer.service.boundary.test.ts | 119 + .../src/modules/customers/customer.service.ts | 8 +- .../marketplace/marketplace.presenter.test.ts | 25 + .../marketplace/marketplace.repo.edge.test.ts | 141 + .../marketplace/marketplace.schemas.test.ts | 42 + .../marketplace.service.edge.test.ts | 83 + .../marketplace/marketplace.service.ts | 19 +- .../modules/marketplace/marketplace.test.ts | 1 + .../menu/menu.service.boundary.test.ts | 173 + apps/api/src/modules/menu/menu.service.ts | 37 +- .../notification.repo.edge.test.ts | 109 + .../notification.schemas.edge.test.ts | 27 + .../notification.service.test.ts | 74 + .../modules/offers/offer.repo.edge.test.ts | 84 + apps/api/src/modules/offers/offer.repo.ts | 2 +- .../modules/offers/offer.schemas.edge.test.ts | 41 + .../src/modules/offers/offer.service.test.ts | 72 + .../src/modules/operations.schemas.test.ts | 42 + .../payments/payment.repo.edge.test.ts | 71 + .../src/modules/payments/payment.routes.ts | 11 +- .../payments/payment.schemas.edge.test.ts | 24 + .../modules/payments/payment.service.test.ts | 206 + .../src/modules/payments/payment.service.ts | 29 +- ...ervation.additional-driver.service.test.ts | 47 + .../reservation.document.service.test.ts | 68 + .../reservation.document.service.ts | 2 +- .../reservation.inspection.service.test.ts | 123 + .../reservation.lifecycle.service.test.ts | 129 + .../reservation.photo.service.test.ts | 71 + .../reservation.presenter.boundary.test.ts | 87 + .../reservation.pricing.service.test.ts | 28 + .../reservation.repo.edge.test.ts | 100 + .../reservation.schemas.edge.test.ts | 42 + .../reservations/reservation.service.ts | 10 +- .../modules/reviews/review.repo.edge.test.ts | 70 + .../reviews/review.schemas.edge.test.ts | 26 + .../modules/reviews/review.service.test.ts | 128 + .../src/modules/site/site.presenter.test.ts | 48 + apps/api/src/modules/site/site.presenter.ts | 37 + .../src/modules/site/site.repo.edge.test.ts | 113 + apps/api/src/modules/site/site.repo.ts | 20 + apps/api/src/modules/site/site.routes.ts | 3 +- .../api/src/modules/site/site.schemas.test.ts | 64 + apps/api/src/modules/site/site.schemas.ts | 1 + .../site/site.service.boundary.test.ts | 173 + apps/api/src/modules/site/site.service.ts | 86 +- apps/api/src/modules/site/site.test.ts | 27 +- .../subscription.entitlement.test.ts | 101 + .../subscriptions/subscription.policy.test.ts | 45 + .../subscription.repo.edge.test.ts | 140 + .../subscriptions/subscription.routes.ts | 15 +- .../subscription.schemas.edge.test.ts | 61 + .../subscription.service.edge.test.ts | 196 + .../subscriptions/subscription.service.ts | 27 +- .../modules/team/team.schemas.edge.test.ts | 34 + .../api/src/modules/team/team.service.test.ts | 42 + .../vehicles/vehicle.presenter.edge.test.ts | 32 + .../vehicles/vehicle.pricing.service.test.ts | 188 + .../vehicles/vehicle.repo.edge.test.ts | 96 + .../modules/vehicles/vehicle.schemas.test.ts | 127 + .../vehicles/vehicle.service.edge.test.ts | 131 + apps/api/src/security/apiKeys.ts | 25 + apps/api/src/security/policies/adminPolicy.ts | 12 + .../src/security/policies/companyPolicy.ts | 14 + apps/api/src/security/publicAccessTokens.ts | 17 + apps/api/src/security/sessionCookies.ts | 31 + apps/api/src/security/tokens.ts | 67 + apps/api/src/security/webhookIdempotency.ts | 97 + .../services/additionalDriverService.test.ts | 61 + apps/api/src/services/amanpayService.test.ts | 94 + .../api/src/services/containerService.test.ts | 136 + .../services/financialReportService.test.ts | 139 + .../api/src/services/insuranceService.test.ts | 57 + .../src/services/invoicePdfService.test.ts | 57 + .../services/licenseValidationService.test.ts | 76 + .../src/services/licenseValidationService.ts | 28 +- ...cationLocalizationService.boundary.test.ts | 102 + .../src/services/notificationService.test.ts | 134 + apps/api/src/services/paypalService.test.ts | 112 + .../services/platformContentService.test.ts | 55 + .../src/services/pricingRuleService.test.ts | 121 + apps/api/src/services/pricingRuleService.ts | 2 +- apps/api/src/services/teamService.ts | 2 +- .../vehicleAvailabilityService.test.ts | 111 + .../services/vehicleAvailabilityService.ts | 10 +- apps/api/src/swagger/openapi.boundary.test.ts | 42 + apps/api/src/swagger/openapi.ts | 2 +- .../src/tests/api/admin-billing.api.test.ts | 128 + .../api/admin-menu-boundaries.api.test.ts | 112 + .../src/tests/api/api-foundation.api.test.ts | 91 + .../src/tests/api/auth-boundaries.api.test.ts | 162 + .../src/tests/api/auth-middleware.api.test.ts | 150 + .../api/company-configuration.api.test.ts | 149 + ...ustomer-notification-analytics.api.test.ts | 148 + ...loyee-marketplace-notification.api.test.ts | 135 + .../api/feedback-offer-boundaries.api.test.ts | 139 + .../api/operations-validation.api.test.ts | 148 + apps/api/src/tests/api/operations.api.test.ts | 196 + apps/api/src/tests/api/payments.api.test.ts | 105 + .../tests/api/public-validation.api.test.ts | 163 + .../api/site-webhook-boundaries.api.test.ts | 90 + .../subscription-team-boundaries.api.test.ts | 126 + .../src/tests/api/subscriptions.api.test.ts | 105 + .../src/tests/api/vehicle-pricing.api.test.ts | 143 + .../e2e/admin-billing-boundaries.e2e.test.ts | 41 + .../e2e/admin-menu-boundaries.e2e.test.ts | 44 + apps/api/src/tests/e2e/api-smoke.e2e.test.ts | 51 + .../e2e/auth-public-disabled.e2e.test.ts | 24 + ...mpany-configuration-boundaries.e2e.test.ts | 25 + .../tests/e2e/customer-boundaries.e2e.test.ts | 47 + ...mployee-marketplace-boundaries.e2e.test.ts | 27 + .../e2e/feedback-offer-boundaries.e2e.test.ts | 23 + .../operations-auth-boundaries.e2e.test.ts | 23 + .../operations-validation-smoke.e2e.test.ts | 53 + .../e2e/payments-webhook-public.e2e.test.ts | 29 + .../e2e/protected-auth-boundaries.e2e.test.ts | 33 + .../e2e/public-validation-smoke.e2e.test.ts | 68 + .../e2e/site-webhook-boundaries.e2e.test.ts | 40 + .../tests/e2e/storage-boundaries.e2e.test.ts | 34 + .../subscription-team-boundaries.e2e.test.ts | 20 + .../e2e/subscriptions-public.e2e.test.ts | 58 + .../vehicle-pricing-boundaries.e2e.test.ts | 65 + apps/api/src/tests/helpers/fixtures.ts | 24 +- .../integration/admin-menu-boundaries.test.ts | 13 + .../tests/integration/api-foundation.test.ts | 91 + .../integration/auth-middleware-stack.test.ts | 18 + .../tests/integration/auth-onboarding.test.ts | 17 + ...ing-marketplace-vehicle-boundaries.test.ts | 11 + .../company-configuration-boundaries.test.ts | 11 + .../integration/complaints-reviews.test.ts | 111 + ...-notification-analytics-boundaries.test.ts | 12 + ...arketplace-notification-boundaries.test.ts | 15 + ...edback-offer-repository-boundaries.test.ts | 14 + .../infrastructure-boundaries.test.ts | 39 + .../src/tests/integration/operations.test.ts | 119 + .../public-validation-boundaries.test.ts | 39 + .../integration/repository-boundaries.test.ts | 13 + .../integration/schema-boundaries.test.ts | 12 + .../site-customer-boundaries.test.ts | 17 + .../subscription-entitlements.test.ts | 41 + .../subscription-team-boundaries.test.ts | 12 + .../vehicle-pricing-boundaries.test.ts | 7 + apps/api/src/types/express.d.ts | 2 + apps/api/vitest.api.config.ts | 16 + apps/api/vitest.e2e.config.ts | 16 + apps/dashboard/README.md | 27 +- apps/dashboard/next-env.d.ts | 3 +- apps/dashboard/next.config.js | 31 + apps/dashboard/package.json | 9 +- .../src/app/(dashboard)/reports/page.tsx | 5 +- .../src/app/(dashboard)/team/page.tsx | 23 +- apps/dashboard/src/app/layout.tsx | 4 +- .../src/app/public-auth-pages.test.ts | 57 + .../[[...sign-in]]/SignInPageClient.tsx | 35 +- .../VehicleCalendar.boundary.test.ts | 64 + .../src/components/VehicleCalendar.tsx | 70 +- .../VehiclePricingManager.helpers.test.ts | 129 + .../src/components/VehiclePricingManager.tsx | 26 +- .../src/components/layout/PublicShell.test.ts | 32 + .../src/components/layout/Sidebar.tsx | 18 +- .../src/components/layout/TopBar.tsx | 32 +- .../DamageInspectionCard.helpers.test.ts | 104 + .../reservations/DamageInspectionCard.tsx | 86 +- .../VehicleConditionSheet.boundary.test.ts | 65 + .../reservations/VehicleConditionSheet.tsx | 45 +- .../src/components/ui/BilingualInput.test.ts | 22 + .../src/components/ui/StatCard.test.ts | 72 + .../dashboard/src/hooks/useTeam.state.test.ts | 104 + apps/dashboard/src/hooks/useTeam.ts | 80 +- apps/dashboard/src/lib/api.test.ts | 81 + apps/dashboard/src/lib/api.ts | 15 - apps/dashboard/src/lib/appUrls.test.ts | 34 + apps/dashboard/src/lib/dashboardPaths.test.ts | 22 + apps/dashboard/src/lib/preferences.test.ts | 96 + apps/dashboard/src/lib/preferences.ts | 11 +- apps/dashboard/src/lib/urls.test.ts | 64 + apps/dashboard/src/middleware.test.ts | 127 + apps/dashboard/src/middleware.ts | 11 +- apps/dashboard/tsconfig.json | 30 +- apps/dashboard/vitest.config.ts | 17 + apps/marketplace/next-env.d.ts | 3 +- apps/marketplace/next.config.js | 31 + apps/marketplace/package.json | 19 +- .../src/app/(public)/app-policy-pages.test.ts | 31 + .../src/app/(public)/explore/[slug]/page.tsx | 2 +- .../explore/[slug]/vehicles/[id]/page.tsx | 2 +- .../src/app/(public)/explore/page.tsx | 2 +- .../app/(public)/footer/[slug]/page.test.ts | 34 + apps/marketplace/src/app/layout.tsx | 6 +- .../src/app/renter/dashboard/page.tsx | 10 +- .../src/app/renter/sign-in/page.tsx | 4 +- .../src/app/renter/sign-up/page.tsx | 4 +- apps/marketplace/src/app/sign-in/page.tsx | 2 +- .../components/BookingForm.boundary.test.ts | 29 + .../src/components/BookingForm.tsx | 35 +- .../src/components/FooterContentPage.test.ts | 64 + .../MarketplaceHeader.boundary.test.ts | 32 + .../src/components/MarketplaceHeader.tsx | 42 +- .../MarketplaceShell.content.test.ts | 35 + .../src/components/MarketplaceShell.tsx | 13 +- .../WorkspaceFrame.boundary.test.ts | 48 + .../src/components/WorkspaceFrame.tsx | 30 +- apps/marketplace/src/lib/api.test.ts | 73 + apps/marketplace/src/lib/appUrls.test.ts | 40 + .../marketplace/src/lib/footerContent.test.ts | 67 + apps/marketplace/src/lib/footerContent.ts | 6 +- apps/marketplace/src/lib/i18n.server.test.ts | 41 + apps/marketplace/src/lib/i18n.server.ts | 4 +- apps/marketplace/src/lib/i18n.test.ts | 18 + apps/marketplace/src/lib/preferences.test.ts | 69 + apps/marketplace/src/lib/preferences.ts | 11 +- apps/marketplace/src/lib/renter.test.ts | 78 + apps/marketplace/src/lib/renter.ts | 12 +- apps/marketplace/src/middleware.test.ts | 104 + apps/marketplace/src/middleware.ts | 9 + apps/marketplace/tsconfig.json | 30 +- apps/marketplace/vitest.config.ts | 17 + audit-production.json | 276 + audit-production.txt | 58 + docker-compose.production.yml | 65 +- docker/entrypoint.production.sh | 13 + docker/scripts/npm | 0 docs/SECURITY_HARDENING_APPLIED_REPORT.md | 160 + ...URITY_HARDENING_LEFTOVER_APPLIED_REPORT.md | 255 + docs/project-design/COOKIE_POLICY.md | 8 +- memory/project_auth_architecture.md | 12 +- package-lock.json | 870 +- package.json | 8 +- .../migration.sql | 15 + .../migration.sql | 13 + .../migration.sql | 18 + .../migration.sql | 4 + .../migration.sql | 16 + packages/database/prisma/schema.prisma | 67 +- packages/database/src/index.d.ts | 1 - packages/database/src/index.ts | 1 - scripts/docker-cleanup.sh | 0 scripts/docker-prod-common.sh | 19 +- scripts/docker-prod-deploy.sh | 2 +- scripts/docker-prod-run-pulled-image.sh | 9 +- scripts/docker-prod-up-admin.sh | 0 scripts/docker-prod-up-all.sh | 4 +- scripts/docker-prod-up-api.sh | 0 scripts/docker-prod-up-dashboard.sh | 0 scripts/docker-prod-up-frontends.sh | 0 scripts/docker-prod-up-marketplace.sh | 0 scripts/docker-prod-up-pgmanage.sh | 15 +- scripts/docker-prod-up-postgres.sh | 0 scripts/docker-prod-up-redis.sh | 0 scripts/docker-prod-up-traefik.sh | 0 scripts/security-static-check.mjs | 74 + scripts/setup-clerk-keys.sh | 0 security-reports/clamav.txt | 37627 ++++++++++++++++ security-reports/npm-audit.json | 1361 + security-reports/osv.json | 7176 +++ security-reports/semgrep.json | 1 + security-reports/trivy.json | 17087 +++++++ security_hardening_changed_files.txt | 12 + security_hardening_incremental.diff | 342 + security_hardening_leftover.diff | 1498 + security_hardening_leftover_changed_files.txt | 32 + test-admin-output-after-db-generate.txt | 53 + test-api-output-after-db-generate.txt | 1221 + test-dashboard-output-after-db-generate.txt | 56 + test-frontedns-after-db-generate.txt | 8 + test-frontends-after-db-generate.txt | 60 + test-marketplace-output-after-db-generate.txt | 84 + 377 files changed, 84020 insertions(+), 1337 deletions(-) create mode 100644 apps/admin/src/app/favicon.ico/route.test.ts create mode 100644 apps/admin/src/app/root-redirects.test.ts create mode 100644 apps/admin/src/components/PublicShell.test.ts create mode 100644 apps/admin/src/lib/api.test.ts create mode 100644 apps/admin/src/lib/appUrls.test.ts create mode 100644 apps/admin/src/middleware.ts create mode 100644 apps/admin/vitest.config.ts create mode 100644 apps/api/src/http/errors/errorMiddleware.test.ts create mode 100644 apps/api/src/http/respond/respond.test.ts create mode 100644 apps/api/src/http/upload/upload.test.ts create mode 100644 apps/api/src/http/validate/validate.test.ts create mode 100644 apps/api/src/http/webhooks.ts create mode 100644 apps/api/src/lib/emailTranslations.test.ts create mode 100644 apps/api/src/lib/isDatabaseUnavailable.test.ts create mode 100644 apps/api/src/lib/storage.test.ts delete mode 100644 apps/api/src/lib/storage/.gitkeep delete mode 100644 apps/api/src/lib/storage/companies/cmoyu36ii000112g8j5m84zgv/vehicles/1959e058bf32f7f672cbea6b7683f88b.jpg delete mode 100644 apps/api/src/lib/storage/companies/cmoyu36ii000112g8j5m84zgv/vehicles/d9b8c7164544f5d971a1ea09dba1ba38.jpg delete mode 100644 apps/api/src/lib/storage/companies/cmoyu36ii000112g8j5m84zgv/vehicles/eae81e21ca0017a9d22eb92b64ff0c42.jpg delete mode 100644 apps/api/src/lib/storage/companies/cmp1s6doc0003wims028due25/brand/logo.jpg delete mode 100644 apps/api/src/lib/storage/companies/cmp1s6doc0003wims028due25/vehicles/02d49acd5dfd85c60ee1758a14dbef07.jpg delete mode 100644 apps/api/src/lib/storage/companies/cmpbosg0o0001bh8tc433zxt2/vehicles/c2c92f69332b855e21ffeb95229a4add.jpg create mode 100644 apps/api/src/middleware/authHelpers.test.ts create mode 100644 apps/api/src/middleware/rateLimiter.test.ts create mode 100644 apps/api/src/middleware/requestId.ts create mode 100644 apps/api/src/middleware/requireAdminAuth.test.ts create mode 100644 apps/api/src/middleware/requireApiKey.test.ts create mode 100644 apps/api/src/middleware/requireCompanyAuth.test.ts create mode 100644 apps/api/src/middleware/requireCompanyPolicy.ts create mode 100644 apps/api/src/middleware/requireRenterAuth.test.ts create mode 100644 apps/api/src/middleware/requireRole.test.ts create mode 100644 apps/api/src/middleware/requireSubscription.test.ts create mode 100644 apps/api/src/middleware/requireTenant.test.ts create mode 100644 apps/api/src/modules/admin/admin.billing.schemas.test.ts create mode 100644 apps/api/src/modules/admin/admin.menu.schemas.test.ts create mode 100644 apps/api/src/modules/admin/admin.presenter.test.ts create mode 100644 apps/api/src/modules/admin/admin.repo.edge.test.ts create mode 100644 apps/api/src/modules/analytics/analytics.schemas.edge.test.ts create mode 100644 apps/api/src/modules/analytics/analytics.service.test.ts create mode 100644 apps/api/src/modules/auth/auth.company.repo.test.ts create mode 100644 apps/api/src/modules/auth/auth.company.service.test.ts create mode 100644 apps/api/src/modules/auth/auth.employee.repo.edge.test.ts create mode 100644 apps/api/src/modules/auth/auth.employee.service.edge.test.ts create mode 100644 apps/api/src/modules/auth/auth.presenter.test.ts create mode 100644 apps/api/src/modules/auth/auth.renter.service.test.ts create mode 100644 apps/api/src/modules/auth/auth.role-specific-schemas.test.ts create mode 100644 apps/api/src/modules/auth/auth.schemas.test.ts create mode 100644 apps/api/src/modules/companies/company.repo.edge.test.ts create mode 100644 apps/api/src/modules/companies/company.schemas.edge.test.ts create mode 100644 apps/api/src/modules/companies/company.service.edge.test.ts create mode 100644 apps/api/src/modules/complaints/complaint.repo.edge.test.ts create mode 100644 apps/api/src/modules/complaints/complaint.schemas.edge.test.ts create mode 100644 apps/api/src/modules/complaints/complaint.service.test.ts create mode 100644 apps/api/src/modules/customers/customer.edge.test.ts create mode 100644 apps/api/src/modules/customers/customer.presenter.test.ts create mode 100644 apps/api/src/modules/customers/customer.repo.boundary.test.ts create mode 100644 apps/api/src/modules/customers/customer.schemas.contract.test.ts create mode 100644 apps/api/src/modules/customers/customer.service.boundary.test.ts create mode 100644 apps/api/src/modules/marketplace/marketplace.presenter.test.ts create mode 100644 apps/api/src/modules/marketplace/marketplace.repo.edge.test.ts create mode 100644 apps/api/src/modules/marketplace/marketplace.schemas.test.ts create mode 100644 apps/api/src/modules/marketplace/marketplace.service.edge.test.ts create mode 100644 apps/api/src/modules/menu/menu.service.boundary.test.ts create mode 100644 apps/api/src/modules/notifications/notification.repo.edge.test.ts create mode 100644 apps/api/src/modules/notifications/notification.schemas.edge.test.ts create mode 100644 apps/api/src/modules/notifications/notification.service.test.ts create mode 100644 apps/api/src/modules/offers/offer.repo.edge.test.ts create mode 100644 apps/api/src/modules/offers/offer.schemas.edge.test.ts create mode 100644 apps/api/src/modules/offers/offer.service.test.ts create mode 100644 apps/api/src/modules/operations.schemas.test.ts create mode 100644 apps/api/src/modules/payments/payment.repo.edge.test.ts create mode 100644 apps/api/src/modules/payments/payment.schemas.edge.test.ts create mode 100644 apps/api/src/modules/payments/payment.service.test.ts create mode 100644 apps/api/src/modules/reservations/reservation.additional-driver.service.test.ts create mode 100644 apps/api/src/modules/reservations/reservation.document.service.test.ts create mode 100644 apps/api/src/modules/reservations/reservation.inspection.service.test.ts create mode 100644 apps/api/src/modules/reservations/reservation.lifecycle.service.test.ts create mode 100644 apps/api/src/modules/reservations/reservation.photo.service.test.ts create mode 100644 apps/api/src/modules/reservations/reservation.presenter.boundary.test.ts create mode 100644 apps/api/src/modules/reservations/reservation.pricing.service.test.ts create mode 100644 apps/api/src/modules/reservations/reservation.repo.edge.test.ts create mode 100644 apps/api/src/modules/reservations/reservation.schemas.edge.test.ts create mode 100644 apps/api/src/modules/reviews/review.repo.edge.test.ts create mode 100644 apps/api/src/modules/reviews/review.schemas.edge.test.ts create mode 100644 apps/api/src/modules/reviews/review.service.test.ts create mode 100644 apps/api/src/modules/site/site.presenter.test.ts create mode 100644 apps/api/src/modules/site/site.repo.edge.test.ts create mode 100644 apps/api/src/modules/site/site.schemas.test.ts create mode 100644 apps/api/src/modules/site/site.service.boundary.test.ts create mode 100644 apps/api/src/modules/subscriptions/subscription.entitlement.test.ts create mode 100644 apps/api/src/modules/subscriptions/subscription.policy.test.ts create mode 100644 apps/api/src/modules/subscriptions/subscription.repo.edge.test.ts create mode 100644 apps/api/src/modules/subscriptions/subscription.schemas.edge.test.ts create mode 100644 apps/api/src/modules/subscriptions/subscription.service.edge.test.ts create mode 100644 apps/api/src/modules/team/team.schemas.edge.test.ts create mode 100644 apps/api/src/modules/team/team.service.test.ts create mode 100644 apps/api/src/modules/vehicles/vehicle.presenter.edge.test.ts create mode 100644 apps/api/src/modules/vehicles/vehicle.pricing.service.test.ts create mode 100644 apps/api/src/modules/vehicles/vehicle.repo.edge.test.ts create mode 100644 apps/api/src/modules/vehicles/vehicle.schemas.test.ts create mode 100644 apps/api/src/modules/vehicles/vehicle.service.edge.test.ts create mode 100644 apps/api/src/security/apiKeys.ts create mode 100644 apps/api/src/security/policies/adminPolicy.ts create mode 100644 apps/api/src/security/policies/companyPolicy.ts create mode 100644 apps/api/src/security/publicAccessTokens.ts create mode 100644 apps/api/src/security/sessionCookies.ts create mode 100644 apps/api/src/security/tokens.ts create mode 100644 apps/api/src/security/webhookIdempotency.ts create mode 100644 apps/api/src/services/additionalDriverService.test.ts create mode 100644 apps/api/src/services/amanpayService.test.ts create mode 100644 apps/api/src/services/containerService.test.ts create mode 100644 apps/api/src/services/financialReportService.test.ts create mode 100644 apps/api/src/services/insuranceService.test.ts create mode 100644 apps/api/src/services/invoicePdfService.test.ts create mode 100644 apps/api/src/services/licenseValidationService.test.ts create mode 100644 apps/api/src/services/notificationLocalizationService.boundary.test.ts create mode 100644 apps/api/src/services/notificationService.test.ts create mode 100644 apps/api/src/services/paypalService.test.ts create mode 100644 apps/api/src/services/platformContentService.test.ts create mode 100644 apps/api/src/services/pricingRuleService.test.ts create mode 100644 apps/api/src/services/vehicleAvailabilityService.test.ts create mode 100644 apps/api/src/swagger/openapi.boundary.test.ts create mode 100644 apps/api/src/tests/api/admin-billing.api.test.ts create mode 100644 apps/api/src/tests/api/admin-menu-boundaries.api.test.ts create mode 100644 apps/api/src/tests/api/api-foundation.api.test.ts create mode 100644 apps/api/src/tests/api/auth-boundaries.api.test.ts create mode 100644 apps/api/src/tests/api/auth-middleware.api.test.ts create mode 100644 apps/api/src/tests/api/company-configuration.api.test.ts create mode 100644 apps/api/src/tests/api/customer-notification-analytics.api.test.ts create mode 100644 apps/api/src/tests/api/employee-marketplace-notification.api.test.ts create mode 100644 apps/api/src/tests/api/feedback-offer-boundaries.api.test.ts create mode 100644 apps/api/src/tests/api/operations-validation.api.test.ts create mode 100644 apps/api/src/tests/api/operations.api.test.ts create mode 100644 apps/api/src/tests/api/payments.api.test.ts create mode 100644 apps/api/src/tests/api/public-validation.api.test.ts create mode 100644 apps/api/src/tests/api/site-webhook-boundaries.api.test.ts create mode 100644 apps/api/src/tests/api/subscription-team-boundaries.api.test.ts create mode 100644 apps/api/src/tests/api/subscriptions.api.test.ts create mode 100644 apps/api/src/tests/api/vehicle-pricing.api.test.ts create mode 100644 apps/api/src/tests/e2e/admin-billing-boundaries.e2e.test.ts create mode 100644 apps/api/src/tests/e2e/admin-menu-boundaries.e2e.test.ts create mode 100644 apps/api/src/tests/e2e/api-smoke.e2e.test.ts create mode 100644 apps/api/src/tests/e2e/auth-public-disabled.e2e.test.ts create mode 100644 apps/api/src/tests/e2e/company-configuration-boundaries.e2e.test.ts create mode 100644 apps/api/src/tests/e2e/customer-boundaries.e2e.test.ts create mode 100644 apps/api/src/tests/e2e/employee-marketplace-boundaries.e2e.test.ts create mode 100644 apps/api/src/tests/e2e/feedback-offer-boundaries.e2e.test.ts create mode 100644 apps/api/src/tests/e2e/operations-auth-boundaries.e2e.test.ts create mode 100644 apps/api/src/tests/e2e/operations-validation-smoke.e2e.test.ts create mode 100644 apps/api/src/tests/e2e/payments-webhook-public.e2e.test.ts create mode 100644 apps/api/src/tests/e2e/protected-auth-boundaries.e2e.test.ts create mode 100644 apps/api/src/tests/e2e/public-validation-smoke.e2e.test.ts create mode 100644 apps/api/src/tests/e2e/site-webhook-boundaries.e2e.test.ts create mode 100644 apps/api/src/tests/e2e/storage-boundaries.e2e.test.ts create mode 100644 apps/api/src/tests/e2e/subscription-team-boundaries.e2e.test.ts create mode 100644 apps/api/src/tests/e2e/subscriptions-public.e2e.test.ts create mode 100644 apps/api/src/tests/e2e/vehicle-pricing-boundaries.e2e.test.ts create mode 100644 apps/api/src/tests/integration/admin-menu-boundaries.test.ts create mode 100644 apps/api/src/tests/integration/api-foundation.test.ts create mode 100644 apps/api/src/tests/integration/auth-middleware-stack.test.ts create mode 100644 apps/api/src/tests/integration/auth-onboarding.test.ts create mode 100644 apps/api/src/tests/integration/billing-marketplace-vehicle-boundaries.test.ts create mode 100644 apps/api/src/tests/integration/company-configuration-boundaries.test.ts create mode 100644 apps/api/src/tests/integration/complaints-reviews.test.ts create mode 100644 apps/api/src/tests/integration/customer-notification-analytics-boundaries.test.ts create mode 100644 apps/api/src/tests/integration/employee-marketplace-notification-boundaries.test.ts create mode 100644 apps/api/src/tests/integration/feedback-offer-repository-boundaries.test.ts create mode 100644 apps/api/src/tests/integration/infrastructure-boundaries.test.ts create mode 100644 apps/api/src/tests/integration/operations.test.ts create mode 100644 apps/api/src/tests/integration/public-validation-boundaries.test.ts create mode 100644 apps/api/src/tests/integration/repository-boundaries.test.ts create mode 100644 apps/api/src/tests/integration/schema-boundaries.test.ts create mode 100644 apps/api/src/tests/integration/site-customer-boundaries.test.ts create mode 100644 apps/api/src/tests/integration/subscription-entitlements.test.ts create mode 100644 apps/api/src/tests/integration/subscription-team-boundaries.test.ts create mode 100644 apps/api/src/tests/integration/vehicle-pricing-boundaries.test.ts create mode 100644 apps/api/vitest.api.config.ts create mode 100644 apps/api/vitest.e2e.config.ts create mode 100644 apps/dashboard/src/app/public-auth-pages.test.ts create mode 100644 apps/dashboard/src/components/VehicleCalendar.boundary.test.ts create mode 100644 apps/dashboard/src/components/VehiclePricingManager.helpers.test.ts create mode 100644 apps/dashboard/src/components/layout/PublicShell.test.ts create mode 100644 apps/dashboard/src/components/reservations/DamageInspectionCard.helpers.test.ts create mode 100644 apps/dashboard/src/components/reservations/VehicleConditionSheet.boundary.test.ts create mode 100644 apps/dashboard/src/components/ui/BilingualInput.test.ts create mode 100644 apps/dashboard/src/components/ui/StatCard.test.ts create mode 100644 apps/dashboard/src/hooks/useTeam.state.test.ts create mode 100644 apps/dashboard/src/lib/api.test.ts create mode 100644 apps/dashboard/src/lib/appUrls.test.ts create mode 100644 apps/dashboard/src/lib/dashboardPaths.test.ts create mode 100644 apps/dashboard/src/lib/preferences.test.ts create mode 100644 apps/dashboard/src/lib/urls.test.ts create mode 100644 apps/dashboard/src/middleware.test.ts create mode 100644 apps/dashboard/vitest.config.ts create mode 100644 apps/marketplace/src/app/(public)/app-policy-pages.test.ts create mode 100644 apps/marketplace/src/app/(public)/footer/[slug]/page.test.ts create mode 100644 apps/marketplace/src/components/BookingForm.boundary.test.ts create mode 100644 apps/marketplace/src/components/FooterContentPage.test.ts create mode 100644 apps/marketplace/src/components/MarketplaceHeader.boundary.test.ts create mode 100644 apps/marketplace/src/components/MarketplaceShell.content.test.ts create mode 100644 apps/marketplace/src/components/WorkspaceFrame.boundary.test.ts create mode 100644 apps/marketplace/src/lib/api.test.ts create mode 100644 apps/marketplace/src/lib/appUrls.test.ts create mode 100644 apps/marketplace/src/lib/footerContent.test.ts create mode 100644 apps/marketplace/src/lib/i18n.server.test.ts create mode 100644 apps/marketplace/src/lib/i18n.test.ts create mode 100644 apps/marketplace/src/lib/preferences.test.ts create mode 100644 apps/marketplace/src/lib/renter.test.ts create mode 100644 apps/marketplace/src/middleware.test.ts create mode 100644 apps/marketplace/vitest.config.ts create mode 100644 audit-production.json create mode 100644 audit-production.txt create mode 100755 docker/entrypoint.production.sh mode change 100755 => 100644 docker/scripts/npm create mode 100644 docs/SECURITY_HARDENING_APPLIED_REPORT.md create mode 100644 docs/SECURITY_HARDENING_LEFTOVER_APPLIED_REPORT.md create mode 100644 packages/database/prisma/migrations/20260609194000_company_api_keys/migration.sql create mode 100644 packages/database/prisma/migrations/20260609194500_reservation_public_access/migration.sql create mode 100644 packages/database/prisma/migrations/20260609201000_add_webhook_events/migration.sql create mode 100644 packages/database/prisma/migrations/20260609233000_drop_legacy_company_api_key/migration.sql create mode 100644 packages/database/prisma/migrations/20260610001500_add_admin_recovery_codes/migration.sql mode change 100755 => 100644 scripts/docker-cleanup.sh mode change 100755 => 100644 scripts/docker-prod-common.sh mode change 100755 => 100644 scripts/docker-prod-up-admin.sh mode change 100755 => 100644 scripts/docker-prod-up-all.sh mode change 100755 => 100644 scripts/docker-prod-up-api.sh mode change 100755 => 100644 scripts/docker-prod-up-dashboard.sh mode change 100755 => 100644 scripts/docker-prod-up-frontends.sh mode change 100755 => 100644 scripts/docker-prod-up-marketplace.sh mode change 100755 => 100644 scripts/docker-prod-up-pgmanage.sh mode change 100755 => 100644 scripts/docker-prod-up-postgres.sh mode change 100755 => 100644 scripts/docker-prod-up-redis.sh mode change 100755 => 100644 scripts/docker-prod-up-traefik.sh create mode 100644 scripts/security-static-check.mjs mode change 100755 => 100644 scripts/setup-clerk-keys.sh create mode 100644 security-reports/clamav.txt create mode 100644 security-reports/npm-audit.json create mode 100644 security-reports/osv.json create mode 100644 security-reports/semgrep.json create mode 100644 security-reports/trivy.json create mode 100644 security_hardening_changed_files.txt create mode 100644 security_hardening_incremental.diff create mode 100644 security_hardening_leftover.diff create mode 100644 security_hardening_leftover_changed_files.txt create mode 100644 test-admin-output-after-db-generate.txt create mode 100644 test-api-output-after-db-generate.txt create mode 100644 test-dashboard-output-after-db-generate.txt create mode 100644 test-frontedns-after-db-generate.txt create mode 100644 test-frontends-after-db-generate.txt create mode 100644 test-marketplace-output-after-db-generate.txt diff --git a/.env.docker.dev b/.env.docker.dev index 0126804..11ec2b1 100644 --- a/.env.docker.dev +++ b/.env.docker.dev @@ -1,4 +1,4 @@ -DATABASE_URL=postgresql://postgres:password@postgres:5432/rentaldrivego +DATABASE_URL=postgresql://placeholder:placeholder@placeholder:5432/placeholder REDIS_URL=redis://redis:6379 API_PORT=4000 API_URL=http://localhost:4000 @@ -15,27 +15,27 @@ ADMIN_INTERNAL_URL=http://host.docker.internal:3002 # correct port rather than through the marketplace proxy (which doesn't have those chunks). DASHBOARD_ASSET_PREFIX=http://localhost:3001/dashboard ADMIN_ASSET_PREFIX=http://localhost:3002/admin -JWT_SECRET=dev-secret +JWT_SECRET=placeholder JWT_EXPIRY=8h RENTER_JWT_EXPIRY=7d ADMIN_SEED_EMAIL=rentaldrivego@gmail.com -ADMIN_SEED_PASSWORD=changeme123 +ADMIN_SEED_PASSWORD=placeholder ADMIN_SEED_FIRST_NAME=Platform ADMIN_SEED_LAST_NAME=Admin NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= -CLERK_SECRET_KEY= +CLERK_SECRET_KEY=placeholder NODE_ENV=development CORS_ORIGINS=http://localhost:3000,http://localhost:4000 # Email - disable Resend (placeholder), use SMTP instead -RESEND_API_KEY=re_... +RESEND_API_KEY=placeholder EMAIL_FROM=rentaldrivego@gmail.com EMAIL_FROM_NAME=RentalDriveGo MAIL_HOST=smtp.gmail.com MAIL_PORT=587 MAIL_SCHEME=smtp MAIL_USERNAME=rentaldrivego@gmail.com -MAIL_PASSWORD=xmhg ibqy muoc rntc +MAIL_PASSWORD=placeholder MAIL_FROM_ADDRESS=rentaldrivego@gmail.com MAIL_FROM_NAME=RentalDriveGo MAIL_REPLY_TO_ADDRESS=rentaldrivego@gmail.com diff --git a/.env.docker.production.example b/.env.docker.production.example index 6b966a2..b503737 100644 --- a/.env.docker.production.example +++ b/.env.docker.production.example @@ -14,20 +14,20 @@ REGISTRY_UPSTREAM_URL=http://10.0.0.10:5000 # APP_VERSION=latest REGISTRY_HOST=registry.rentaldrivego.ma REGISTRY_USER=rentaldrivego_registry -REGISTRY_PASSWORD=PMPS5k0D7rUeJOk0NkhI5bRtoGjkUqjK +REGISTRY_PASSWORD=placeholder REGISTRY_IMAGE=registry.rentaldrivego.ma/rentaldrivego/car_management_system -REGISTRY_HTTP_SECRET=PMPS5k0D7rUeJOk0NkhI5bRtoGjkUqjK +REGISTRY_HTTP_SECRET=placeholder # ── Database ────────────────────────────────────────────────────────────────── # Full connection URL (used when DATABASE_URL_FROM_POSTGRES=false) -DATABASE_URL=postgresql://postgres:24DY&1u5FLCkNMeiO_p@postgres:5432/rentaldrivego +DATABASE_URL=postgresql://placeholder:placeholder@placeholder:5432/placeholder # Build the URL from individual vars (set to true to use POSTGRES_* vars below) DATABASE_URL_FROM_POSTGRES=true POSTGRES_HOST=postgres POSTGRES_PORT=5432 POSTGRES_DB=rentaldrivego POSTGRES_USER=postgres -POSTGRES_PASSWORD=24DY&1u5FLCkNMeiO_p +POSTGRES_PASSWORD=placeholder # ── Cache ───────────────────────────────────────────────────────────────────── @@ -62,7 +62,7 @@ ADMIN_URL=https://rentaldrivego.ma/admin CORS_ORIGINS=https://rentaldrivego.ma,https://www.rentaldrivego.ma # ── Auth ────────────────────────────────────────────────────────────────────── -JWT_SECRET=PMPS5k0D7rUeJOk0NkhI5bRtoGjkUqjK +JWT_SECRET=placeholder JWT_EXPIRY=8h RENTER_JWT_EXPIRY=7d NODE_ENV=production @@ -78,7 +78,7 @@ MAIL_HOST=smtp.gmail.com MAIL_PORT=587 MAIL_SCHEME=smtp MAIL_USERNAME=rentaldrivego@gmail.com -MAIL_PASSWORD="xmhg ibqy muoc rntc" +MAIL_PASSWORD=placeholder MAIL_FROM_ADDRESS=rentaldrivego@gmail.com MAIL_FROM_NAME=RentalDriveGo MAIL_REPLY_TO_ADDRESS=rentaldrivego@gmail.com diff --git a/.env.docker.production.generated b/.env.docker.production.generated index dac62b6..90ae0fd 100644 --- a/.env.docker.production.generated +++ b/.env.docker.production.generated @@ -13,8 +13,8 @@ POSTGRES_HOST=postgres POSTGRES_PORT=5432 POSTGRES_DB=rentaldrivego POSTGRES_USER=postgres -POSTGRES_PASSWORD=change-me -DATABASE_URL=postgresql://postgres:change-me@postgres:5432/rentaldrivego +POSTGRES_PASSWORD=placeholder +DATABASE_URL=postgresql://placeholder:placeholder@placeholder:5432/placeholder # Cache REDIS_URL=redis://redis:6379 @@ -38,7 +38,7 @@ ADMIN_URL=https://example.com/admin CORS_ORIGINS=https://example.com,https://www.example.com # Auth -JWT_SECRET=replace-with-a-long-random-secret +JWT_SECRET=placeholder JWT_EXPIRY=8h RENTER_JWT_EXPIRY=7d NODE_ENV=production @@ -57,7 +57,7 @@ MAIL_HOST=smtp.gmail.com MAIL_PORT=587 MAIL_SCHEME=smtp MAIL_USERNAME=your-smtp-user@example.com -MAIL_PASSWORD=replace-with-an-app-password +MAIL_PASSWORD=placeholder MAIL_FROM_ADDRESS=noreply@example.com MAIL_FROM_NAME=RentalDriveGo MAIL_REPLY_TO_ADDRESS=support@example.com diff --git a/.env.docker.test b/.env.docker.test index 5a91bd4..9079815 100644 --- a/.env.docker.test +++ b/.env.docker.test @@ -1,10 +1,10 @@ -DATABASE_URL=postgresql://postgres:password@postgres:5432/rentaldrivego_test +DATABASE_URL=postgresql://placeholder:placeholder@placeholder:5432/placeholder REDIS_URL=redis://redis:6379 API_PORT=4000 API_URL=http://localhost:4000 API_INTERNAL_URL=http://localhost:4000/api/v1 NEXT_PUBLIC_API_URL=http://localhost:4000/api/v1 -JWT_SECRET=test-secret +JWT_SECRET=placeholder JWT_EXPIRY=8h RENTER_JWT_EXPIRY=7d NODE_ENV=test diff --git a/.env.example b/.env.example index e8dfeaa..fb74aa5 100644 --- a/.env.example +++ b/.env.example @@ -4,7 +4,7 @@ # ═══════════════════════════════════════════════════════════════ # ─── Database ────────────────────────────────────────────────── -DATABASE_URL="postgresql://postgres:24DY&1u5FLCkNMeiO_p@localhost:5432/rentaldrivego" +DATABASE_URL=postgresql://placeholder:placeholder@placeholder:5432/placeholder # ─── API ─────────────────────────────────────────────────────── API_PORT=4000 @@ -12,13 +12,13 @@ API_URL=http://localhost:4000 NEXT_PUBLIC_API_URL=http://localhost:4000/api/v1 # ─── JWT (Renter auth + Admin auth) ─────────────────────────── -JWT_SECRET=your-super-secret-jwt-key-change-in-production +JWT_SECRET=placeholder JWT_EXPIRY=8h RENTER_JWT_EXPIRY=7d # ─── Clerk (Company employee auth) ──────────────────────────── NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_... -CLERK_SECRET_KEY=sk_test_... +CLERK_SECRET_KEY=placeholder CLERK_WEBHOOK_SECRET=whsec_... NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up @@ -28,14 +28,14 @@ NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/onboarding # ─── AmanPay (Primary payment provider) ─────────────────────── # RentalDriveGo's own AmanPay account (for collecting subscription fees) AMANPAY_MERCHANT_ID=your-amanpay-merchant-id -AMANPAY_SECRET_KEY=your-amanpay-secret-key +AMANPAY_SECRET_KEY=placeholder AMANPAY_BASE_URL=https://api.amanpay.net -AMANPAY_WEBHOOK_SECRET=your-amanpay-webhook-secret +AMANPAY_WEBHOOK_SECRET=placeholder # ─── PayPal (Secondary payment provider) ────────────────────── # RentalDriveGo's own PayPal account (for collecting subscription fees) PAYPAL_CLIENT_ID=your-paypal-client-id -PAYPAL_CLIENT_SECRET=your-paypal-client-secret +PAYPAL_CLIENT_SECRET=placeholder PAYPAL_BASE_URL=https://api-m.paypal.com # Use https://api-m.sandbox.paypal.com for sandbox NEXT_PUBLIC_PAYPAL_CLIENT_ID=your-paypal-client-id @@ -43,23 +43,23 @@ NEXT_PUBLIC_PAYPAL_CLIENT_ID=your-paypal-client-id # ─── Cloudinary (Vehicle + brand photos) ────────────────────── CLOUDINARY_CLOUD_NAME=your-cloud-name CLOUDINARY_API_KEY=your-api-key -CLOUDINARY_API_SECRET=your-api-secret +CLOUDINARY_API_SECRET=placeholder NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your-cloud-name # ─── Resend (Email) ──────────────────────────────────────────── -RESEND_API_KEY=re_... +RESEND_API_KEY=placeholder EMAIL_FROM=rentaldrivego@gmail.com EMAIL_FROM_NAME=RentalDriveGo # ─── Twilio (SMS + WhatsApp) ─────────────────────────────────── TWILIO_ACCOUNT_SID=AC... -TWILIO_AUTH_TOKEN=your-twilio-auth-token +TWILIO_AUTH_TOKEN=placeholder TWILIO_PHONE_NUMBER=+1234567890 TWILIO_WHATSAPP_NUMBER=whatsapp:+14155238886 # ─── Firebase (Push notifications) ─────────────────────────── FIREBASE_PROJECT_ID=your-project-id -FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n" +FIREBASE_PRIVATE_KEY=placeholder FIREBASE_CLIENT_EMAIL=firebase-adminsdk@your-project.iam.gserviceaccount.com NEXT_PUBLIC_FIREBASE_API_KEY=your-firebase-api-key NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com @@ -84,7 +84,7 @@ DASHBOARD_URL=http://localhost:3000/dashboard # ─── Admin seed (first SUPER_ADMIN created on db:seed) ──────── ADMIN_SEED_EMAIL=admin@rentaldrivego.com -ADMIN_SEED_PASSWORD=changeme123 +ADMIN_SEED_PASSWORD=placeholder ADMIN_SEED_FIRST_NAME=Super ADMIN_SEED_LAST_NAME=Admin diff --git a/Dockerfile.production b/Dockerfile.production index e41f833..e1de287 100644 --- a/Dockerfile.production +++ b/Dockerfile.production @@ -37,12 +37,21 @@ WORKDIR /app ENV NODE_ENV=production -RUN corepack enable && corepack prepare npm@10.5.0 --activate +RUN corepack enable && corepack prepare npm@10.5.0 --activate \ + && groupadd --system --gid 10001 app \ + && useradd --system --uid 10001 --gid app --home-dir /app --shell /usr/sbin/nologin app \ + && mkdir -p /var/lib/rentaldrivego/storage/public /var/lib/rentaldrivego/storage/private /tmp/rentaldrivego \ + && chown -R app:app /app /var/lib/rentaldrivego /tmp/rentaldrivego -COPY --from=builder /app/package.json /app/package-lock.json /app/turbo.json /app/tsconfig.base.json ./ -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/apps ./apps -COPY --from=builder /app/packages ./packages +COPY --from=builder --chown=app:app /app/package.json /app/package-lock.json /app/turbo.json /app/tsconfig.base.json ./ +COPY --from=builder --chown=app:app /app/node_modules ./node_modules +COPY --from=builder --chown=app:app /app/apps ./apps +COPY --from=builder --chown=app:app /app/packages ./packages +COPY --chown=root:root docker/entrypoint.production.sh /usr/local/bin/rdg-entrypoint.sh + +RUN chmod 755 /usr/local/bin/rdg-entrypoint.sh + +ENTRYPOINT ["/usr/local/bin/rdg-entrypoint.sh"] EXPOSE 3000 3001 3002 4000 diff --git a/apps/admin/next-env.d.ts b/apps/admin/next-env.d.ts index 4f11a03..9edff1c 100644 --- a/apps/admin/next-env.d.ts +++ b/apps/admin/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/admin/next.config.js b/apps/admin/next.config.js index 0d29994..6331e40 100644 --- a/apps/admin/next.config.js +++ b/apps/admin/next.config.js @@ -1,4 +1,27 @@ /** @type {import('next').NextConfig} */ + +const securityHeaders = [ + { key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' }, + { key: 'X-Content-Type-Options', value: 'nosniff' }, + { key: 'X-Frame-Options', value: 'DENY' }, + { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' }, + { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' }, + { + key: 'Content-Security-Policy', + value: [ + "default-src 'self'", + "script-src 'self' 'unsafe-inline'", + "style-src 'self' 'unsafe-inline'", + "img-src 'self' data: blob: https:", + "font-src 'self' data:", + "connect-src 'self' https: wss:", + "frame-ancestors 'none'", + "base-uri 'self'", + "form-action 'self'", + "object-src 'none'", + ].join('; '), + }, +] const apiOrigin = (process.env.API_INTERNAL_URL ?? process.env.API_URL ?? 'http://localhost:4000').replace(/\/api\/v1\/?$/, '') const apiUrl = new URL(apiOrigin) const ADMIN_BASE_PATH = '/admin' @@ -43,6 +66,14 @@ const nextConfig = { ], }, transpilePackages: ['@rentaldrivego/types'], + async headers() { + return [ + { + source: '/:path*', + headers: securityHeaders, + }, + ] + }, async rewrites() { return [ { diff --git a/apps/admin/package.json b/apps/admin/package.json index 6817a57..3707ed9 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -10,25 +10,28 @@ "prestart": "npm run build --workspace @rentaldrivego/types", "pretype-check": "npm run build --workspace @rentaldrivego/types", "start": "next start -H 0.0.0.0 -p 3002", - "type-check": "tsc --noEmit" + "type-check": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest" }, "dependencies": { "@rentaldrivego/types": "*", - "next": "14.2.3", + "autoprefixer": "^10.4.19", + "dayjs": "^1.11.11", + "lucide-react": "^0.376.0", + "next": "^16.2.7", + "postcss": "^8.4.38", "react": "^18.3.1", "react-dom": "^18.3.1", - "tailwindcss": "^3.4.3", - "autoprefixer": "^10.4.19", - "postcss": "^8.4.38", - "zod": "^3.23.0", - "dayjs": "^1.11.11", "recharts": "^2.12.7", - "lucide-react": "^0.376.0" + "tailwindcss": "^3.4.3", + "zod": "^3.23.0" }, "devDependencies": { "@types/node": "^20.12.0", "@types/react": "^18.3.1", "@types/react-dom": "^18.3.0", - "typescript": "^5.4.0" + "typescript": "^5.4.0", + "vitest": "^1.6.0" } } diff --git a/apps/admin/src/app/auth-redirect/page.tsx b/apps/admin/src/app/auth-redirect/page.tsx index b1d9e7a..b929b2c 100644 --- a/apps/admin/src/app/auth-redirect/page.tsx +++ b/apps/admin/src/app/auth-redirect/page.tsx @@ -18,14 +18,10 @@ export default function AuthRedirectPage() { useEffect(() => { const hash = window.location.hash const params = new URLSearchParams(hash.replace(/^#/, '')) - const token = params.get('token') const next = resolveAdminNextPath(params.get('next')) - if (token) { - localStorage.setItem('admin_token', token) - // Clear the token from the URL - window.history.replaceState(null, '', window.location.pathname) - } + // Clear legacy token fragments from the URL. Admin auth now uses an HttpOnly cookie. + window.history.replaceState(null, '', window.location.pathname) window.location.replace(next) }, []) diff --git a/apps/admin/src/app/dashboard/admin-users/page.tsx b/apps/admin/src/app/dashboard/admin-users/page.tsx index 862fbb7..d2283e1 100644 --- a/apps/admin/src/app/dashboard/admin-users/page.tsx +++ b/apps/admin/src/app/dashboard/admin-users/page.tsx @@ -33,13 +33,11 @@ export default function AdminUsersPage() { const [form, setForm] = useState(EMPTY_FORM) const [saving, setSaving] = useState(false) - function getToken() { return localStorage.getItem('admin_token') ?? '' } - async function fetchAdmins() { try { const res = await fetch(`${ADMIN_API_BASE}/admin/admins`, { - headers: { Authorization: `Bearer ${getToken()}` }, cache: 'no-store', + credentials: 'include', }) const json = await res.json() if (!res.ok) throw new Error(json?.message ?? 'Failed') @@ -97,7 +95,8 @@ export default function AdminUsersPage() { const res = await fetch(`${ADMIN_API_BASE}/admin/admins${editingAdminId ? `/${editingAdminId}` : ''}`, { method: isEditing ? 'PATCH' : 'POST', - headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${getToken()}` }, + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', body: JSON.stringify(payload), }) const json = await res.json() diff --git a/apps/admin/src/app/dashboard/audit-logs/page.tsx b/apps/admin/src/app/dashboard/audit-logs/page.tsx index e163d25..6e503a2 100644 --- a/apps/admin/src/app/dashboard/audit-logs/page.tsx +++ b/apps/admin/src/app/dashboard/audit-logs/page.tsx @@ -28,9 +28,8 @@ export default function AdminAuditLogsPage() { const [filter, setFilter] = useState('') useEffect(() => { - const token = localStorage.getItem('admin_token') ?? '' fetch(`${ADMIN_API_BASE}/admin/audit-logs`, { - headers: { Authorization: `Bearer ${token}` }, + credentials: 'include', cache: 'no-store', }) .then((r) => r.json()) diff --git a/apps/admin/src/app/dashboard/billing/page.tsx b/apps/admin/src/app/dashboard/billing/page.tsx index bee3704..87a5955 100644 --- a/apps/admin/src/app/dashboard/billing/page.tsx +++ b/apps/admin/src/app/dashboard/billing/page.tsx @@ -467,14 +467,13 @@ export default function AdminBillingPage() { ) async function api(path: string, init?: RequestInit): Promise { - const token = localStorage.getItem('admin_token') const res = await fetch(`${ADMIN_API_BASE}${path}`, { ...init, headers: { 'Content-Type': 'application/json', - ...(token ? { Authorization: `Bearer ${token}` } : {}), ...(init?.headers ?? {}), }, + credentials: 'include', cache: 'no-store', }) if (res.headers.get('content-type')?.includes('application/pdf')) { diff --git a/apps/admin/src/app/dashboard/companies/[id]/page.tsx b/apps/admin/src/app/dashboard/companies/[id]/page.tsx index d0ee90a..fc8822e 100644 --- a/apps/admin/src/app/dashboard/companies/[id]/page.tsx +++ b/apps/admin/src/app/dashboard/companies/[id]/page.tsx @@ -207,10 +207,6 @@ const STATUS_COLORS: Record = { const INPUT_CLASS = 'mt-1 w-full rounded-xl border border-zinc-700 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 outline-none focus:border-emerald-500' const LABEL_CLASS = 'text-xs font-medium uppercase tracking-wide text-zinc-500' -function getToken() { - return localStorage.getItem('admin_token') ?? '' -} - function toDateInput(value: string | null | undefined) { return value ? new Date(value).toISOString().slice(0, 10) : '' } @@ -324,11 +320,10 @@ export default function AdminCompanyDetailPage() { const [deleteConfirm, setDeleteConfirm] = useState(false) async function fetchData() { - const headers = { Authorization: `Bearer ${getToken()}` } try { const [cRes, aRes] = await Promise.all([ - fetch(`${ADMIN_API_BASE}/admin/companies/${id}`, { headers, cache: 'no-store' }), - fetch(`${ADMIN_API_BASE}/admin/audit-logs?entityId=${id}&pageSize=10`, { headers, cache: 'no-store' }), + fetch(`${ADMIN_API_BASE}/admin/companies/${id}`, { cache: 'no-store', credentials: 'include' }), + fetch(`${ADMIN_API_BASE}/admin/audit-logs?entityId=${id}&pageSize=10`, { cache: 'no-store', credentials: 'include' }), ]) const cJson = await cRes.json() const aJson = await aRes.json() @@ -339,7 +334,7 @@ export default function AdminCompanyDetailPage() { const subId = cJson.data?.subscription?.id if (subId) { - const eRes = await fetch(`${ADMIN_API_BASE}/admin/subscriptions/${subId}/events`, { headers, cache: 'no-store' }) + const eRes = await fetch(`${ADMIN_API_BASE}/admin/subscriptions/${subId}/events`, { cache: 'no-store', credentials: 'include' }) const eJson = await eRes.json() setSubEvents(Array.isArray(eJson.data) ? eJson.data : []) } @@ -365,7 +360,8 @@ export default function AdminCompanyDetailPage() { try { const res = await fetch(`${ADMIN_API_BASE}/admin/companies/${id}`, { method: 'PATCH', - headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${getToken()}` }, + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', body: JSON.stringify({ company: { name: form.company.name, @@ -469,7 +465,8 @@ export default function AdminCompanyDetailPage() { try { const res = await fetch(`${ADMIN_API_BASE}/admin/subscriptions/${company.subscription.id}/${action}`, { method: 'POST', - headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${getToken()}` }, + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', body: JSON.stringify({ reason: subOverrideReason }), }) const json = await res.json() @@ -490,7 +487,8 @@ export default function AdminCompanyDetailPage() { try { const res = await fetch(`${ADMIN_API_BASE}/admin/companies/${id}/status`, { method: 'PATCH', - headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${getToken()}` }, + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', body: JSON.stringify({ status }), }) const json = await res.json() @@ -508,7 +506,7 @@ export default function AdminCompanyDetailPage() { try { const res = await fetch(`${ADMIN_API_BASE}/admin/companies/${id}`, { method: 'DELETE', - headers: { Authorization: `Bearer ${getToken()}` }, + credentials: 'include', }) const json = await res.json() if (!res.ok) throw new Error(json?.message ?? 'Delete failed') diff --git a/apps/admin/src/app/dashboard/companies/page.tsx b/apps/admin/src/app/dashboard/companies/page.tsx index 75d85b1..0f0963e 100644 --- a/apps/admin/src/app/dashboard/companies/page.tsx +++ b/apps/admin/src/app/dashboard/companies/page.tsx @@ -84,10 +84,9 @@ export default function AdminCompaniesPage() { const [actioning, setActioning] = useState(null) async function fetchCompanies() { - const token = localStorage.getItem('admin_token') try { const res = await fetch(`${ADMIN_API_BASE}/admin/companies`, { - headers: token ? { Authorization: `Bearer ${token}` } : {}, + credentials: 'include', cache: 'no-store', }) const json = await res.json() @@ -111,11 +110,11 @@ export default function AdminCompaniesPage() { async function changeStatus(id: string, status: string) { setActioning(id) - const token = localStorage.getItem('admin_token') try { const res = await fetch(`${ADMIN_API_BASE}/admin/companies/${id}/status`, { method: 'PATCH', - headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) }, + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', body: JSON.stringify({ status }), }) if (!res.ok) throw new Error('Action failed') diff --git a/apps/admin/src/app/dashboard/containers/page.tsx b/apps/admin/src/app/dashboard/containers/page.tsx index af7434e..163174b 100644 --- a/apps/admin/src/app/dashboard/containers/page.tsx +++ b/apps/admin/src/app/dashboard/containers/page.tsx @@ -25,8 +25,7 @@ interface CompanyContainer { } function authHeaders() { - const token = typeof window !== 'undefined' ? localStorage.getItem('admin_token') : '' - return { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` } + return { 'Content-Type': 'application/json' } } function StatusBadge({ status }: { status: ContainerStatus }) { @@ -57,7 +56,7 @@ function LogsModal({ companyId, companyName, onClose }: { companyId: string; com const fetchLogs = useCallback(async (lines: number) => { setLoading(true) try { - const res = await fetch(`${ADMIN_API_BASE}/admin/containers/${companyId}/logs?tail=${lines}`, { headers: authHeaders() }) + const res = await fetch(`${ADMIN_API_BASE}/admin/containers/${companyId}/logs?tail=${lines}`, { headers: authHeaders(), credentials: 'include' }) const json = await res.json() setLogs(json.data?.logs ?? '') } catch { @@ -126,7 +125,7 @@ export default function ContainersPage() { const fetchContainers = useCallback(async () => { try { - const res = await fetch(`${ADMIN_API_BASE}/admin/containers`, { headers: authHeaders() }) + const res = await fetch(`${ADMIN_API_BASE}/admin/containers`, { headers: authHeaders(), credentials: 'include' }) const json = await res.json().catch(() => null) if (!res.ok) { throw new Error(json?.message ?? 'Failed to load containers.') @@ -151,7 +150,7 @@ export default function ContainersPage() { setProvisioning(true) setProvisionResults(null) try { - const res = await fetch(`${ADMIN_API_BASE}/admin/containers/provision-all`, { method: 'POST', headers: authHeaders() }) + const res = await fetch(`${ADMIN_API_BASE}/admin/containers/provision-all`, { method: 'POST', headers: authHeaders(), credentials: 'include' }) const json = await res.json().catch(() => null) if (!res.ok) throw new Error(json?.message ?? 'Provisioning failed.') setProvisionResults(json.data?.results ?? []) @@ -172,7 +171,7 @@ export default function ContainersPage() { action === 'remove' ? `${ADMIN_API_BASE}/admin/containers/${companyId}` : `${ADMIN_API_BASE}/admin/containers/${companyId}/${action}` - const res = await fetch(url, { method, headers: authHeaders() }) + const res = await fetch(url, { method, headers: authHeaders(), credentials: 'include' }) const json = await res.json().catch(() => null) if (!res.ok) { throw new Error(json?.message ?? `Failed to ${action} container.`) diff --git a/apps/admin/src/app/dashboard/layout.tsx b/apps/admin/src/app/dashboard/layout.tsx index 258eb94..513b5e4 100644 --- a/apps/admin/src/app/dashboard/layout.tsx +++ b/apps/admin/src/app/dashboard/layout.tsx @@ -38,16 +38,30 @@ export default function AdminDashboardLayout({ children }: { children: React.Rea const [ready, setReady] = useState(false) useEffect(() => { - const token = localStorage.getItem('admin_token') - if (!token) { - window.location.replace(buildUnifiedLoginUrl(pathname)) - } else { - setReady(true) + let cancelled = false + + fetch(`${process.env.NEXT_PUBLIC_API_URL ?? '/admin/api/v1'}/admin/auth/me`, { + credentials: 'include', + }) + .then((response) => { + if (cancelled) return + if (response.ok) { + setReady(true) + } else { + window.location.replace(buildUnifiedLoginUrl(pathname)) + } + }) + .catch(() => { + if (!cancelled) window.location.replace(buildUnifiedLoginUrl(pathname)) + }) + + return () => { + cancelled = true } }, [pathname]) function handleLogout() { - localStorage.removeItem('admin_token') + void fetch('/admin/api/v1/admin/auth/logout', { method: 'POST', credentials: 'include' }) window.location.href = buildUnifiedLoginUrl('/dashboard') } diff --git a/apps/admin/src/app/dashboard/menu-management/page.tsx b/apps/admin/src/app/dashboard/menu-management/page.tsx index 65235fa..c8961fc 100644 --- a/apps/admin/src/app/dashboard/menu-management/page.tsx +++ b/apps/admin/src/app/dashboard/menu-management/page.tsx @@ -86,20 +86,15 @@ const PLANS: Plan[] = ['STARTER', 'GROWTH', 'PRO'] const ITEM_TYPES: MenuItemType[] = ['INTERNAL_PAGE', 'EXTERNAL_LINK', 'PARENT_MENU', 'SECTION_LABEL', 'DIVIDER'] const INPUT = - 'mt-1 w-full rounded-xl border border-stone-200 bg-white px-3 py-2 text-sm text-blue-950 outline-none focus:border-orange-500 dark:border-blue-800 dark:bg-[#07101e] dark:text-white' -const LABEL = 'text-xs font-semibold uppercase tracking-[0.14em] text-stone-500 dark:text-slate-400' - -function getToken() { - return typeof window === 'undefined' ? '' : localStorage.getItem('admin_token') ?? '' -} + 'mt-1 w-full rounded-xl border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 outline-none focus:ring-2 focus:ring-orange-500' +const LABEL = 'text-xs font-medium uppercase tracking-wider text-zinc-500' async function api(path: string, options?: RequestInit): Promise { - const token = getToken() const res = await fetch(`${ADMIN_API_BASE}${path}`, { ...options, + credentials: 'include', headers: { 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, ...(options?.headers ?? {}), }, }) @@ -148,13 +143,13 @@ function formFromItem(item: MenuItem): FormState { function chip(text: string, tone = 'default') { const toneClass = tone === 'success' - ? 'border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-900/60 dark:bg-emerald-900/20 dark:text-emerald-300' + ? 'bg-emerald-950/40 text-emerald-400' : tone === 'warn' - ? 'border-orange-200 bg-orange-50 text-orange-700 dark:border-orange-900/60 dark:bg-orange-900/20 dark:text-orange-300' - : 'border-stone-200 bg-stone-50 text-stone-700 dark:border-blue-800 dark:bg-[#07101e] dark:text-slate-300' + ? 'bg-orange-950/40 text-orange-400' + : 'bg-zinc-800 text-zinc-300' return ( - + {text} ) @@ -347,8 +342,8 @@ export default function MenuManagementPage() { if (loading) { return ( -
-
+
+
Loading menu management…
@@ -356,20 +351,20 @@ export default function MenuManagementPage() { } return ( -
-
+
+
-

Platform Navigation

-

Menu Management

-

+

Platform

+

Menu Management

+

Control company dashboard navigation by plan, role, and company-specific exceptions from one admin surface.

@@ -378,62 +373,62 @@ export default function MenuManagementPage() { {(error || success) && (
- {error &&
{error}
} - {success &&
{success}
} + {error &&
{error}
} + {success &&
{success}
}
)}
-
+
-

Menu items

-

{items.length} configured items

+

Menu items

+

{items.length} configured items

-
+
- - - - - - + + + + + + - + {items.map((item) => ( - - + - - + - -
ItemTypeAssignmentsStatusActions
ItemTypeAssignmentsStatusActions
+
- {item.label} + {item.label} {item.isRequired && chip('Required', 'warn')} {item.systemKey && chip(item.systemKey)}
-

+

{item.routeOrUrl || 'No route'} • order {item.displayOrder} {item.parent ? ` • child of ${item.parent.label}` : ''}

{item.itemType} + {item.itemType}
{item.subscriptionAssignments.length > 0 ? item.subscriptionAssignments.map((assignment) => ( - + {assignment.plan} )) - : No plan assignments} + : No plan assignments}
{item.companyAssignments.slice(0, 3).map((assignment) => ( - + {assignment.company.name} ))} @@ -441,25 +436,25 @@ export default function MenuManagementPage() {
{item.roleVisibilities.map((entry) => ( - + {entry.role} ))}
+ {item.isActive ? chip('Active', 'success') : chip('Inactive')} +
- - -
@@ -471,16 +466,16 @@ export default function MenuManagementPage() { -
-
+
+
-

{editingId ? 'Edit menu item' : 'New menu item'}

-

+

{editingId ? 'Edit menu item' : 'New menu item'}

+

Configure menu metadata, role visibility, plan defaults, and company-specific overrides.

{editingId && ( - )} @@ -537,18 +532,18 @@ export default function MenuManagementPage() {
-
-