xpeditis2.0/CLAUDE.md
David-Henri ARNAUD e863399bb2
Some checks failed
CI / Lint & Format Check (push) Failing after 1m11s
CI / Test Backend (push) Failing after 1m32s
CI / Build Backend (push) Has been skipped
Security Audit / npm audit (push) Failing after 5s
Security Audit / Dependency Review (push) Has been skipped
CI / Test Frontend (push) Failing after 29s
CI / Build Frontend (push) Has been skipped
first commit
2025-10-07 18:39:32 +02:00

22 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 and management platform (maritime equivalent of WebCargo). The platform allows freight forwarders to search and compare real-time shipping rates, book containers online, and manage shipments from a centralized dashboard.

MVP Goal: Deliver a minimal but professional product capable of handling 50-100 bookings/month for 10-20 early adopter freight forwarders within 4-6 months.

Architecture

Hexagonal Architecture (Ports & Adapters)

The codebase follows hexagonal architecture principles with clear separation of concerns:

  • API Layer (External Adapters): Controllers, validation, auth middleware
  • Application Layer (Ports): Use cases (searchRates, createBooking, confirmBooking)
  • Domain Layer (Core): Entities (Booking, RateQuote, Carrier, Organization, User)
  • Infrastructure Adapters: DB repositories, carrier connectors, email services, storage, Redis cache

Tech Stack

  • Frontend: Next.js (TypeScript) with SSR/ISR
  • Backend: Node.js API-first (OpenAPI spec)
  • Database: PostgreSQL (TypeORM or Prisma)
  • Cache: Redis (15 min TTL for spot rates)
  • Storage: S3-compatible (AWS S3 or MinIO)
  • Auth: OAuth2 + JWT (access 15min, refresh 7 days)

Project Structure (Planned Monorepo)

apps/
  frontend/     # Next.js application
  backend/      # Node.js API
libs/
  domain/       # Core domain entities and business logic
infra/          # Infrastructure configuration

Core Domain Entities

  • Organization: { id, name, type, scac, address, logo_url, documents[] }
  • User: { id, org_id, role, email, pwd_hash, totp_secret }
  • RateQuote: { id, origin, destination, carrier_id, price_currency, price_value, surcharges[], etd, eta, transit_days, route, availability }
  • Booking: { id, booking_number, user_id, org_id, rate_quote_id, shipper, consignee, containers[], status, created_at, updated_at }
  • Container: { id, booking_id, type, container_number, vgm, temp, seal_number }

Key API Endpoints

  • POST /api/v1/rates/search: Search shipping rates with origin/destination/container specs
  • Response includes carrier, pricing, surcharges, ETD/ETA, transit time, route, CO2 emissions
  • Cache TTL: 15 minutes (Redis)
  • Timeout: 5 seconds per carrier API

Booking Management

  • POST /api/v1/bookings: Create booking (multi-step workflow)
  • GET /api/v1/bookings/:id: Get booking details
  • Booking number format: WCM-YYYY-XXXXXX (6 alphanumeric chars)

Authentication

  • /auth/register: Email + password (≥12 chars)
  • /auth/login: JWT-based login + OAuth2 (Google Workspace, Microsoft 365)
  • /auth/logout: Session termination
  • /auth/refresh: Token refresh

Critical Integration Points

Carrier Connectors (MVP Priority)

Implement connectors for 3-5 carriers using Strategy pattern:

  • Maersk (required)
  • MSC
  • CMA CGM
  • Hapag-Lloyd
  • ONE

Each connector must:

  • Normalize data to internal schema
  • Implement retry logic and circuit breakers
  • Respect rate limiting
  • Log detailed metrics
  • Respond within 5s or fallback gracefully

Cache Strategy

  • Preload top 100 trade lanes on startup
  • 15-minute TTL for spot rates
  • Cache hit target: >90% for common routes

Security Requirements

  • TLS 1.2+ for all traffic
  • Password hashing: Argon2id or bcrypt (≥12 rounds)
  • OWASP Top 10 protection (rate limiting, input validation, CSP headers)
  • Audit logs for sensitive actions
  • S3 ACLs for compliance documents
  • Optional TOTP 2FA

RBAC Roles

  • Admin: Full system access
  • Manager: Manage bookings and users within organization
  • User: Create and view bookings
  • Viewer: Read-only access

Performance Targets

  • Rate search: <2s for 90% of requests (with cache)
  • Dashboard load: <1s for up to 5k bookings
  • Carrier API timeout: 5s
  • Email confirmation: Send within 3s of booking
  • Session auto-logout: 2h inactivity

