merge: integrer chore/outillage-et-format

This commit is contained in:
David 2026-09-07 21:42:36 +02:00
commit c4c70862c1
10 changed files with 436 additions and 15 deletions

View File

@ -0,0 +1,43 @@
---
name: "source-command-explore-and-plan"
description: "Explore codebase, create implementation plan, code, and test following EPCT workflow"
---
# source-command-explore-and-plan
Use this skill when the user asks to run the migrated source command `explore-and-plan`.
## Command Template
# Explore, Plan, Code, Test Workflow
At the end of this message, I will ask you to do something.
Please follow the "Explore, Plan, Code, Test" workflow when you start.
## Explore
First, use parallel subagents to find and read all files that may be useful for implementing the ticket, either as examples or as edit targets. The subagents should return relevant file paths, and any other info that may be useful.
## Plan
Next, think hard and write up a detailed implementation plan. Don't forget to include tests, lookbook components, and documentation. Use your judgement as to what is necessary, given the standards of this repo.
If there are things you are not sure about, use parallel subagents to do some web research. They should only return useful information, no noise.
If there are things you still do not understand or questions you have for the user, pause here to ask them before continuing.
## Code
When you have a thorough implementation plan, you are ready to start writing code. Follow the style of the existing codebase (e.g. we prefer clearly named variables and methods to extensive comments). Make sure to run our autoformatting script when you're done, and fix linter warnings that seem reasonable to you.
## Test
Use parallel subagents to run tests, and make sure they all pass.
If your changes touch the UX in a major way, use the browser to make sure that everything works correctly. Make a list of what to test for, and use a subagent for this step.
If your testing shows problems, go back to the planning stage and think ultrahard.
## Write up your work
When you are happy with your work, write up a short report that could be used as the PR description. Include what you set out to do, the choices you made with their brief justification, and any commands you ran in the process that may be useful for future developers to know about.

View File

@ -0,0 +1,17 @@
---
name: "source-command-fix-pr-comments"
description: "Fetch all comments for the current pull request and fix them."
---
# source-command-fix-pr-comments
Use this skill when the user asks to run the migrated source command `fix-pr-comments`.
## Command Template
Workflow:
1. Use `gh cli` to fetch the comments that are NOT resolved from the pull request.
2. Define all the modifications you should actually make.
3. Act and update the files.
4. Create a commit and push.

View File

@ -0,0 +1,43 @@
---
name: "source-command-quick-commit"
description: "Quickly commit all changes with an auto-generated message"
---
# source-command-quick-commit
Use this skill when the user asks to run the migrated source command `quick-commit`.
## Command Template
Workflow for quick Git commits:
1. Check git status to see what changes are present
2. Analyze changes to generate a short, clear commit message
3. Stage all changes (tracked and untracked files)
4. Create the commit with DH7789-dev signature
5. Optionally push to remote if tracking branch exists
The commit message will be automatically generated by analyzing:
- Modified files and their purposes (components, configs, tests, docs, etc.)
- New files added and their function
- Deleted files and cleanup operations
- Overall scope of changes to determine action verb (add, update, fix, refactor, remove, etc.)
Commit message format: `[action] [what was changed]`
Examples:
- `add user authentication system`
- `fix navigation menu responsive issues`
- `update API endpoints configuration`
- `refactor database connection logic`
- `remove deprecated utility functions`
This command is ideal for:
- Quick iteration cycles
- Work-in-progress commits
- Feature development checkpoints
- Bug fix commits
The commit will include your custom signature:
```
Signed-off-by: DH7789-dev
```

25
.codex/hooks.json Normal file
View File

@ -0,0 +1,25 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bun /Users/david/.claude/scripts/validate-command.js"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "afplay /Users/david/.claude/song/finish.mp3"
}
]
}
]
}
}

277
AGENTS.md Normal file
View File

