# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview **Xpeditis** is a B2B SaaS maritime freight booking platform. Freight forwarders search and compare real-time shipping rates, book containers, and manage shipments. Monorepo with NestJS 10 backend (Hexagonal Architecture) and Next.js 14 frontend. ## Development Commands All commands run from repo root unless noted otherwise. ```bash # Infrastructure (PostgreSQL 15 + Redis 7 + MinIO) docker-compose up -d # Install all dependencies npm run install:all # Environment setup (required on first run) cp apps/backend/.env.example apps/backend/.env cp apps/frontend/.env.example apps/frontend/.env # Database migrations (from apps/backend/) cd apps/backend && npm run migration:run # Development servers npm run backend:dev # http://localhost:4000, Swagger: /api/docs npm run frontend:dev # http://localhost:3000 ``` ### Testing ```bash # Backend (from apps/backend/) npm test # Unit tests (Jest) npm test -- booking.entity.spec.ts # Single file npm run test:cov # With coverage npm run test:integration # Integration tests (needs DB/Redis, 30s timeout) npm run test:e2e # E2E tests # Frontend (from apps/frontend/) npm test npm run test:e2e # Playwright (chromium, firefox, webkit + mobile) # From root npm run backend:test npm run frontend:test ``` Backend test config is in `apps/backend/package.json` (Jest). Integration test config: `apps/backend/jest-integration.json` (covers infrastructure layer, setup in `test/setup-integration.ts`). Frontend E2E config: `apps/frontend/playwright.config.ts`. ### Linting, Formatting & Type Checking ```bash npm run backend:lint # ESLint backend npm run frontend:lint # ESLint frontend npm run format # Prettier (all files) npm run format:check # Check formatting # From apps/frontend/ npm run type-check # TypeScript checking (frontend only) ``` ### Database Migrations ```bash cd apps/backend npm run migration:generate -- src/infrastructure/persistence/typeorm/migrations/MigrationName npm run migration:run npm run migration:revert ``` ### Build ```bash npm run backend:build # NestJS build with tsc-alias for path resolution npm run frontend:build # Next.js production build (standalone output) ``` ## Local Infrastructure Docker-compose defaults (no `.env` changes needed for local dev): - **PostgreSQL**: `xpeditis:xpeditis_dev_password@localhost:5432/xpeditis_dev` - **Redis**: password `xpeditis_redis_password`, port 6379 - **MinIO** (S3-compatible storage): `minioadmin:minioadmin`, API port 9000, console port 9001 Frontend env var: `NEXT_PUBLIC_API_URL` (defaults to `http://localhost:4000`) — configured in `next.config.js`. ## Architecture ### Hexagonal Architecture (Backend) ``` apps/backend/src/ ├── domain/ # CORE - Pure TypeScript, NO framework imports │ ├── entities/ # Booking, RateQuote, Carrier, Port, Container, Notification, Webhook, AuditLog │ ├── value-objects/ # Money, Email, BookingNumber, BookingStatus, PortCode, ContainerType, Volume, etc. │ ├── services/ # Pure domain services (csv-rate-price-calculator) │ ├── ports/ │ │ ├── in/ # Use case interfaces with execute() method │ │ └── out/ # Repository/SPI interfaces (token constants like BOOKING_REPOSITORY = 'BookingRepository') │ └── exceptions/ # Domain-specific exceptions ├── application/ # Controllers, DTOs (class-validator), Guards, Decorators, Mappers │ ├── [feature]/ # Feature modules grouped by domain (auth/, bookings/, rates/, etc.) │ ├── controllers/ # REST controllers (also nested under feature folders) │ ├── services/ # Application services (audit, notification, webhook, booking-automation, export, etc.) │ ├── gateways/ # WebSocket gateways (notifications.gateway.ts via Socket.IO) │ ├── guards/ # JwtAuthGuard, RolesGuard, CustomThrottlerGuard │ ├── decorators/ # @Public(), @Roles(), @CurrentUser() │ ├── dto/ # Request/response DTOs with class-validator │ ├── mappers/ # Domain ↔ DTO mappers │ └── interceptors/ # PerformanceMonitoringInterceptor └── infrastructure/ # TypeORM entities/repos/mappers, Redis cache, carrier APIs, MinIO/S3, email (MJML+Nodemailer), Stripe, Sentry ``` **Critical dependency rules**: - Domain layer: zero imports from NestJS, TypeORM, Redis, or any framework - Dependencies flow inward only: Infrastructure → Application → Domain - Path aliases: `@domain/*`, `@application/*`, `@infrastructure/*` (defined in `apps/backend/tsconfig.json`) - Domain tests run without NestJS TestingModule - Backend has strict TypeScript: `strict: true`, `strictNullChecks: true` (but `strictPropertyInitialization: false`) - Env vars validated at startup via Joi schema in `app.module.ts` — required vars include DATABASE_*, REDIS_*, JWT_SECRET, SMTP_* ### NestJS Modules (app.module.ts) Global guards: JwtAuthGuard (all routes protected by default), CustomThrottlerGuard. Feature modules: Auth, Rates, Ports, Bookings, CsvBookings, Organizations, Users, Dashboard, Audit, Notifications, Webhooks, GDPR, Admin, Subscriptions. Infrastructure modules: CacheModule, CarrierModule, SecurityModule, CsvRateModule, StripeModule, PdfModule, StorageModule, EmailModule. Swagger plugin enabled in `nest-cli.json` — DTOs auto-documented. Logging via `nestjs-pino` (pino-pretty in dev). ### Frontend (Next.js 14 App Router) ``` apps/frontend/ ├── app/ # App Router pages (root-level) │ ├── dashboard/ # Protected routes (bookings, admin, settings, wiki, search) │ ├── carrier/ # Carrier portal (magic link auth — accept/reject/documents) │ ├── booking/ # Booking confirmation/rejection flows │ └── [auth pages] # login, register, forgot-password, verify-email └── src/ ├── app/ # Additional app pages (e.g. rates/csv-search) ├── components/ # React components (ui/, layout/, bookings/, admin/, rate-search/, organization/) ├── hooks/ # useBookings, useNotifications, useCsvRateSearch, useCompanies, useFilterOptions ├── lib/ │ ├── api/ # Fetch-based API client with auto token refresh (client.ts + per-module files) │ ├── context/ # Auth context, cookie context │ ├── providers/ # QueryProvider (TanStack Query / React Query) │ └── fonts.ts # Manrope (headings) + Montserrat (body) ├── types/ # TypeScript type definitions ├── utils/ # Export utilities (Excel, PDF) └── legacy-pages/ # Archived page components (BookingsManagement, CarrierManagement, CarrierMonitoring) ``` Path aliases: `@/*` → `./src/*`, `@/components/*`, `@/lib/*`, `@/app/*` → `./app/*`, `@/types/*`, `@/hooks/*`, `@/utils/*` **Note**: Frontend tsconfig has `strict: false`, `noImplicitAny: false`, `strictNullChecks: false` (unlike backend which is strict). Uses TanStack Query (React Query) for server state — wrap new data fetching in hooks, not bare `fetch` calls. ### Brand Design Colors: Navy `#10183A` (primary), Turquoise `#34CCCD` (accent), Green `#067224` (success), Gray `#F2F2F2`. Fonts: Manrope (headings), Montserrat (body). Landing page is in French. ## Key Patterns ### Entity Pattern (Domain) Private constructor + static `create()` factory. Immutable — mutation methods return new instances. Some entities also have `fromPersistence()` for reconstitution and `toObject()` for serialization. ```typescript export class Booking { private readonly props: BookingProps; static create(props: Omit): Booking { ... } updateStatus(newStatus: BookingStatus): Booking { // Returns new instance return new Booking({ ...this.props, status: newStatus }); } } ``` ### Value Object Pattern Immutable, self-validating via static `create()`. E.g. `Money` supports USD, EUR, GBP, CNY, JPY with arithmetic and formatting methods. ### Repository Pattern - Interface in `domain/ports/out/` with token constant (e.g. `BOOKING_REPOSITORY = 'BookingRepository'`) - Implementation in `infrastructure/persistence/typeorm/repositories/` - ORM entities: `infrastructure/persistence/typeorm/entities/*.orm-entity.ts` - Separate mapper classes (`infrastructure/persistence/typeorm/mappers/`) with static `toOrm()`, `toDomain()`, `toDomainMany()` methods ### Frontend API Client Custom Fetch wrapper in `src/lib/api/client.ts` — exports `get()`, `post()`, `patch()`, `del()`, `upload()`, `download()`. Auto-refreshes JWT on 401. Tokens stored in localStorage **and synced to cookies** (`accessToken` cookie) so Next.js middleware can read them server-side. Per-module files (auth.ts, bookings.ts, rates.ts, etc.) import from client. ### Route Protection (Middleware) `apps/frontend/middleware.ts` checks the `accessToken` cookie to protect routes. Public paths are defined in two lists: - `exactPublicPaths`: exact matches (e.g. `/`) - `prefixPublicPaths`: prefix matches including sub-paths (e.g. `/login`, `/carrier`, `/about`, etc.) All other routes redirect to `/login?redirect=` when the cookie is absent. ### Application Decorators - `@Public()` — skip JWT auth - `@Roles()` — role-based access control - `@CurrentUser()` — inject authenticated user ### Carrier Connectors Five carrier connectors (Maersk, MSC, CMA CGM, Hapag-Lloyd, ONE) extending `base-carrier.connector.ts`, each with request/response mappers. Circuit breaker via `opossum` (5s timeout). ### Caching Redis with 15-min TTL for rate quotes. Key format: `rate:{origin}:{destination}:{containerType}`. ## Business Rules - Booking number format: `WCM-YYYY-XXXXXX` - Booking status flow: draft → confirmed → shipped → delivered - Rate quotes expire after 15 minutes - Multi-currency: USD, EUR, GBP, CNY, JPY - RBAC Roles: ADMIN, MANAGER, USER, VIEWER, CARRIER - JWT: access token 15min, refresh token 7d - Password hashing: Argon2 - OAuth providers: Google, Microsoft (configured via passport strategies) ### Carrier Portal Workflow 1. Admin creates CSV booking → assigns carrier 2. Email with magic link sent (1-hour expiry) 3. Carrier auto-login → accept/reject booking 4. Activity logged in `carrier_activities` table ## Common Pitfalls - Never import NestJS/TypeORM in domain layer - Never use `any` type in backend (strict mode enabled) - Never modify applied migrations — create new ones - Always validate DTOs with `class-validator` decorators - Always create separate mappers for Domain ↔ ORM conversions - ORM entity files must match pattern `*.orm-entity.{ts,js}` (auto-discovered by data-source) - Migration files must be in `infrastructure/persistence/typeorm/migrations/` - Database synchronize is hard-coded to `false` — always use migrations ## Adding a New Feature 1. **Domain Entity** → `domain/entities/*.entity.ts` (pure TS, unit tests) 2. **Value Objects** → `domain/value-objects/*.vo.ts` (immutable) 3. **In Port (Use Case)** → `domain/ports/in/*.use-case.ts` (interface with `execute()`) 4. **Out Port (Repository)** → `domain/ports/out/*.repository.ts` (with token constant) 5. **ORM Entity** → `infrastructure/persistence/typeorm/entities/*.orm-entity.ts` 6. **Migration** → `npm run migration:generate -- src/infrastructure/persistence/typeorm/migrations/MigrationName` 7. **Repository Impl** → `infrastructure/persistence/typeorm/repositories/` 8. **Mapper** → `infrastructure/persistence/typeorm/mappers/` (static toOrm/toDomain/toDomainMany) 9. **DTOs** → `application/dto/` (with class-validator decorators) 10. **Controller** → `application/controllers/` (with Swagger decorators) 11. **Module** → Register repository + use-case providers, import in `app.module.ts` ## Documentation - API Docs: http://localhost:4000/api/docs (Swagger, when running) - Setup guide: `docs/installation/START-HERE.md` - Carrier Portal API: `apps/backend/docs/CARRIER_PORTAL_API.md` - Full docs index: `docs/README.md` - Development roadmap: `TODO.md` - Infrastructure configs (CI/CD, Docker): `infra/`