9.8 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
Architecture
Hexagonal Architecture (Backend)
apps/backend/src/
├── domain/ # CORE - Pure TypeScript, NO framework imports
│ ├── entities/ # Booking, RateQuote, User, Carrier, Port, Container, CsvBooking, etc.
│ ├── value-objects/ # Money, Email, BookingNumber, BookingStatus, PortCode, ContainerType, Volume, LicenseStatus, SubscriptionPlan, etc.
│ ├── services/ # Pure domain services (rate-search, csv-rate-price-calculator, booking, port-search, etc.)
│ ├── 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
└── infrastructure/ # TypeORM entities/repos/mappers, Redis cache, carrier APIs, MinIO/S3, email (MJML+Nodemailer), 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 inapps/backend/tsconfig.json) - Domain tests run without NestJS TestingModule
- Backend has strict TypeScript:
strict: true,strictNullChecks: true(butstrictPropertyInitialization: false)
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.
Swagger plugin enabled in nest-cli.json — DTOs auto-documented.
Frontend (Next.js 14 App Router)
apps/frontend/
├── app/ # App Router pages
│ ├── dashboard/ # Protected routes (bookings, admin, settings)
│ └── carrier/ # Carrier portal (magic link auth)
└── src/
├── components/ # React components (shadcn/ui in ui/, layout/, bookings/, admin/)
├── hooks/ # useBookings, useNotifications, useCsvRateSearch, useCompanies, useFilterOptions
├── lib/
│ ├── api/ # Fetch-based API client with auto token refresh (client.ts + per-module files)
│ ├── context/ # Auth context
│ └── fonts.ts # Manrope (headings) + Montserrat (body)
├── types/ # TypeScript type definitions
└── utils/ # Export utilities (Excel, PDF)
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).
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 statictoOrm(),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. Per-module files (auth.ts, bookings.ts, rates.ts, etc.) import from client.
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
Carrier Portal Workflow
- Admin creates CSV booking → assigns carrier
- Email with magic link sent (1-hour expiry)
- Carrier auto-login → accept/reject booking
- Activity logged in
carrier_activitiestable
Common Pitfalls
- Never import NestJS/TypeORM in domain layer
- Never use
anytype in backend (strict mode enabled) - Never modify applied migrations — create new ones
- Always validate DTOs with
class-validatordecorators - 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
- Domain Entity →
domain/entities/*.entity.ts(pure TS, unit tests) - Value Objects →
domain/value-objects/*.vo.ts(immutable) - Port Interface →
domain/ports/out/*.repository.ts(with token constant) - ORM Entity →
infrastructure/persistence/typeorm/entities/*.orm-entity.ts - Migration →
npm run migration:generate -- src/infrastructure/persistence/typeorm/migrations/MigrationName - Repository Impl →
infrastructure/persistence/typeorm/repositories/ - Mapper →
infrastructure/persistence/typeorm/mappers/(static toOrm/toDomain/toDomainMany) - DTOs →
application/dto/(with class-validator decorators) - Controller →
application/controllers/(with Swagger decorators) - Module → Register and 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