2026-07-16 08:52:29 +00:00
2026-07-23 20:11:11 +03:00
2026-06-22 12:04:07 +03:00
2026-05-12 15:17:16 +03:00
2026-07-16 08:52:29 +00:00
2026-07-08 09:01:07 +03:00

EDR Platform - Ethio-Djibouti Railway Passenger API

Enterprise-grade NestJS REST API for the Ethio-Djibouti Railway passenger booking and management platform. Built with TypeScript, PostgreSQL, and Prisma ORM.

🚀 Features

🆕 NEW: Age-Based Pricing, Verifayda 2.0 & Multi-Currency

Age-Based Pricing

  • ADULT (≥5 years): Pay 100% of base fare
  • CHILD (<5 years): First child travels FREE, subsequent children pay 100%
  • Automatic age calculation from date of birth
  • Example: 2 adults + 3 children = 4× base fare (first child free)

Verifayda 2.0 Integration

  • Real-time Ethiopian national ID verification
  • Retrieves passenger data from government database
  • National IDs NOT stored (policy compliant)
  • Non-Ethiopians use passport (no verification required)
  • Booking fails if verification unsuccessful

Multi-Currency Support

  • Transaction Currency: ETB (Ethiopian Birr)
  • Display Currencies: ETB, DJF (Djiboutian Franc), USD (US Dollar)
  • Real-time exchange rate conversion
  • Prices shown in user's preferred currency
  • Exchange rates: ETB→DJF=3.25, ETB→USD=0.018

Core Modules

  • Authentication & Authorization - Dual authentication system:
    • Passenger Auth: JWT-based auth with OTP verification, password reset, account lockout
    • Corporate IAM: Integration with @tria-plc corporate identity system for back-office operations (agents, supervisors, admins)
    • Role-based access control (RBAC) with granular permissions
  • Age-Based Pricing - Smart passenger categorization:
    • ADULT (≥5 years): Full fare
    • CHILD (<5 years): First child free, subsequent children full fare
    • Automatic age calculation from date of birth
  • Verifayda 2.0 Integration - Ethiopian national ID verification:
    • Real-time verification via government API
    • Retrieves passenger data (name, DOB, nationality)
    • National IDs NOT stored (policy compliant)
    • Non-Ethiopians use passport (no verification)
  • Multi-Currency Support - Display prices in multiple currencies:
    • ETB (Ethiopian Birr) - Transaction currency
    • DJF (Djiboutian Franc) - Display option
    • USD (US Dollar) - Display option
    • Real-time exchange rate conversion
  • Booking Management - Complete booking lifecycle:
    • Guest Booking: Book without login, optional account creation
    • Saved Passengers: Store passenger details for quick rebooking
    • Modification, cancellation, refunds, and fare breakdown
    • Multi-segment journey support
  • Payment Integration - Multi-provider support (Telebirr, CBE Birr, eBirr, Card, Wallet) with webhook handling
  • Seat Management - Real-time seat inventory:
    • Seat holds with 5-minute expiry
    • Seat releases and blocking with coach/class management
    • Segment-based seat availability (partial journey bookings)
    • Auto-assign seats with contiguous algorithm
    • CSV import/export for seat configurations
  • Ticketing - QR code and barcode generation, PDF tickets, gate validation with audit logs
  • Agent Operations - Counter booking, shift management, commission tracking, and reconciliation
  • Passenger Services - Profile management, traveler profiles, saved routes, and preferences
  • Loyalty Program - Points accumulation, tier management (Bronze/Silver/Gold/Platinum), and rewards
  • Wallet System - Balance management, top-up, transaction ledger
  • Live Tracking - Real-time trip status, location updates, delay notifications, crowd signals
  • Notifications - Multi-channel (Email, SMS, Push) with templating engine
  • Support System - FAQ management, live chat conversations
  • Reports & Analytics - Revenue reports, occupancy analytics, agent sales tracking
  • Route Management - Route configuration, stops, fare rules, baggage allowance