Development Workflow

Testing Requirements

  • Unit tests: Domain logic and use cases
  • Integration tests: Carrier connectors and DB repositories
  • E2E tests: Complete booking workflow (happy path + 3 common error scenarios)

Email & Notifications

  • Templates: MJML format
  • Booking confirmation email on successful booking
  • Push notifications (if mobile app)

Document Generation

  • PDF booking confirmations
  • Excel/PDF export for rate search results

Data Requirements

  • Port autocomplete: 10k+ ports (IATA/UN LOCODE)
  • Multi-currency support: USD, EUR
  • Hazmat support: IMO class validation
  • Container types: 20', 40', 40'HC, etc.

MVP Roadmap (4-6 months)

Sprint 0 (2 weeks): Repo setup, infrastructure, OpenAPI skeleton, ports/adapters scaffolding

Phase 1 (6-8 weeks): Rate search API + UI, Redis cache, 1-2 carrier connectors, basic auth

Phase 2 (6-8 weeks): Booking workflow, email templates, dashboard, RBAC, organizations

Phase 3 (4-6 weeks): Additional carrier integrations, exports, E2E tests, monitoring, security hardening

Go-to-market (2 weeks): Early adopter onboarding, support, KPI tracking

Important Constraints

  • Pre-fetch top 100 trade lanes on application startup
  • All carrier API calls must have circuit breakers
  • Booking workflow: ≤4 steps maximum
  • Session timeout: 2 hours of inactivity
  • Rate search pagination: >20 results
  • SLA: 95% of rate searches <1s (including cache)

Business KPIs to Track

  • Active users (DAU/MAU)
  • Bookings per month
  • Search-to-booking conversion rate (target ≥3%)
  • Average time to create booking
  • Carrier API error rates
  • Cache hit ratio
  • Customer retention at 3 months

Backend Hexagonal Architecture Guidelines (Node.js/TypeScript)

Phase 1: Business Domain Analysis

Domain Identification

  • Primary business domain: Maritime freight booking platform
  • Core entities: Organization, User, RateQuote, Booking, Container, Carrier
  • Business rules:
    • Rate quotes expire after 15 minutes (Redis cache)
    • Bookings must validate container availability in real-time
    • Multi-step booking workflow (≤4 steps)
    • RBAC enforcement for all operations
    • Carrier API timeout: 5 seconds with fallback
  • Use cases: searchRates, createBooking, confirmBooking, manageOrganizations, authenticateUser

Integration Requirements

  • External actors: Freight forwarders (users), carriers (API integrations)
  • External services: PostgreSQL, Redis, S3, Email (MJML templates), Carrier APIs (Maersk, MSC, CMA CGM, etc.)
  • Input interfaces: REST API (OpenAPI), OAuth2 callbacks
  • Output interfaces: Database persistence, email notifications, carrier API calls, S3 document storage

Phase 2: Architectural Design

Module Structure