@ -0,0 +1,277 @@
# AGENTS.md
This file provides guidance to Codex (Codex.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 test -- --testNamePattern="should create" # Filter by test name
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)
npm run clean # Remove all node_modules, dist, .next directories
```
## 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, User, Organization, Subscription, License, CsvBooking,
│ │ # CsvRate, InvitationToken
│ ├── value-objects/ # Money, Email, BookingNumber, BookingStatus, PortCode, ContainerType,
│ │ # Volume, DateRange, Surcharge
│ ├── 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: auth/, bookings/, csv-bookings, rates/, ports/,
│ │ # organizations/, users/, dashboard/, audit/, notifications/, webhooks/,
│ │ # gdpr/, admin/, subscriptions/
│ ├── controllers/ # REST controllers (also nested under feature folders)
│ ├── services/ # Application services: audit, notification, webhook,
│ │ # booking-automation, export, fuzzy-search, brute-force-protection
│ ├── 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,
# Pappers (French SIRET registry), PDF generation
```
**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
### API Key Authentication
A second auth mechanism alongside JWT. `ApiKey` domain entity (`domain/entities/api-key.entity.ts`) — keys are hashed with Argon2. `ApiKeyGuard` in `application/guards/` checks the `x-api-key` header. Routes can accept either JWT or API key; see `admin.controller.ts` for examples.
### WebSocket (Real-time Notifications)
Socket.IO gateway at `application/gateways/notifications.gateway.ts`. Clients connect to `/` namespace with a JWT bearer token in the handshake auth. Server emits `notification` events. The frontend `useNotifications` hook handles subscriptions.
### 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)
- Organizations can be validated via Pappers API (French SIRET/company registry) at `infrastructure/external/pappers-siret.adapter.ts`
### 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 (via `CarrierProfile` + `CarrierActivity` ORM entities)
## 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. **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 Entity** → `infrastructure/persistence/typeorm/entities/*.orm-entity.ts`
6. **Migration** → `npm run migration:generate -- src/infrastructure/persistence/typeorm/migrations/MigrationName`
7. **Repository Impl** → `infrastructure/persistence/typeorm/repositories/`
8. **Mapper** → `infrastructure/persistence/typeorm/mappers/` (static toOrm/toDomain/toDomainMany)
9. **DTOs** → `application/dto/` (with class-validator decorators)
10. **Controller** → `application/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/`

View File

@ -607,9 +607,7 @@ export class CsvRatesAdminController {
// company's other grid (export vs import) would be deleted too. // company's other grid (export vs import) would be deleted too.
await this.csvConfigRepository.delete(config.companyName, config.direction); await this.csvConfigRepository.delete(config.companyName, config.direction);
this.logger.log( this.logger.log(`Deleted CSV config and file for: ${config.companyName} (${config.direction})`);
`Deleted CSV config and file for: ${config.companyName} (${config.direction})`
);
return { return {
success: true, success: true,

View File

@ -326,9 +326,7 @@ export class RatesController {
status: 401, status: 401,
description: 'Unauthorized - missing or invalid token', description: 'Unauthorized - missing or invalid token',
}) })
async getAvailableOrigins( async getAvailableOrigins(@Query('direction') direction?: string): Promise<AvailableOriginsDto> {
@Query('direction') direction?: string
): Promise<AvailableOriginsDto> {
this.logger.log( this.logger.log(
`Fetching available origin ports from CSV rates${direction ? ` (${direction})` : ''}` `Fetching available origin ports from CSV rates${direction ? ` (${direction})` : ''}`
); );

View File

@ -12,7 +12,10 @@ const customConfig = {
], ],
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/', '<rootDir>/e2e/'], testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/', '<rootDir>/e2e/'],
moduleNameMapper: { moduleNameMapper: {
// Ces deux alias ne pointent pas vers `src/` : ils doivent precede la regle
// generique, sans quoi `@/i18n/navigation` est cherche dans `src/i18n`.
'^@/app/(.*)$': '<rootDir>/app/$1', '^@/app/(.*)$': '<rootDir>/app/$1',
'^@/i18n/(.*)$': '<rootDir>/i18n/$1',
'^@/(.*)$': '<rootDir>/src/$1', '^@/(.*)$': '<rootDir>/src/$1',
}, },
}; };