Technical Features

  • Security - Password hashing (bcrypt), JWT tokens, rate limiting, audit logging
  • Validation - Request validation with class-validator, DTO transformation
  • Documentation - Auto-generated Swagger/OpenAPI docs at /api-docs
  • Error Handling - Global exception filters with standardized error responses
  • Database - PostgreSQL with Prisma ORM, migrations, and comprehensive seeding
  • Scheduling - Cron jobs for automated tasks (seat release, report generation)
  • Event System - Event-driven architecture with @nestjs/event-emitter

📋 Prerequisites

  • Node.js >= 20.x
  • pnpm >= 9.x (npm install -g pnpm)
  • PostgreSQL >= 15.x
  • Git

🛠️ Installation & Setup

1. Clone Repository

git clone <repository-url>
cd edr-platform

2. Install Dependencies

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/<feature>/
  entities/<feature>.entity.ts          // extends BaseEntity (UUID, timestamps, soft delete)
  dto/<verb>-<feature>.dto.ts           // class-validator DTOs
  <feature>.module.ts                   // wires controller + service + repository
  <feature>.controller.ts               // HTTP layer only — no business logic
  <feature>.service.ts                  // business logic
  <feature>.repository.ts               // extends BaseRepository<Entity>; services inject this, NEVER `Repository<T>` 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. Main brand color: #10B981 (rgb(16, 185, 129) — emerald-500). Icon containers use the filled style: brand-color background with a white icon.
  • 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

pnpm install

3. Environment Configuration

# Copy environment template
cp apps/edr-passenger-api/.env.example apps/edr-passenger-api/.env

# Edit .env file with your configuration

Required Environment Variables

Variable Description Example
NODE_ENV Environment mode development
PORT HTTP server port 4000
DATABASE_URL PostgreSQL connection string postgresql://user:pass@localhost:5432/edr_passenger
JWT_SECRET JWT signing secret (change in production) your-secret-key
JWT_EXPIRES_IN JWT token expiry 7d
PORTAL_URL Web app CORS origin http://localhost:3000
BACK_OFFICE_URL Admin portal CORS origin http://localhost:3001
SENDGRID_API_KEY SendGrid API key (optional) SG.xxx
SENDGRID_FROM_EMAIL Email sender address noreply@edr-platform.com

Verifayda 2.0 Configuration (Ethiopian National ID Verification)

Variable Description Example
VERIFAYDA_ENABLED Enable Verifayda integration true or false
VERIFAYDA_API_URL Verifayda API endpoint https://api.verifayda.gov.et/v2
VERIFAYDA_API_KEY API key for Verifayda service your-verifayda-api-key

Note: When VERIFAYDA_ENABLED=false, verification is skipped (development mode only).

Corporate IAM Configuration (Back-office Authentication)

Variable Description Example
IAM_ENABLED Enable corporate IAM integration true or false
IAM_API_URL Corporate IAM API endpoint https://iam.tria-plc.com/api
IAM_API_KEY API key for IAM service your-iam-api-key

Note: When IAM_ENABLED=false, IAM-protected routes allow access without validation (development mode only).

Optional: Payment Provider Configuration

# Telebirr Configuration
TELEBIRR_BASE_URL=https://api.telebirr.com
TELEBIRR_MERCHANT_CODE=your-merchant-code
TELEBIRR_APP_SECRET=your-app-secret
# ... see .env.example for complete list

4. Database Setup

Start PostgreSQL

# Using Docker (recommended)
docker run --name edr-postgres \
  -e POSTGRES_USER=edr \
  -e POSTGRES_PASSWORD=edr_secret \
  -e POSTGRES_DB=edr_passenger \
  -p 5432:5432 \
  -d postgres:15

# Or use your local PostgreSQL installation

Generate Prisma Client

pnpm --filter @edr/passenger-api run prisma:generate

Run Migrations

pnpm --filter @edr/passenger-api run prisma:migrate:dev

Seed Database

pnpm --filter @edr/passenger-api run prisma:seed

