mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 14:20:58 +00:00
348 lines
17 KiB
TypeScript
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();
|