backend/
├── src/
│   ├── domain/                    # Pure business logic (NO external dependencies)
│   │   ├── entities/
│   │   │   ├── organization.entity.ts
│   │   │   ├── user.entity.ts
│   │   │   ├── rate-quote.entity.ts
│   │   │   ├── booking.entity.ts
│   │   │   ├── container.entity.ts
│   │   │   ├── carrier.entity.ts
│   │   │   └── index.ts
│   │   ├── value-objects/
│   │   │   ├── email.vo.ts
│   │   │   ├── booking-number.vo.ts
│   │   │   ├── port-code.vo.ts
│   │   │   └── index.ts
│   │   ├── services/
│   │   │   ├── rate-search.service.ts
│   │   │   ├── booking.service.ts
│   │   │   ├── user.service.ts
│   │   │   └── index.ts
│   │   ├── ports/
│   │   │   ├── in/                # API Ports (use cases)
│   │   │   │   ├── search-rates.port.ts
│   │   │   │   ├── create-booking.port.ts
│   │   │   │   ├── manage-user.port.ts
│   │   │   │   └── index.ts
│   │   │   └── out/               # SPI Ports (infrastructure interfaces)
│   │   │       ├── rate-quote.repository.ts
│   │   │       ├── booking.repository.ts
│   │   │       ├── user.repository.ts
│   │   │       ├── carrier-connector.port.ts
│   │   │       ├── cache.port.ts
│   │   │       ├── email.port.ts
│   │   │       └── index.ts
│   │   └── exceptions/
│   │       ├── booking-not-found.exception.ts
│   │       ├── invalid-rate-quote.exception.ts
│   │       ├── carrier-timeout.exception.ts
│   │       └── index.ts
│   │
│   ├── application/               # Controllers and DTOs (depends ONLY on domain)
│   │   ├── controllers/
│   │   │   ├── rates.controller.ts
│   │   │   ├── bookings.controller.ts
│   │   │   ├── auth.controller.ts
│   │   │   └── index.ts
│   │   ├── dto/
│   │   │   ├── rate-search.dto.ts
│   │   │   ├── create-booking.dto.ts
│   │   │   ├── booking-response.dto.ts
│   │   │   └── index.ts
│   │   ├── mappers/
│   │   │   ├── rate-quote.mapper.ts
│   │   │   ├── booking.mapper.ts
│   │   │   └── index.ts
│   │   └── config/
│   │       ├── validation.config.ts
│   │       └── swagger.config.ts
│   │
│   ├── infrastructure/            # All external integrations (depends ONLY on domain)
│   │   ├── persistence/
│   │   │   ├── typeorm/
│   │   │   │   ├── entities/
│   │   │   │   │   ├── organization.orm-entity.ts
│   │   │   │   │   ├── user.orm-entity.ts
│   │   │   │   │   ├── booking.orm-entity.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── repositories/
│   │   │   │   │   ├── typeorm-booking.repository.ts
│   │   │   │   │   ├── typeorm-user.repository.ts
│   │   │   │   │   └── index.ts
│   │   │   │   └── mappers/
│   │   │   │       ├── booking-orm.mapper.ts
│   │   │   │       └── index.ts
│   │   │   └── database.module.ts
│   │   ├── cache/
│   │   │   ├── redis-cache.adapter.ts
│   │   │   └── cache.module.ts
│   │   ├── carriers/
│   │   │   ├── maersk/
│   │   │   │   ├── maersk.connector.ts
│   │   │   │   ├── maersk.mapper.ts
│   │   │   │   └── maersk.types.ts
│   │   │   ├── msc/
│   │   │   ├── cma-cgm/
│   │   │   └── carrier.module.ts
│   │   ├── email/
│   │   │   ├── mjml-email.adapter.ts
│   │   │   └── email.module.ts
│   │   ├── storage/
│   │   │   ├── s3-storage.adapter.ts
│   │   │   └── storage.module.ts
│   │   └── config/
│   │       ├── database.config.ts
│   │       ├── redis.config.ts
│   │       └── jwt.config.ts
│   │
│   ├── main.ts                    # Application entry point
│   └── app.module.ts              # Root module (NestJS)
│
├── test/
│   ├── unit/
│   ├── integration/
│   └── e2e/
│
├── package.json
├── tsconfig.json
├── jest.config.js
└── .env.example

Port Definitions

API Ports (domain/ports/in/) - Exposed by domain:

  • SearchRatesPort: Interface for searching shipping rates
  • CreateBookingPort: Interface for creating bookings
  • ManageUserPort: Interface for user management
  • AuthenticatePort: Interface for authentication flows

SPI Ports (domain/ports/out/) - Required by domain:

  • RateQuoteRepository: Persistence interface for rate quotes
  • BookingRepository: Persistence interface for bookings
  • UserRepository: Persistence interface for users
  • CarrierConnectorPort: Interface for carrier API integrations
  • CachePort: Interface for caching (Redis)
  • EmailPort: Interface for sending emails
  • StoragePort: Interface for S3 document storage

Adapter Design

Driving Adapters (Input):

  • REST controllers (NestJS @Controller)
  • GraphQL resolvers (future)
  • CLI commands (future)

Driven Adapters (Output):

  • TypeORM repositories implementing repository ports
  • Carrier connectors (Maersk, MSC, etc.) implementing CarrierConnectorPort
  • Redis adapter implementing CachePort
  • MJML email adapter implementing EmailPort
  • S3 adapter implementing StoragePort

Phase 3: Layer Architecture

Domain Layer Rules

  • Zero external dependencies: No NestJS, TypeORM, Redis, etc.
  • Pure TypeScript: Only type definitions and business logic
  • Self-contained: Must compile independently
  • Test without framework: Jest only, no NestJS testing utilities