Seed Data Includes:

  • 21 Stations (Complete Ethiopian-Djibouti Railway with country codes)
  • 1 Route with 21 stops and fare rules
  • 2 Train services with 4 trips
  • 360 seats across 12 coaches (Economy Regular, Economy Bed, VIP Bed classes)
  • 3 User accounts (Admin, Passenger, Agent)
  • Fare rules for ADULT and CHILD passenger categories
  • Currency exchange rates (ETB, DJF, USD)
  • Luggage allowance rules
  • Notification templates
  • Promotions and FAQ content
  • Menu items and station crowd signals
  • Fraud detection rules
  • Saved passenger profiles for testing

5. Start Development Server

pnpm --filter @edr/passenger-api run dev

API Server: http://localhost:4000
Swagger Docs: http://localhost:4000/api-docs

🔑 Default Credentials

After seeding, use these credentials to test the API:

Role Email Password Description
Admin admin@edr-platform.com admin123 Full system access, reports, agent management
Passenger kelemu@email.com password123 Regular user with loyalty (Silver) and wallet
Agent agent@edr-platform.com agent123 Counter booking agent with commission tracking

📚 API Documentation

Swagger UI

Interactive API documentation available at: http://localhost:4000/api-docs

Authentication Methods

The API uses two authentication schemes:

1. JWT Authentication (Passenger-facing)

  • Used for: Passenger bookings, profile management, wallet, loyalty
  • Header: Authorization: Bearer <jwt-token>
  • Obtain token: POST /auth/login with passenger credentials
  • Swagger Security: JWT-auth

2. IAM Authentication (Back-office)

  • Used for: Agent operations, fraud detection, reports, admin functions
  • Header: Authorization: Bearer <iam-token>
  • Obtain token: From corporate IAM system (https://iam.tria-plc.com)
  • Swagger Security: IAM-auth
  • Roles: AGENT, SUPERVISOR, ADMIN, STAFF

API Endpoints Overview

Module Base Path Auth Type Description
Auth /auth Public/JWT Register, login, OTP verification, password reset
Passengers /passengers Public/JWT Verifayda verification, international registration, profiles
Search /search Public Trip search, availability, fare quotes
Stations /stations Public/JWT Station directory and information
Seats /seats JWT/IAM Seat maps, holds, releases, blocking
Bookings /bookings Public/JWT Guest booking, create, modify, cancel bookings
Payments /payments JWT/Public Payment initiation, webhooks, refunds
Tickets /tickets JWT/IAM Ticket generation, QR/barcode, validation
Notifications /notifications JWT In-app notifications, preferences
Loyalty /loyalty JWT Points, tiers, rewards redemption
Wallet /wallet JWT Balance, top-up, transaction history
Promotions /promos Public/JWT Active promotions, promo code validation
Live Tracking /live Public/JWT Real-time trip status, crowd signals
Support /support Public/JWT FAQ, chat conversations
Dashboard /dashboard JWT Home screen aggregated data
Routes /routes JWT/IAM Reusable route templates with ordered stops
Schedules /schedules JWT/IAM Trip schedules, fare rules, status updates
Fleet /fleet JWT/IAM Train services, coaches, seat configurations
Seat Classes /seat-classes or /classes Public/JWT/IAM Seat class management and configuration
Segment Seats /segments/seats Public/JWT Segment-based seat availability and booking
Agents /agents IAM Agent booking, shifts, commissions, reconciliation
Fraud Detection /fraud IAM Fraud alerts, rules management, user blocking
Reports /reports IAM Revenue, occupancy, agent sales analytics

Example API Calls

1. Register Passenger

POST /auth/register
Content-Type: application/json

{
  "email": "user@example.com",
  "phone": "+251911234567",
  "fullName": "John Doe",
  "password": "SecurePass123"
}

2. Login (Passenger)

POST /auth/login
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "SecurePass123"
}

# Response includes JWT token
{
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": { "id": "uuid", "role": "PASSENGER" }
}

