# EDR Platform Monorepo for the **Ethio-Djibouti Railway** digital platform. Hosts two product lines — **Freight Management** and **Passenger Management** — each with a NestJS API plus React portal and back-office web apps, sharing TypeScript types, NestJS utilities, and a React component library. --- ## Tech Stack ### Backend | Layer | Tech | | ---------------- | ----------------------------------------------- | | Runtime | Node.js ≥ 20 | | Framework | NestJS 11 (modular architecture) | | Language | TypeScript 5 (strict mode, project-wide) | | ORM | TypeORM 0.3 (UUID PKs, soft deletes, `snake_case` columns) | | Database | PostgreSQL 16 (one DB per domain) | | Validation | class-validator + class-transformer | | API docs | Swagger via `@nestjs/swagger` | | Messaging | `@nestjs/microservices` (inter-service ready) | | Testing | Jest + Supertest | ### Frontend | Layer | Tech | | ---------------- | ----------------------------------------------- | | Framework | React 18 + Vite 5 | | Language | TypeScript 5 (strict) | | Routing | React Router v6 | | Styling | Tailwind CSS v4 (`@tailwindcss/vite`) + `tailwind-merge` + `class-variance-authority` | | UI primitives | Radix UI (meta `radix-ui` package, shadcn-style components) | | Icons | lucide-react | | State / Data | Zustand (client state) · TanStack Query (server state) | | HTTP | Axios | | Auth UI | `@tria-plc/iamui-common` (external IAM) | ### Shared Packages | Package | Purpose | | ---------------------- | -------------------------------------------------------------------- | | `@edr/types` | Shared TypeScript interfaces and enums | | `@edr/api-common` | NestJS decorators, filters, interceptors, pipes, `BaseEntity`, `BaseRepository` | | `@edr/ui-common` | Shared React components (`DashboardLayout`, `Sidebar`, `Button`, `Modal`, etc.) and theme tokens | | `@edr/eslint-config` | Shared ESLint configs (base / nestjs / react) | | `@edr/tsconfig` | Shared TypeScript configs | | `@edr/prettier-config` | Shared Prettier configuration | ### Tooling - **pnpm 9** — workspace package manager (sole supported PM) - **Turborepo 2** — task orchestrator with caching - **Husky + lint-staged + commitlint** — pre-commit ESLint/Prettier and conventional-commit enforcement - **Docker Compose** — local Postgres instances + production stack (see `infrastructure/`) - **Nginx** — reverse proxy / static asset server in production ### Planned Integrations - **MinIO** — S3-compatible object storage for documents (object keys already shaped under `edr-freight/{linkedType}/{ref}/{filename}`) --- ## Architecture ### High-level layout ``` ┌──────────────────────────────────────────────────────────────────┐ │ EDR Platform (monorepo) │ ├──────────────────────┬───────────────────────────────────────────┤ │ Freight domain │ Passenger domain │ │ ┌────────────────┐ │ ┌────────────────┐ │ │ │ freight-portal │ │ │ passenger- │ │ │ │ (React) │ │ │ portal (React)│ │ │ ├────────────────┤ │ ├────────────────┤ │ │ │ freight- │ │ │ passenger- │ │ │ │ backoffice │ │ │ backoffice │ │ │ └───────┬────────┘ │ └───────┬────────┘ │ │ │ │ │ │ │ ▼ │ ▼ │ │ ┌────────────────┐ │ ┌────────────────┐ │ │ │ freight-api │ │ │ passenger-api │ │ │ │ (NestJS) │ │ │ (NestJS) │ │ │ └───────┬────────┘ │ └───────┬────────┘ │ │ │ │ │ │ │ ▼ │ ▼ │ │ ┌────────────────┐ │ ┌────────────────┐ │ │ │ postgres- │ │ │ postgres- │ │ │ │ freight │ │ │ passenger │ │ │ └────────────────┘ │ └────────────────┘ │ └──────────────────────┴───────────────────────────────────────────┘ Shared (workspace) packages @edr/types · @edr/api-common · @edr/ui-common · @edr/{eslint,tsconfig,prettier}-config ``` ### Domain isolation - **One database per domain.** `postgres-freight` (port 5433, db `edr_freight`) and `postgres-passenger` (port 5434, db `edr_passenger`). No cross-database joins. Cross-domain data flows only through API calls or message queues. - **Each domain owns its data model.** Freight bookings/consignments/shipments/trains/invoices/documents live only in the freight DB; passenger journeys/tickets live only in the passenger DB. ### NestJS module pattern (per feature) ``` modules// entities/.entity.ts // extends BaseEntity (UUID, timestamps, soft delete) dto/-.dto.ts // class-validator DTOs .module.ts // wires controller + service + repository .controller.ts // HTTP layer only — no business logic .service.ts // business logic .repository.ts // extends BaseRepository; services inject this, NEVER `Repository` directly ``` Conventions enforced across the codebase: - All entities have UUID primary keys (`@PrimaryGeneratedColumn('uuid')`). - All entities inherit `createdAt` / `updatedAt` / `deletedAt` from `BaseEntity` (soft delete). - DB columns use `snake_case` via `@Column({ name: '...' })`; TS properties stay `camelCase`. - **No `synchronize: true`** in production — schema changes go through TypeORM migrations. - ESLint + Prettier run on pre-commit via Husky + lint-staged. - Conventional-commits enforced via commitlint. ### Frontend application structure ``` apps/edr-freight-web/portal/src/ App.tsx // routes + sidebar definition main.tsx // React Router + QueryClient providers components/ Breadcrumbs.tsx // shared local UI ui/ // shadcn-style primitives (Button, Input, Dialog, Label, Textarea) pages/ customers/ // CustomersPage + CustomerDetailPage + NewCustomerPage (dialog) bookings/ // ... + multimodal Transport Legs editor consignments/ tracking/ // Shipment grid + table view toggle trains/ // Fleet roster billing/ // Invoices documents/ // MinIO-shaped document library hooks/ // TanStack Query hooks (per feature) services/ // Axios clients (per feature) store/ // Zustand stores lib/ // utilities (`cn` helper, formatters) ``` Each feature folder typically contains: `*Page.tsx` (list), `*DetailPage.tsx`, `New*Page.tsx` (create/edit dialog with `mode: "create" | "edit"`), `Delete*Dialog.tsx`, and a `*.mock.ts` seed file used by the current mock UI. ### Shared layout (`@edr/ui-common`) `DashboardLayout` provides the sidebar + header shell shared across all freight and passenger web apps: - **Sidebar** — brand-tinted, icon-led navigation; the brand color (`#33578D`) marks the active item. - **Header** — language picker, notifications, user dropdown (click-driven, click-outside / Escape close); host apps opt into a light/dark theme toggle via `enableThemeToggle` (Tailwind class-based, persisted in `localStorage`). ### Auth integration (planned) Authentication is provided by an external `@edr/iamui-common` / `@tria-plc/iamapi-common` package. **Do not** implement login/JWT/password logic in this repo. Use placeholder TODO comments next to controllers and `@CurrentUser` decorators (in `@edr/api-common`) until the integration ships. --- ## Apps & Ports | App | Package name | Purpose | Port | | ------------------------------ | --------------------------- | ---------------------------------------- | ---- | | `edr-freight-api` | `@edr/freight-api` | NestJS API for freight | 3001 | | `edr-freight-web/portal` | `@edr/freight-portal` | React frontend for freight customers | 5173 | | `edr-freight-web/backoffice` | `@edr/freight-backoffice` | React frontend for freight employees | 5183 | | `edr-passenger-api` | `@edr/passenger-api` | NestJS API for passengers | 3002 | | `edr-passenger-web/portal` | `@edr/passenger-portal` | React frontend for passenger customers | 5174 | | `edr-passenger-web/backoffice` | `@edr/passenger-backoffice` | React frontend for passenger employees | 5184 | `edr-freight-web` and `edr-passenger-web` are grouping folders, not workspace packages. Each holds independent `portal/` and `backoffice/` workspace packages declared in `pnpm-workspace.yaml`. --- ## Getting Started ### Prerequisites - Node.js ≥ 20 - pnpm 9 (`corepack enable && corepack prepare pnpm@9.12.0 --activate`) - Docker (for local Postgres) ### Install ```bash pnpm install ``` ### Start local databases ```bash docker compose -f infrastructure/docker/docker-compose.db.dev.yml up -d ``` ### Run apps ```bash pnpm dev # every app pnpm dev:freight # freight API + portal + backoffice pnpm dev:passenger # passenger API + portal + backoffice ``` ### Common scripts | Script | Description | | ------------------- | ------------------------------------ | | `pnpm install` | Install workspace dependencies | | `pnpm dev` | Run every app in watch mode | | `pnpm build` | Build every package and app | | `pnpm test` | Run all tests | | `pnpm lint` | Lint everything | | `pnpm type-check` | Type-check every package | | `pnpm format` | Format with Prettier | --- ## Repository Layout ``` . ├── apps/ │ ├── edr-freight-api/ NestJS — freight backend │ ├── edr-freight-web/ │ │ ├── portal/ React — freight customer portal │ │ └── backoffice/ React — freight back-office │ ├── edr-passenger-api/ NestJS — passenger backend │ └── edr-passenger-web/ │ ├── portal/ React — passenger customer portal │ └── backoffice/ React — passenger back-office ├── packages/ │ ├── api-common/ Shared NestJS utilities + BaseEntity/Repository │ ├── types/ Shared TS types/enums │ ├── ui-common/ Shared React components + theme │ └── config/ │ ├── eslint/ @edr/eslint-config │ ├── tsconfig/ @edr/tsconfig │ └── prettier/ @edr/prettier-config ├── infrastructure/ │ ├── docker/ docker-compose files (db.dev / dev / prod) │ └── nginx/ Production nginx config ├── CLAUDE.md Developer guide for AI-assisted work ├── turbo.json Turborepo task pipeline ├── pnpm-workspace.yaml Workspace manifest └── README.md ``` --- ## Standards (recap) - **TypeScript strict mode** is enabled in every package. - **pnpm only** — never run `npm install` or `yarn`. - **Conventional commits** — enforced via commitlint on every commit. - **NestJS 4-layer pattern** — `module → controller → service → repository`. - **Repository injection** — services inject the custom `*Repository` class, not `Repository`. - **Controllers are thin** — no business logic; delegate to services. - **Migrations only** — never enable TypeORM `synchronize` in production. - **One DB per domain** — no cross-database joins. See [`CLAUDE.md`](./CLAUDE.md) for the deeper developer guide used during AI-assisted contributions.