View File

@ -20,15 +20,15 @@
} }
], ],
"paths": { "paths": {
"@/*": ["./src/*"], "@/app/*": ["./app/*"],
"@/i18n/*": ["./i18n/*"],
"@/components/*": ["./src/components/*"], "@/components/*": ["./src/components/*"],
"@/lib/*": ["./src/lib/*"], "@/lib/*": ["./src/lib/*"],
"@/app/*": ["./app/*"],
"@/types/*": ["./src/types/*"], "@/types/*": ["./src/types/*"],
"@/hooks/*": ["./src/hooks/*"], "@/hooks/*": ["./src/hooks/*"],
"@/utils/*": ["./src/utils/*"], "@/utils/*": ["./src/utils/*"],
"@/pages/*": ["./src/pages/*"], "@/pages/*": ["./src/pages/*"],
"@/i18n/*": ["./i18n/*"] "@/*": ["./src/*"]
} }
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],

View File

@ -60,14 +60,30 @@ Expiry : 1 heure. Si expiré, l'admin doit renvoyer un nouveau lien.
## Statuts des réservations CSV ## Statuts des réservations CSV
Valeurs réelles de l'énumération `CsvBookingStatus` (`domain/entities/csv-booking.entity.ts`) :
| Statut | Description | | Statut | Description |
|--------|-------------| |--------|-------------|
| pending | En attente d'assignation carrier | | PENDING_PAYMENT | Réservation créée, commission non payée |
| sent | Lien magique envoyé au carrier | | PENDING_BANK_TRANSFER | Virement déclaré, en attente de validation par l'administration |
| accepted | Carrier a accepté | | PENDING | Commission payée, en attente de réponse du transporteur |
| rejected | Carrier a refusé | | ACCEPTED | Le transporteur a accepté |
| in_transit | En cours de transport | | REJECTED | Le transporteur a refusé |
| delivered | Livré | | CANCELLED | Annulée par l'utilisateur |
---
## Suppression d'une réservation impayée
`DELETE /api/v1/csv-bookings/:id` supprime définitivement une réservation **dont la commission n'a pas été payée**, c'est-à-dire au seul statut `PENDING_PAYMENT`. Seul le propriétaire peut le faire ; une réservation appartenant à quelqu'un d'autre répond `404`, sans se distinguer d'une réservation inexistante.
Une fois la commission payée, la réservation est partie chez le transporteur et porte une trace comptable : elle ne peut plus qu'être **annulée** (`PATCH :id/cancel`), jamais effacée. L'API répond alors `400`.
`PENDING_BANK_TRANSFER` est volontairement exclu : le virement déclaré peut être en cours d'acheminement, et supprimer la réservation priverait l'administration de ce qu'elle doit rapprocher à sa réception. Étendre la règle à ce statut est une décision comptable, pas technique — il suffit d'ajouter le statut à `DELETABLE_STATUSES` dans l'entité.
Les documents déjà téléversés restent dans le stockage objet, conformément à la politique appliquée à la suppression d'un document isolé (conservation pour l'audit). Le quota de réservations n'est pas affecté : il ne compte que les expéditions payées.
Côté interface, l'action « Supprimer » n'apparaît dans le menu d'une ligne que pour les réservations impayées, à côté de « Modifier » et « Payer ». Elle demande une confirmation qui nomme la réservation concernée.
--- ---
@ -75,6 +91,7 @@ Expiry : 1 heure. Si expiré, l'admin doit renvoyer un nouveau lien.
| Route | Description | | Route | Description |
|-------|-------------| |-------|-------------|
| /dashboard/bookings | Liste des réservations (voir, modifier, payer, supprimer) |
| /dashboard/csv-bookings | Liste admin des réservations CSV | | /dashboard/csv-bookings | Liste admin des réservations CSV |
| /carrier/auth | Page d'auth carrier (via magic link) | | /carrier/auth | Page d'auth carrier (via magic link) |
| /carrier/booking | Dashboard carrier (accept/reject) | | /carrier/booking | Dashboard carrier (accept/reject) |