3. Verify Ethiopian National ID (Verifayda)

POST /passengers/verify-fayda
Content-Type: application/json

{
  "nationalId": "ET123456789"
}

# Response with verified passenger data
{
  "verified": true,
  "passengerData": {
    "fullName": "Abebe Kebede",
    "dateOfBirth": "1985-03-15T00:00:00.000Z",
    "gender": "Male",
    "nationality": "Ethiopian"
  }
}

4. Universal Passenger Registration (NEW)

# Guest Ethiopian with Fayda verification
POST /passengers/register
Content-Type: application/json

{
  "passengerName": "Abebe Kebede",
  "dateOfBirth": "1985-03-15",
  "nationalId": "ET123456789",
  "phone": "+251911234567",
  "deviceId": "device-uuid-123"
}

# Logged-in user with JWT token
POST /passengers/register
Authorization: Bearer <jwt-token>
Content-Type: application/json

{
  "passengerName": "Abebe Kebede",
  "dateOfBirth": "1985-03-15",
  "nationalId": "ET123456789",
  "phone": "+251911234567"
}

# International passenger (passport)
POST /passengers/register
Content-Type: application/json

{
  "passengerName": "John Smith",
  "dateOfBirth": "1990-07-20",
  "passportNumber": "P1234567",
  "passportCountry": "Kenya",
  "nationality": "Kenyan",
  "phone": "+254712345678",
  "email": "john@example.com",
  "deviceId": "device-uuid-123"
}

5. Get User Profile (NEW)

GET /auth/profile
Authorization: Bearer <jwt-token>

# Response includes user, passenger, loyalty, and wallet details
{
  "id": "uuid",
  "email": "user@example.com",
  "phone": "+251911234567",
  "fullName": "John Doe",
  "role": "PASSENGER",
  "nationality": "Ethiopian",
  "faydaVerified": true,
  "faydaVerifiedAt": "2024-01-15T10:30:00.000Z",
  "passenger": {
    "id": "uuid",
    "loyalty": {
      "tier": "SILVER",
      "pointsBalance": 1500,
      "lifetimePoints": 3000
    },
    "wallet": {
      "balanceMinor": 50000,
      "currency": "ETB"
    }
  }
}

6. Search Trips

POST /search
Content-Type: application/json

{
  "originStationId": "uuid",
  "destinationStationId": "uuid",
  "date": "2026-06-15",
  "adultCount": 2,
  "childCount": 1
}

7. Get Fare Quote

POST /search/fare-quote
Content-Type: application/json

{
  "tripId": "uuid",
  "serviceClass": "ECONOMY_REGULAR",
  "adultCount": 2,
  "childCount": 1,
  "displayCurrency": "USD"
}

# Response includes age-based pricing breakdown
{
  "baseFareMinor": 35000,
  "adultCount": 2,
  "adultFareMinor": 70000,
  "childCount": 1,
  "freeChildrenCount": 1,
  "paidChildrenCount": 0,
  "childFareMinor": 0,
  "totalMinor": 73500,
  "currency": "ETB",
  "displayCurrency": "USD",
  "displayTotalMinor": 1323
}

8. Guest Booking (No Login Required)

POST /bookings/guest
Content-Type: application/json

{
  "tripId": "uuid",
  "holdId": "uuid",
  "serviceClass": "ECONOMY_REGULAR",
  "displayCurrency": "ETB",
  "passengers": [
    {
      "seatId": "uuid",
      "passengerName": "Abebe Kebede",
      "dateOfBirth": "1985-03-15",
      "idDocumentType": "NATIONAL_ID",
      "idDocumentNumber": "ET123456789"
    }
  ],
  "createAccount": false,
  "savePassengerDetails": true,
  "deviceId": "device-uuid"
}

9. Agent Booking (IAM Auth)

POST /agents/bookings
Authorization: Bearer <iam-token>
Content-Type: application/json

{
  "tripId": "uuid",
  "seats": [...],
  "paymentMethod": "CASH",
  "cashReceived": 50000
}