Application Layer Rules

  • Depends only on domain: Import from @domain/* only
  • Exposes REST API: Controllers validate input and delegate to domain services
  • DTO mapping: Transform external DTOs to domain entities
  • No business logic: Controllers are thin, logic stays in domain

Infrastructure Layer Rules

  • Implements SPI ports: All repository and service interfaces
  • Framework dependencies: TypeORM, Redis, AWS SDK, etc.
  • Maps external data: ORM entities ↔ Domain entities
  • Circuit breakers: Carrier connectors must implement retry/fallback logic

Phase 4: Technical Validation

Dependency Management

domain/package.json (if separate):

{
  "dependencies": {},  // NO runtime dependencies
  "devDependencies": {
    "typescript": "^5.3.0",
    "@types/node": "^20.0.0"
  }
}

Root package.json:

{
  "dependencies": {
    "@nestjs/common": "^10.0.0",
    "@nestjs/core": "^10.0.0",
    "@nestjs/swagger": "^7.0.0",
    "typeorm": "^0.3.17",
    "pg": "^8.11.0",
    "redis": "^4.6.0",
    "class-validator": "^0.14.0",
    "class-transformer": "^0.5.1"
  }
}

tsconfig.json Path Aliases

{
  "compilerOptions": {
    "strict": true,
    "baseUrl": "./src",
    "paths": {
      "@domain/*": ["domain/*"],
      "@application/*": ["application/*"],
      "@infrastructure/*": ["infrastructure/*"]
    }
  }
}

Design Patterns

  • Domain-Driven Design: Entities, Value Objects, Aggregates
  • SOLID Principles: Especially DIP (Dependency Inversion)
  • Repository Pattern: Abstraction over data persistence
  • Strategy Pattern: Carrier connectors (Maersk, MSC, etc.)
  • Circuit Breaker: For carrier API calls (5s timeout)

NestJS Configuration

  • Use @Injectable() in application and infrastructure layers ONLY
  • Domain services registered manually via @Module providers
  • Avoid @Component or decorators in domain layer
  • Use interceptors for transactions (@Transactional)

Phase 5: Testing Strategy

Unit Tests (Domain)

// domain/services/booking.service.spec.ts
describe('BookingService', () => {
  it('should create booking with valid rate quote', () => {
    // Test without any framework dependencies
    const service = new BookingService(mockRepository);
    const result = service.createBooking(validInput);
    expect(result.bookingNumber).toMatch(/^WCM-\d{4}-[A-Z0-9]{6}$/);
  });
});

Integration Tests (Infrastructure)

// infrastructure/persistence/typeorm/repositories/booking.repository.spec.ts
describe('TypeOrmBookingRepository', () => {
  let repository: TypeOrmBookingRepository;

  beforeAll(async () => {
    // Use testcontainers for real PostgreSQL
    await setupTestDatabase();
  });

  it('should persist booking to database', async () => {
    const booking = createTestBooking();
    await repository.save(booking);
    const found = await repository.findById(booking.id);
    expect(found).toBeDefined();
  });
});

E2E Tests (Full API)

// test/e2e/booking-workflow.e2e-spec.ts
describe('Booking Workflow (E2E)', () => {
  it('should complete full booking flow', async () => {
    // 1. Search rates
    const ratesResponse = await request(app.getHttpServer())
      .post('/api/v1/rates/search')
      .send(searchPayload);

    // 2. Create booking
    const bookingResponse = await request(app.getHttpServer())
      .post('/api/v1/bookings')
      .send(bookingPayload);

    // 3. Verify booking confirmation email sent
    expect(bookingResponse.status).toBe(201);
    expect(emailSpy).toHaveBeenCalled();
  });
});

Test Coverage Targets

  • Domain: 90%+ coverage
  • Application: 80%+ coverage
  • Infrastructure: 70%+ coverage (focus on critical paths)

Phase 6: Naming Conventions

TypeScript Conventions

  • Interfaces: UserRepository (no "I" prefix)
  • Ports: SearchRatesPort, CarrierConnectorPort
  • Adapters: TypeOrmBookingRepository, MaerskConnectorAdapter
  • Services: BookingService, RateSearchService
  • Entities: Booking, RateQuote, Organization
  • Value Objects: BookingNumber, Email, PortCode
  • DTOs: CreateBookingDto, RateSearchRequestDto

File Naming

  • Entities: booking.entity.ts
  • Interfaces: booking.repository.ts (for ports)
  • Implementations: typeorm-booking.repository.ts
  • Tests: booking.service.spec.ts
  • Barrel exports: index.ts

Import Order

// 1. External dependencies
import { Injectable } from '@nestjs/common';

// 2. Domain imports
import { Booking } from '@domain/entities';
import { BookingRepository } from '@domain/ports/out';

// 3. Relative imports
import { BookingOrmEntity } from './entities/booking.orm-entity';

Phase 7: Validation Checklist

Critical Questions

  • Domain isolation: No import of NestJS/TypeORM in domain layer?
  • Dependency direction: All dependencies point inward toward domain?
  • Framework-free testing: Can domain be tested without NestJS TestingModule?
  • Database agnostic: Could we switch from TypeORM to Prisma without touching domain?
  • Interface flexibility: Could we add GraphQL without changing domain?
  • Compilation independence: Does domain compile without other layers?

Data Flow Validation

  • Inbound: HTTP Request → Controller → DTO → Mapper → Domain Entity → Use Case
  • Outbound: Use Case → Repository Port → Adapter → ORM Entity → Database
  • Carrier Integration: Use Case → CarrierConnectorPort → MaerskAdapter → Maersk API

Phase 8: Pre-Coding Checklist

Setup Tasks

  • Node.js v20+ installed
  • TypeScript with strict: true
  • NestJS CLI installed globally
  • ESLint + Prettier configured
  • Husky pre-commit hooks
  • .env.example with all required variables

Architecture Validation

  • PRD reviewed and understood
  • Domain entities mapped to database schema
  • All ports (in/out) identified
  • Carrier connector strategy defined
  • Cache strategy documented (Redis, 15min TTL)
  • Test strategy approved

Development Order

  1. Domain layer: Entities → Value Objects → Services → Ports
  2. Infrastructure layer: TypeORM entities → Repositories → Carrier connectors → Cache/Email adapters
  3. Application layer: DTOs → Mappers → Controllers → Validation pipes
  4. Bootstrap: main.ts → app.module.ts → DI configuration
  5. Tests: Unit (domain) → Integration (repos) → E2E (API)

Common Pitfalls to Avoid

⚠️ Critical Mistakes:

  • Circular imports (use barrel exports index.ts)
  • Framework decorators in domain (@Column, @Injectable)
  • Business logic in controllers or adapters
  • Using any type (always explicit types)
  • Promises not awaited (use async/await properly)
  • Carrier APIs without circuit breakers
  • Missing Redis cache for rate queries
  • Not validating DTOs with class-validator
  • Validation: class-validator + class-transformer
  • Mapping: Manual mappers (avoid heavy libraries)
  • API Docs: @nestjs/swagger (OpenAPI)
  • Logging: Winston or Pino
  • Config: @nestjs/config with Joi validation
  • Testing: Jest + Supertest + @faker-js/faker
  • Circuit Breaker: opossum library
  • Redis: ioredis
  • Email: mjml + nodemailer

Example: Complete Feature Flow

Scenario: Search Rates for Rotterdam → Shanghai

// 1. Controller (application layer)
@Controller('api/v1/rates')
export class RatesController {
  constructor(private readonly searchRatesUseCase: SearchRatesPort) {}

  @Post('search')
  async searchRates(@Body() dto: RateSearchDto) {
    const domainInput = RateSearchMapper.toDomain(dto);
    const quotes = await this.searchRatesUseCase.execute(domainInput);
    return RateSearchMapper.toDto(quotes);
  }
}

// 2. Use Case (domain port in)
export interface SearchRatesPort {
  execute(input: RateSearchInput): Promise<RateQuote[]>;
}

// 3. Domain Service (domain layer)
export class RateSearchService implements SearchRatesPort {
  constructor(
    private readonly cache: CachePort,
    private readonly carriers: CarrierConnectorPort[]
  ) {}

  async execute(input: RateSearchInput): Promise<RateQuote[]> {
    // Check cache first
    const cached = await this.cache.get(input.cacheKey);
    if (cached) return cached;

    // Query carriers in parallel with timeout
    const results = await Promise.allSettled(
      this.carriers.map(c => c.searchRates(input))
    );

    // Filter successful results
    const quotes = results
      .filter(r => r.status === 'fulfilled')
      .flatMap(r => r.value);

    // Cache results (15 min TTL)
    await this.cache.set(input.cacheKey, quotes, 900);

    return quotes;
  }
}

// 4. Infrastructure Adapter (infrastructure layer)
export class MaerskConnectorAdapter implements CarrierConnectorPort {
  async searchRates(input: RateSearchInput): Promise<RateQuote[]> {
    // HTTP call to Maersk API with 5s timeout
    const response = await this.httpClient.post(
      'https://api.maersk.com/rates',
      this.mapToMaerskFormat(input),
      { timeout: 5000 }
    );

    // Map Maersk response to domain entities
    return this.mapToDomainQuotes(response.data);
  }
}

This architecture ensures clean separation, testability, and flexibility for the Xpeditis maritime freight platform.