xpeditis2.0/CLAUDE.md
2026-03-18 15:11:09 +01:00

257 lines
12 KiB
Markdown

# 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<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
### 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. **Port Interface**`domain/ports/out/*.repository.ts` (with token constant)
4. **ORM Entity**`infrastructure/persistence/typeorm/entities/*.orm-entity.ts`
5. **Migration**`npm run migration:generate -- src/infrastructure/persistence/typeorm/migrations/MigrationName`
6. **Repository Impl**`infrastructure/persistence/typeorm/repositories/`
7. **Mapper**`infrastructure/persistence/typeorm/mappers/` (static toOrm/toDomain/toDomainMany)
8. **DTOs**`application/dto/` (with class-validator decorators)
9. **Controller**`application/controllers/` (with Swagger decorators)
10. **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`