🏗️ Project Structure

apps/edr-passenger-api/
├── prisma/
│   ├── schema.prisma          # Database schema (40+ models)
│   ├── seed.ts                # Comprehensive seed script
│   └── migrations/            # Database migrations
├── src/
│   ├── common/                # Shared utilities
│   │   ├── filters/           # Exception filters
│   │   ├── interceptors/      # Response interceptors
│   │   ├── pipes/             # Validation pipes
│   │   ├── i18n/              # Internationalization
│   │   ├── jwt.guard.ts       # JWT authentication guard (passengers)
│   │   ├── jwt.strategy.ts    # Passport JWT strategy
│   │   ├── iam-adapter.ts     # Corporate IAM guard (back-office)
│   │   ├── iam.module.ts      # IAM module
│   │   ├── roles.guard.ts     # RBAC authorization guard
│   │   ├── roles.decorator.ts # Roles decorator
│   │   ├── prisma.service.ts  # Prisma client service
│   │   └── prisma.module.ts   # Prisma module
│   ├── config/                # Configuration files
│   │   ├── app.config.ts      # App configuration
│   │   ├── database.config.ts # Database configuration
│   │   └── telebirr.config.ts # Payment provider config
│   ├── modules/               # Feature modules
│   │   ├── auth/              # Authentication & authorization (JWT)
│   │   ├── agents/            # Agent operations (IAM-protected)
│   │   ├── bookings/          # Booking management (JWT)
│   │   ├── currency/          # Currency conversion service
│   │   ├── dashboard/         # Dashboard aggregations (JWT)
│   │   ├── fleet/             # Train fleet management (JWT/IAM)
│   │   ├── fraud/             # Fraud detection (IAM-protected)
│   │   ├── live/              # Live tracking (JWT)
│   │   ├── loyalty/           # Loyalty program (JWT)
│   │   ├── notifications/     # Notification system (JWT)
│   │   ├── passengers/        # Passenger management (JWT)
│   │   ├── payments/          # Payment processing (JWT/Webhooks)
│   │   ├── promos/            # Promotions (JWT)
│   │   ├── reports/           # Reports & analytics (IAM-protected)
│   │   ├── schedules/         # Trip schedules (JWT/IAM)
│   │   ├── search/            # Trip search (JWT)
│   │   ├── seats/             # Seat management (JWT/IAM)
│   │   ├── segments/          # Journey segments (JWT)
│   │   ├── stations/          # Station management (JWT)
│   │   ├── support/           # Customer support (JWT)
│   │   ├── tickets/           # Ticketing (JWT/IAM)
│   │   ├── verifayda/         # Verifayda 2.0 integration
│   │   └── wallet/            # Wallet system (JWT)
│   ├── app.module.ts          # Root application module
│   └── main.ts                # Application entry point
├── test/                      # E2E tests
├── .env.example               # Environment template
├── Dockerfile                 # Docker configuration
├── nest-cli.json              # NestJS CLI configuration
├── package.json               # Dependencies & scripts
├── tsconfig.json              # TypeScript configuration
└── tsconfig.build.json        # Build configuration

🗄️ Database Schema

Key Models (40+ total)

Core Entities:

  • User, Session, Passenger, Agent
  • Station, Route, RouteStop, RouteFareRule
  • TrainService, Trip, TripStopTime, Coach, Seat
  • Booking, BookingSeat, Ticket
  • PaymentIntent, PaymentRefund, PaymentWebhookEvent

Enhanced Features:

  • OtpCode, PasswordResetToken (Auth)
  • AgentBooking, AgentShift, AgentCommission (Agents)
  • BookingModification, BookingCancellation (Booking lifecycle)
  • GateValidationLog (Ticket validation)
  • BaggageAllowance, BaggageBooking (Baggage)
  • LoyaltyAccount, LoyaltyLedgerEntry, LoyaltyReward
  • WalletAccount, WalletLedgerEntry
  • Notification, NotificationTemplate
  • AuditLog, OperationalReport
  • SeatBlock, SeatHold
  • CurrencyExchangeRate (Multi-currency)
  • VerifaydaVerification (National ID verification)
  • SavedPassengerProfile (Guest booking)
  • SeatClass (Seat class configuration)
  • JourneySegment (Multi-segment journeys)

