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

645 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
### Rate Search
- `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):
```json
{
"dependencies": {}, // NO runtime dependencies
"devDependencies": {
"typescript": "^5.3.0",
"@types/node": "^20.0.0"
}
}
```
**Root package.json**:
```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
```json
{
"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)
```typescript
// 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)
```typescript
// 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)
```typescript
// 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
```typescript
// 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`
## Recommended Tools
- **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
```typescript
// 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.