Files
edr-platform/apps/edr-passenger-api/src/main.ts
2026-06-17 20:20:40 +03:00

348 lines
17 KiB
TypeScript

import "reflect-metadata";
import { NestFactory } from "@nestjs/core";
import { ValidationPipe } from "@nestjs/common";
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
import { AppModule } from "./app.module";
import { HttpExceptionFilter } from "./common/filters/http-exception.filter";
import { ResponseTransformInterceptor } from "./common/interceptors/response-transform.interceptor";
import { SessionActivityInterceptor } from "./common/interceptors/session-activity.interceptor";
async function bootstrap() {
// rawBody: true buffers the unparsed request body onto req.rawBody so webhook handlers
// (e.g. Waafi HMAC verification) can sign over the exact bytes the provider signed.
const app = await NestFactory.create(AppModule, { rawBody: true });
app.enableCors({
origin: [
process.env.PORTAL_URL ?? "http://localhost:5174",
process.env.BACK_OFFICE_URL ?? "http://localhost:5184",
],
});
app.useGlobalFilters(new HttpExceptionFilter());
app.useGlobalInterceptors(
new ResponseTransformInterceptor(),
app.get(SessionActivityInterceptor),
);
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true, forbidUnknownValues: false }));
const config = new DocumentBuilder()
.setTitle("EDR Passenger API")
.setDescription(
`# Ethio-Djibouti Railway Passenger Booking API
## Overview
Enterprise-grade REST API for the Ethio-Djibouti Railway passenger booking and management platform. Built with NestJS, TypeScript, PostgreSQL, and Prisma ORM.
## Latest Updates
- **TRANSIT & ROUND_TRIP_TRANSIT Booking Types:** Full multi-leg booking support. TRANSIT = single journey via connecting train (single PNR). ROUND_TRIP_TRANSIT = round trip where one or both directions use a connecting train (4 holds, 4 seat sets).
- **returnSeatId on Passenger Payloads:** For ROUND_TRIP and ROUND_TRIP_TRANSIT bookings each passenger object must include \`returnSeatId\` (the seat on the return leg-1). Guest and authenticated booking endpoints both enforce this.
- **Unified Booking Type Matrix:** bookingType field on Booking now accepts ONE_WAY | ROUND_TRIP | TRANSIT | ROUND_TRIP_TRANSIT across all create endpoints (POST /bookings and POST /bookings/guest).
- **Round-Trip Leg Tracking:** returnLegStatus on every booking tracks outbound/return leg usage (NEITHER_USED, OUTBOUND_ONLY, INBOUND_ONLY, BOTH_USED). Gate validation accepts a leg field (OUTBOUND | RETURN | LEG1 | LEG2 | OUTBOUND_LEG1 | OUTBOUND_LEG2 | RETURN_LEG1 | RETURN_LEG2).
- **Auto No-Show Detection:** Cron marks OUTBOUND_ONLY 30 min after return departure when return leg was never scanned.
- **Offline Batch Validation:** validateOfflineBatch now accepts leg per entry and handles both legs of a round-trip in one batch.
- **Booking Filters:** GET /bookings now accepts ?returnLegStatus= to filter no-show/inbound-only cases in back-office.
- **Sequence Ordering:** Stations and coaches now sorted by sequence field for consistent UI display.
- **User Profile Data:** Gender, DOB, passport, and national ID fields for comprehensive passenger profiles.
- **Seat Class Fees:** Premium charges and insurance fees per seat class for transparent pricing.
- **Multi-Currency Display:** Bookings track display currency and converted amounts.
- **Ticket Lifecycle:** Tickets now include validatedAt, outboundBoardedAt, returnBoardedAt for complete audit trail.
## Key Features
### Booking Lifecycle
- Search trips with real-time availability
- Age-based passenger categorization (Adult 5+ years, Child under 5)
- Nationality-based verification (Ethiopian Fayda, International Passport)
- Passenger information collection with verification
- Coach and seat selection with real-time availability
- Seat holding (15-minute expiry)
- Create bookings with verified passenger data
- Modify bookings (seat changes, passenger updates)
- Cancel bookings with automatic refunds
- Multi-segment journey support
- Cross-border journeys via Dire Dawa transit (Ethiopia to Djibouti)
- Round-trip booking with return journey scheduling
- Transit booking (single journey via connecting train, single PNR, single ticket)
- Round-trip transit booking (round trip where one or both directions use a connecting train)
- Coach type selection with seat class and pricing options
- Booking type field: ONE_WAY | ROUND_TRIP | TRANSIT | ROUND_TRIP_TRANSIT
- Display currency and converted pricing per booking
- returnLegStatus field tracks which legs of a round-trip were used
- GET /bookings?returnLegStatus=OUTBOUND_ONLY filters no-show returns in back-office
### Passenger Verification
1. Ethiopian Nationals:
- Automatic Fayda verification for adults (5+ years)
- Real-time national ID verification via government database
- Retrieves verified passenger data (name, DOB, gender)
- National IDs not stored (policy compliant)
2. International Passengers:
- Passport information collection
- Manual verification for Djiboutian and other nationals
- No government database verification required
### Age-Based Pricing
- ADULT (5+ years): Pay 100% of base fare
- CHILD (under 5): First child travels FREE, subsequent children pay 100%
- Automatic age calculation from date of birth
- Example: 2 adults + 3 children = 4x base fare (first child free)
- NEW: Premium charges and insurance fees per seat class
- NEW: Transparent fee breakdown in pricing calculations
### Payment Integration
1. Ethiopian Payment Methods: Telebirr, CBE Birr
2. Djiboutian Payment Methods: Waafi
3. International Payment Methods: Card, Wallet
### Seat Management
- Real-time seat availability by coach and class
- Seat holds with 15-minute expiry
- Auto-assign seats with contiguous algorithm
- Seat blocking for maintenance
- Coach-level seat maps (ordered by sequence)
- Class-based seating (Economy Regular, Economy Bed, VIP Bed)
- NEW: Sequence-based coach ordering for consistent display
### Ticketing
- QR code and barcode generation
- PDF ticket generation
- Gate validation with audit logs
- Offline validation support
- Multi-passenger tickets
- NEW: Ticket lifecycle tracking (validatedAt, outboundBoardedAt, returnBoardedAt timestamps)
- NEW: Gate validation accepts leg (OUTBOUND or RETURN) for round-trip tickets
- NEW: Complete audit trail per leg for compliance and reporting
### Booking Type Matrix
| bookingType | Holds required | Passenger seat fields | Legs in DB |
|---|---|---|---|
| ONE_WAY | holdId | seatId | 1 |
| ROUND_TRIP | holdId + returnHoldId | seatId + returnSeatId | 2 (leg=1 outbound, leg=2 return) |
| TRANSIT | holdId + leg2HoldId | seatId + leg2SeatId | 2 (leg=1, leg=2 on same direction) |
| ROUND_TRIP_TRANSIT | holdId + leg2HoldId + returnHoldId + returnLeg2HoldId | seatId + leg2SeatId + returnSeatId + returnLeg2SeatId | 4 |
### Round-Trip Leg Tracking
- returnLegStatus on Booking: NOT_APPLICABLE, NEITHER_USED, OUTBOUND_ONLY, INBOUND_ONLY, BOTH_USED
- Gate validation POST /tickets/:ref/validate accepts optional leg field:
- ONE_WAY: omit
- TRANSIT: LEG1 | LEG2
- ROUND_TRIP: OUTBOUND | RETURN
- ROUND_TRIP_TRANSIT: OUTBOUND_LEG1 | OUTBOUND_LEG2 | RETURN_LEG1 | RETURN_LEG2
- Auto no-show cron: sets OUTBOUND_ONLY 30 min after return departure when return leg unscanned
- Back-office filter: GET /bookings?returnLegStatus=OUTBOUND_ONLY surfaces no-shows
- Offline batch: validateOfflineBatch accepts leg per entry, handles both legs of same booking
### Round-Trip & Transit Bookings
- ONE_WAY and ROUND_TRIP for direct routes
- TRANSIT for single connecting journey (Dire Dawa hub), single PNR
- ROUND_TRIP_TRANSIT for round trips via connecting trains
- Combined pricing: total = sum of all leg base fares, single promo/loyalty deduction
- Separate seat management per leg; each leg stored with its scheduleId and leg number
- returnLegStatus tracks which legs have been boarded for no-show management
### Loyalty Program
- 4 tiers: Bronze, Silver, Gold, Platinum
- Points accumulation on trips
- Reward redemption
- Tier-based benefits
### Wallet System
- Top-up via payment methods
- Pay with wallet balance
- Transaction ledger
- Refund to wallet
### Live Tracking
- Real-time trip status
- Location updates
- Delay notifications
- Station crowd signals
### Fraud Detection
- Velocity checks (multiple bookings)
- High-value transaction monitoring
- Failed payment pattern detection
- Automatic user blocking
### Passenger Profiles
- Comprehensive profile data: gender, date of birth, nationality
- National ID for Ethiopian citizens (Fayda verified)
- Passport information for international passengers
- NEW: Complete demographic data for personalized services
### Internationalization
- Multi-language support (English, Amharic, French, Oromo)
- Locale-based responses
- Currency formatting (ETB, DJF, USD)
- NEW: Multi-currency display per booking (ETB, DJF, USD)
### Transit Stop Management
- Automatic detection of cross-border journeys (Ethiopia to Djibouti)
- Dire Dawa as mandatory transit hub for international journeys
- Dual-leg fare calculation (domestic + international)
- Age-based pricing applied independently per leg
- Seamless multi-segment booking workflow
### Coach Type & Class Selection
- Browse available coach types per route
- View seat classes per coach (Economy Regular, Economy Bed, VIP Bed)
- Compare base prices by coach type and class
- Real-time availability per coach configuration
- NEW: Sequence-based coach ordering for consistent UI
- NEW: Premium and insurance fee transparency per class
### Data Organization
- Stations ordered by sequence (1-15) for consistent route display
- Coaches ordered by sequence (1+) per type for predictable configuration
- Booking history sorted chronologically with filtering options
## Authentication
### Passenger Authentication (JWT-auth)
Used for passenger-facing endpoints. Obtain token via \`POST /auth/login\`.
**Usage:** Add header \`Authorization: Bearer <token>\`
### Back-office Authentication (IAM-auth)
Used for agent, fraud, and reporting endpoints. Requires corporate IAM token.
**Usage:** Add header \`Authorization: Bearer <iam-token>\`
## Passenger Booking Flow
### Step 1: Search Trips
\`POST /search\` with origin, destination, date, passenger counts, and nationality.
For round-trips also pass \`journeyType=ROUND_TRIP\` and \`returnDate\`.
### Step 2: Get Fare Quote
\`POST /search/fare-quote\` with passenger counts and display currency.
For round-trips also pass \`returnScheduleId\`, \`returnOriginStationId\`, \`returnDestinationStationId\`.
### Step 3: Passenger Information & Verification
**For Ethiopian Passengers:**
\`POST /passengers/verify-fayda\` — Automatic Fayda verification for adults (5+ years)
**For International Passengers:**
\`POST /passengers/register-international\` — Passport information collection
### Step 4: View Seat Map
\`GET /seats/seatmap/{scheduleId}\` — Show available coaches and seats.
For round-trips, call this twice: once for outbound scheduleId, once for return scheduleId.
### Step 5: Hold Seats
\`POST /seats/hold\` to reserve seats for 15 minutes.
- ONE_WAY / TRANSIT outbound leg: one hold call → \`holdId\`
- TRANSIT leg-2: second hold call → \`leg2HoldId\`
- ROUND_TRIP return: second hold call → \`returnHoldId\`
- ROUND_TRIP_TRANSIT: four hold calls → \`holdId\`, \`leg2HoldId\`, \`returnHoldId\`, \`returnLeg2HoldId\`
### Step 6: Create Booking
Choose the right endpoint and bookingType:
- **ONE_WAY** → \`POST /bookings/guest\` or \`POST /bookings\` with \`bookingType: ONE_WAY\`, passenger \`seatId\`
- **ROUND_TRIP** → same endpoint with \`bookingType: ROUND_TRIP\`, \`returnScheduleId/returnHoldId/returnOriginStationId/returnDestinationStationId\`, passenger \`seatId + returnSeatId\`
- **TRANSIT** → same endpoint with \`bookingType: TRANSIT\`, \`leg2ScheduleId/leg2HoldId/transitStationId/leg2DestinationStationId\`, passenger \`seatId + leg2SeatId\`
- **ROUND_TRIP_TRANSIT** → same endpoint with \`bookingType: ROUND_TRIP_TRANSIT\`, all 4 sets of schedule/hold/station fields, passenger \`seatId + leg2SeatId + returnSeatId + returnLeg2SeatId\`
### Step 7: Process Payment
\`POST /payments/telebirr\` (Ethiopian) or \`POST /payments/waafi\` (Djiboutian)
### Step 8: Get Tickets
\`GET /payments/{paymentId}/status\` to confirm payment and retrieve tickets with QR codes
## Rate Limiting
- Auth endpoints: 5 requests/minute
- General endpoints: 100 requests/minute
- Webhook endpoints: No limit
## Error Handling
All errors follow standard format:
\`\`\`json
{
"statusCode": 400,
"message": "Validation failed",
"error": "Bad Request",
"timestamp": "2026-05-20T14:30:00.000Z",
"path": "/bookings"
}
\`\`\`
## Pagination
List endpoints support pagination:
- \`limit\`: Number of items (default: 20, max: 100)
- \`offset\`: Skip items (default: 0)
## Webhooks
Payment providers send notifications to:
- \`POST /payments/webhooks/telebirr\` (Ethiopia)
- \`POST /payments/webhooks/cbe-birr\` (Ethiopia)
- \`POST /payments/webhooks/waafi\` (Djibouti)
- \`POST /payments/webhooks/card\` (International)
## Support
- **Email:** support@edr-platform.com
- **Documentation:** https://docs.edr-platform.com
- **Status Page:** https://status.edr-platform.com
`,
)
.setVersion("1.0.0")
.addBearerAuth(
{ type: "http", scheme: "bearer", bearerFormat: "JWT", in: "header" },
"JWT-auth",
)
.addTag("Agents", "Counter booking, shift management, commission tracking, and reconciliation")
.addTag("Audit", "User activity logging, system changes, compliance tracking, and audit trails")
.addTag("Auth", "Passenger registration, login, OTP, password reset, and profile management")
.addTag("Booking", "Complete booking lifecycle: create, modify, cancel, guest checkout. Supports ONE_WAY | ROUND_TRIP | TRANSIT | ROUND_TRIP_TRANSIT booking types. returnLegStatus filter for round-trip no-show management")
.addTag("Config", "System settings, feature flags, and configuration management")
.addTag("Currencies", "Multi-currency support, exchange rates, and currency conversion")
.addTag("Dashboard", "Home screen aggregations: trips, loyalty, wallet, notifications")
.addTag("Fare Engine", "Distance-based fare calculation with age-based pricing and multi-currency")
.addTag("Fayda Verification", "Ethiopian national ID verification via Verifayda 2.0 government API")
.addTag("Fleet", "Train services, coaches, coach types, seat classes, amenities, and configurations")
.addTag("Fraud Detection", "Velocity checks, monitoring alerts, pattern detection, and user blocking")
.addTag("Internal Payments", "Internal payment tracking, wallet transactions, and balance management")
.addTag("Live Tracking", "Real-time trip status, location updates, delays, and crowd signals")
.addTag("Loyalty", "Points ledger, tier management (Bronze/Silver/Gold/Platinum), rewards")
.addTag("Notifications", "Multi-channel delivery (email, SMS, push) and preference management")
.addTag("Passengers", "Registration, Fayda verification, international passports, saved profiles")
.addTag("Payment", "Telebirr, CBE Birr, Waafi, Card, Wallet payment processing and refunds")
.addTag("Payment Webhooks", "Payment provider webhook handlers and transaction confirmation")
.addTag("Promotions", "Promo codes, campaigns, discounts, and redemption tracking")
.addTag("Reports", "Revenue analytics, occupancy reports, agent sales, and KPI dashboards")
.addTag("Routes", "Route templates with ordered stops, fare rules, and baggage allowance")
.addTag("Schedule", "Trip schedules, availability windows, status tracking, and timing")
.addTag("Search", "Trip search, fare quotes, coach types, and real-time availability")
.addTag("Seat Classes", "Economy Regular, Economy Bed, VIP Bed class configuration and pricing")
.addTag("Seats", "Seat maps, holds (15-min expiry), releases, blocking, and inventory")
.addTag("Segment-based Seats", "Multi-leg journey seats, segment allocation, and per-leg availability")
.addTag("Stations", "Station directory, location data, baggage facilities, and amenities")
.addTag("Support", "FAQ management, search, live chat conversations, and ticket resolution")
.addTag("Tickets", "QR/barcode generation, PDF tickets, gate validation with per-leg tracking (OUTBOUND/RETURN/LEG1/LEG2/OUTBOUND_LEG1/OUTBOUND_LEG2/RETURN_LEG1/RETURN_LEG2), and audit trails")
.addTag("Transit Stops", "Cross-border journey management, Dire Dawa hub, TRANSIT and ROUND_TRIP_TRANSIT bookings")
.addTag("Wallet", "Balance management, top-ups, withdrawals, and transaction ledger")
//.addServer('http://localhost:4000', 'Development')
// .addServer("https://api.edr-platform.com", "Production")
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup("api-docs", app, document, {
customSiteTitle: "EDR Passenger API",
swaggerOptions: {
persistAuthorization: true,
docExpansion: "none",
filter: true,
tagsSorter: "alpha",
operationsSorter: "alpha",
},
});
const port = process.env.PORT ?? 4000;
await app.listen(port);
console.log(`🚀 EDR Passenger API running on port ${port}`);
console.log(`📚 Swagger: http://localhost:${port}/api-docs`);
}
bootstrap();