🔧 Available Scripts

# Development
pnpm --filter @edr/passenger-api run dev          # Start with hot-reload

# Build
pnpm --filter @edr/passenger-api run build        # Compile TypeScript

# Production
pnpm --filter @edr/passenger-api run start        # Run compiled code

# Testing
pnpm --filter @edr/passenger-api run test         # Unit tests
pnpm --filter @edr/passenger-api run test:e2e     # E2E tests

# Code Quality
pnpm --filter @edr/passenger-api run lint         # ESLint
pnpm --filter @edr/passenger-api run type-check   # TypeScript check

# Database
pnpm --filter @edr/passenger-api run prisma:generate  # Generate Prisma client
pnpm --filter @edr/passenger-api run prisma:migrate:dev   # Run migrations (local dev)
pnpm --filter @edr/passenger-api run prisma:seed      # Seed database

🐳 Docker Deployment

All six apps build from Dockerfiles: each API has its own (apps/edr-freight-api/Dockerfile, apps/edr-passenger-api/Dockerfile); Vite frontends share infrastructure/docker/Dockerfile.web and are served with nginx. APIs run on Node 22.

Prerequisites

  • Docker with BuildKit enabled
  • A local .npmrc with GitHub Packages auth for @tria-plc/* (required for freight API and web images)
  • External Postgres for each API (compose does not include databases)
  • Copy apps/edr-freight-api/.env.example.env and apps/edr-passenger-api/.env.example.env with real connection strings

Build and run (all apps)

# From monorepo root
DOCKER_BUILDKIT=1 pnpm docker:build
pnpm docker:up

Or without pnpm scripts:

DOCKER_BUILDKIT=1 docker compose build
docker compose up -d
Service URL (default)
Freight API http://localhost:3001
Passenger API http://localhost:4000
Freight portal http://localhost:5173
Freight backoffice http://localhost:5183
Passenger portal http://localhost:5174
Passenger backoffice http://localhost:5184

Build a single service

docker compose build freight-api
docker compose build passenger-portal

Freight images mount .npmrc as a BuildKit secret during pnpm install. Passenger web images do not require private packages.

VITE_API_URL (frontends)

API URLs are baked in at image build time (import.meta.env.VITE_API_URL). Defaults in docker-compose.yaml use http://localhost:3001/api (freight) and http://localhost:4000 (passenger) for local smoke tests. Override build args for production, e.g.:

docker compose build freight-portal \
  --build-arg VITE_API_URL=https://freight-api.example.com/api

Migrations

  • Freight API: TypeORM migrations are not run on container startup — apply them separately before deploy.
  • Passenger API: On each container start, the entrypoint runs npm run prisma:migrate and npm run prisma:seed (same package.json scripts as pnpm run) before starting the server. Ensure DATABASE_URL in .env points at a reachable Postgres instance.

For local development, use pnpm --filter @edr/passenger-api run prisma:migrate:dev instead of prisma:migrate.

GitHub Actions (self-hosted runner)

Two workflows deploy independently on push to main, develop, or staging:

Workflow Services Server env root
.github/workflows/deploy-freight.yml freight-api, freight-portal, freight-backoffice /home/user/environmen/edr-freight/<branch>/
.github/workflows/deploy-passenger.yml passenger-api, passenger-portal, passenger-backoffice /home/user/environmen/edr-passenger/<branch>/

On the runner, place env files before the first deploy (example for branch main):

/home/user/environmen/edr-freight/main/
  freight-api.env
  freight-portal.env          # optional runtime env for Vite/nginx
  freight-backoffice.env
  freight-web.build.env       # exports FREIGHT_VITE_API_URL=...

/home/user/environmen/edr-passenger/main/
  passenger-api.env
  passenger-portal.env
  passenger-backoffice.env
  passenger-web.build.env     # exports PASSENGER_VITE_API_URL=...

Example freight-web.build.env:

export FREIGHT_VITE_API_URL=https://freight-api.example.com/api

The workflow copies *.env into each app directory, creates .npmrc from the NPM_TOKEN repository secret, then runs docker compose build and docker compose up -d for that stack.

🔒 Security Best Practices

  1. Environment Variables - Never commit .env files. Use secrets management in production.
  2. JWT Secret - Use strong, randomly generated secrets (min 32 characters).
  3. Password Hashing - Bcrypt with salt rounds (default: 10).
  4. Rate Limiting - Implement rate limiting for auth endpoints.
  5. CORS - Configure allowed origins in production.
  6. HTTPS - Always use HTTPS in production.
  7. Database - Use connection pooling and prepared statements (Prisma handles this).
  8. Audit Logging - All sensitive operations are logged in AuditLog table.
  9. Dual Authentication - Passenger routes use JWT, back-office routes use corporate IAM.
  10. IAM Integration - Corporate IAM validates tokens against centralized identity service.
  11. Role-Based Access - Granular permissions enforced via IAM roles (AGENT, SUPERVISOR, ADMIN).
  12. Token Validation - IAM tokens validated in real-time with 5-second timeout.

📊 Monitoring & Logging

  • Application Logs - NestJS built-in logger
  • Database Queries - Prisma query logging (enable in development)
  • Audit Trail - All user actions logged in AuditLog table
  • Error Tracking - Global exception filters with detailed error responses

🧪 Testing

# Unit tests
pnpm --filter @edr/passenger-api run test

# E2E tests
pnpm --filter @edr/passenger-api run test:e2e

# Test coverage
pnpm --filter @edr/passenger-api run test:cov

🚀 Production Deployment

Pre-deployment Checklist

  • Update environment variables (JWT_SECRET, DATABASE_URL, etc.)
  • Configure IAM integration (IAM_ENABLED=true, IAM_API_URL, IAM_API_KEY)
  • Configure Verifayda integration (VERIFAYDA_ENABLED=true, VERIFAYDA_API_KEY)
  • Set up currency exchange rate sync (external API)
  • Set NODE_ENV=production
  • Configure CORS origins (PORTAL_URL, BACK_OFFICE_URL)
  • Set up SSL/TLS certificates
  • Configure database connection pooling
  • Set up monitoring and logging
  • Configure backup strategy
  • Test payment provider integrations
  • Verify IAM token validation endpoint
  • Test Verifayda verification with real national IDs
  • Verify currency conversion accuracy
  • Test age-based pricing calculations
  • Review security settings and audit logs
  • Test both JWT and IAM authentication flows

Deployment Steps

# 1. Build application
pnpm --filter @edr/passenger-api run build

# 2. Run migrations
pnpm --filter @edr/passenger-api run prisma:migrate

# 3. Start production server
NODE_ENV=production pnpm --filter @edr/passenger-api run start:prod

🤝 Contributing

  1. Fork the repository
  2. Create feature branch (git checkout -b feature/amazing-feature)
  3. Commit changes (git commit -m 'Add amazing feature')
  4. Push to branch (git push origin feature/amazing-feature)
  5. Open Pull Request

📝 License

This project is proprietary and confidential.

📧 Support

For technical support or questions:


Built with ❤️ for Ethio-Djibouti Railway

Start local databases

docker compose -f infrastructure/docker/docker-compose.db.dev.yml up -d

Run apps

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 patternmodule → controller → service → repository.
  • Repository injection — services inject the custom *Repository class, not Repository<T>.
  • 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 for the deeper developer guide used during AI-assisted contributions.

Description
No description provided
Readme 211 MiB
Languages
TypeScript 86.1%
JavaScript 9.8%
CSS 2.3%
HTML 1.6%
Handlebars 0.1%