Case study 02 / 26
TimeTravel
Multi-tenant B2B airline ticketing: agent booking portal, admin dashboard, agency wallets with credit lines, and a pluggable flight-provider layer.
- Status
- Commercial · private
- Domain
- web · cloud · security
- Source of claims
- Private repository README (reviewed). Source and deployment are not public.
A Turborepo monorepo — Next.js 15 web app and NestJS API sharing types, permissions and Zod schemas — where agencies search and book flights, admins confirm or reject timed booking holds, and every wallet movement is written to an immutable, Decimal-exact ledger.
01/The problem
Travel agencies booking on credit need a platform where a fare offer can't be sold twice, an agency can't silently overspend, and every currency movement can be traced — while airlines, agencies and the platform operator each see only what their role permits.
02/The system
A Turborepo monorepo — Next.js 15 web app and NestJS API sharing types, permissions and Zod schemas — where agencies search and book flights, admins confirm or reject timed booking holds, and every wallet movement is written to an immutable, Decimal-exact ledger.
- Admin dashboard to NestJS API
- Agent portal to NestJS API
- NestJS API to FlightProvider
- NestJS API to Redis
- NestJS API to WalletService
- NestJS API to PostgreSQL
- WalletService to Ledger
03/Scope
- 01Auth: JWT access/refresh cookies with rotation and reuse detection, access-token revocation on logout, per-account lockout, password reset, per-device session management and email invites.
- 02Fine-grained permission-based RBAC layered on roles, across AIRLINE, AGENCY and PLATFORM organisations.
- 03Agent portal: flight search through a pluggable provider layer — a mock provider and a full Amadeus Self-Service adapter (OAuth2, Flight Offers Search, Flight Create Orders); Sabre, Galileo, Travelport and custom-REST adapters scaffolded.
- 04Full booking flow (search → passengers → review → submit), booking history, PNR search and e-ticket PDF download; up to 9 passengers, fare and group-fare adjustments, USD/SAR pricing.
- 05Agency wallets with configurable credit lines, bank-transfer top-ups with magic-byte-validated receipt uploads and admin approval.
- 06Admin dashboard with analytics, an activity/audit log, member and session management; revenue reports by agent, route and airline with Excel and manifest exports.
04/Engineering
A hold that can't be double-sold
A booking holds its offer in a timed PENDING lock enforced by a partial unique index; expired holds release the offer automatically.
Money moves in one transaction
Confirmation and its wallet debit commit together, and cancelling a paid booking refunds the wallet in the same transaction. Balances change only through one service, under row locks, with an immutable ledger row per change.
Bookings are snapshots
Currency, segments and fare are captured at creation, so a booking never drifts when a provider's live data changes.
Vendors stay behind one interface
Every flight source implements a single FlightProvider interface; swapping providers is configuration, not code. Offers live in Redis so they survive restarts and replicas.
One schema, both sides
Shared Zod schemas validate frontend forms and backend DTOs alike; real authorization is always enforced by NestJS guards, with frontend checks for UX only.
05/Interface
Interface screenshots of this commercial product are not public. The visual above is an abstract representation of its modules — not the product itself.
06/Tech stack
- Next.js 15
- React 19
- TypeScript
- NestJS
- Prisma
- PostgreSQL
- Redis
- Zod
- Tailwind CSS 4
- shadcn/ui
- Turborepo
- Docker Compose
07/Result
Verified outcomes
- Runs end to end with Docker Compose — Postgres, Redis, API and web app, with migrations and seeding on start.
- OpenAPI documentation served by the API in development.
Known limitations
- Amadeus is the only fully implemented live provider; Sabre, Galileo, Travelport and custom-REST adapters are scaffolds.
08/Links
Private commercial codebase — no public links.
Next case study
Kinamna →