xpeditis2.0/CLAUDE.md
2026-03-26 18:08:28 +01:00

12 KiB

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.

# 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

# 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

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

cd apps/backend
npm run migration:generate -- src/infrastructure/persistence/typeorm/migrations/MigrationName
npm run migration:run
npm run migration:revert

Build

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.

export class Booking {
  private readonly props: BookingProps;
  static create(props: Omit<BookingProps, 'bookingNumber' | 'status'>): 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=<pathname> 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 Entitydomain/entities/*.entity.ts (pure TS, unit tests)
  2. Value Objectsdomain/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 Entityinfrastructure/persistence/typeorm/entities/*.orm-entity.ts
  6. Migrationnpm run migration:generate -- src/infrastructure/persistence/typeorm/migrations/MigrationName
  7. Repository Implinfrastructure/persistence/typeorm/repositories/
  8. Mapperinfrastructure/persistence/typeorm/mappers/ (static toOrm/toDomain/toDomainMany)
  9. DTOsapplication/dto/ (with class-validator decorators)
  10. Controllerapplication/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/