A full-stack flight booking platform with real-time search, seat selection, integrated payments, and an admin dashboard — deployed on AWS via CI/CD.
- Overview
- Key Features
- Architecture
- Tech Stack
- Project Structure
- Getting Started
- API Reference
- Environment Variables
- Event-Driven Architecture
- Observability
- CI/CD & Deployment
- Testing & Code Quality
- License
Aero Bound Ventures is a production-grade flight booking application that integrates with the Amadeus Flight API for real-time flight data and the Pesapal Payment Gateway for secure USD transactions. It features an event-driven notification pipeline, group-based admin permissions, and full observability through Prometheus, Grafana, and Loki.
| Area | Highlights |
|---|---|
| Flight Search | Real-time search across multiple airlines via the Amadeus API (one-way & round-trip) |
| Price Confirmation | Verify live pricing before booking to prevent stale-offer errors |
| Seat Maps | Visual seat selection for booked flights |
| Booking Management | Create, view, and cancel flight bookings with PNR tracking |
| Payments | Pesapal iframe modal integration (USD only), IPN webhooks, refund support |
| Auth | JWT-based authentication with HTTP-only cookies, Google OAuth 2.0 login, password reset flow |
| Admin Dashboard | Booking stats, revenue tracking, booking management, and ticket upload (Cloudinary) |
| Notifications | Real-time via SSE (Redis Pub/Sub), with event-driven email notifications via Kafka consumers |
| Pagination | Cursor-based pagination for bookings, notifications, and admin views |
| Observability | Prometheus metrics, Grafana dashboards, Loki + Promtail for centralized logging |
┌──────────────────┐ ┌──────────────────────────────────────────────────────┐
│ Next.js 15 │ │ FastAPI Backend │
│ (React 19) │ ◄──── │ ┌────────┐ ┌────────┐ ┌─────────┐ ┌──────────┐│
│ │ REST │ │ Routers│──│ CRUD │──│ Models │──│PostgreSQL││
│ Zustand Store │ SSE │ └───┬────┘ └────────┘ └─────────┘ └──────────┘│
│ TanStack Query │ │ │ │
└──────────────────┘ │ ┌───▼────────────────┐ ┌────────────┐ │
│ │ External Services │ │ Redis │ │
│ │ ├─ Amadeus Flight │ │ (Caching + │ │
│ │ ├─ Pesapal Payment │ │ Pub/Sub) │ │
│ │ ├─ Cloudinary CDN │ └────────────┘ │
│ │ └─ Email (SMTP) │ │
│ └────────────────────┘ ┌────────────┐ │
│ │ Kafka │ │
│ ┌────────────────────┐ │ (Event Bus)│ │
│ │ Kafka Consumers │◄─┤ │ │
│ │ ├─ User Events │ └────────────┘ │
│ │ ├─ Booking Events │ │
│ │ ├─ Payment Events │ ┌────────────┐ │
│ │ └─ Ticket Events │ │Observability │
│ └────────────────────┘ │ Prometheus │ │
│ │ Grafana │ │
│ │ Loki │ │
│ └────────────┘ │
└──────────────────────────────────────────────────────┘
| Technology | Purpose |
|---|---|
| Next.js 15 (React 19) | Framework with Turbopack dev server |
| TypeScript | Type safety |
| Tailwind CSS 4 | Utility-first styling |
| Zustand | Client-side state management |
| TanStack Query | Server state management & caching |
| NProgress | Navigation progress indicator |
| React Icons | Icon library |
| Technology | Purpose |
|---|---|
| FastAPI | Async Python web framework |
| Python 3.12+ | Runtime |
| SQLModel | ORM (SQLAlchemy + Pydantic) |
| PostgreSQL 16 | Relational database |
| Redis | Caching, Pub/Sub for SSE notifications |
| Confluent Kafka | Event-driven message broker |
| Alembic | Database migrations |
| Typer | CLI management commands |
| uv | Fast Python package manager |
| Service | Purpose |
|---|---|
| Amadeus API | Flight search, pricing, booking, seat maps, cancellation ( |
| Pesapal | Payment gateway (USD only) — IPN, refunds |
| Cloudinary | Ticket file uploads & CDN |
| Google OAuth 2.0 | Social login |
| SMTP (Gmail) | Transactional email notifications |
| Technology | Purpose |
|---|---|
| Docker | Multi-stage builds & containerization |
| Docker Compose | Multi-service orchestration |
| Terraform | AWS infrastructure as code |
| GitHub Actions | CI/CD — plan, apply, deploy |
| Nginx | Reverse proxy with SSL (Certbot) |
| AWS EC2 | Application hosting |
| Technology | Purpose |
|---|---|
| Prometheus | Metrics collection |
| Grafana | Dashboards & alerting |
| Loki | Log aggregation |
| Promtail | Log shipping agent |
aero_bound_ventures/
├── backend/ # FastAPI REST API
│ ├── main.py # App entry point, lifespan events, middleware
│ ├── manage.py # CLI commands (create-super-user, create-admin)
│ ├── routers/ # API route handlers
│ │ ├── flights.py # Flight search, booking, cancellation, seat maps
│ │ ├── payments.py # Pesapal payment initiation, callbacks, IPN, refunds
│ │ ├── users.py # Registration, login, logout, password reset
│ │ ├── oauth.py # Google OAuth2 flow
│ │ ├── admin.py # Admin dashboard (stats, bookings)
│ │ ├── tickets.py # Ticket upload (Cloudinary)
│ │ ├── notifications.py # SSE streams, CRUD for notifications
│ │ └── health.py # System health check (DB, Redis, Kafka)
│ ├── models/ # SQLModel database models
│ │ ├── users.py # User model (supports local + Google auth)
│ │ ├── bookings.py # Booking model with status enum
│ │ ├── permissions.py # Group, Permission, UserGroup, UserPermission
│ │ ├── notifications.py # Notification model with type enum
│ │ └── constants.py # Admin group & permission definitions
│ ├── schemas/ # Pydantic request/response schemas
│ ├── crud/ # Database access layer
│ ├── external_services/ # Third-party API integrations
│ │ ├── interface.py # FlightServiceProtocol (Strategy pattern)
│ │ ├── flight.py # Amadeus SDK wrapper + service factory
│ │ ├── mock_flight_service.py # Mock flight service (demo mode)
│ │ ├── mock_data/ # JSON fixtures for mock mode
│ │ ├── pesapal.py # Pesapal REST client
│ │ ├── cloudinary_service.py # File upload service
│ │ ├── email.py # SMTP email sender
│ │ └── cache.py # Redis caching utilities
│ ├── consumers/ # Kafka event consumers
│ │ ├── user_notifications.py
│ │ ├── booking_notifications.py
│ │ ├── payment_notifications.py
│ │ └── ticket_notifications.py
│ ├── utils/ # Shared utilities
│ │ ├── security.py # JWT, password hashing, auth dependencies
│ │ ├── kafka.py # Kafka producer singleton
│ │ ├── consumer.py # Kafka consumer framework
│ │ ├── redis.py # Redis SSE pub/sub streams
│ │ ├── pagination.py # Cursor-based pagination utilities
│ │ ├── permissions.py # Permission checking utilities
│ │ ├── log_manager.py # Singleton logger (console + file rotation)
│ │ └── cookies.py # Cookie configuration helper
│ ├── templates/ # HTML email templates (9 templates)
│ ├── tests/ # Pytest test suite
│ ├── alembic/ # Database migration scripts
│ ├── grafana/ # Grafana provisioning configs
│ ├── Dockerfile # Multi-stage build (builder → runtime)
│ ├── compose.yaml # All services: FastAPI, PostgreSQL, Redis,
│ │ # Kafka, Kafka UI, Prometheus, Grafana, Loki, Promtail
│ ├── pyproject.toml # Python dependencies (managed by uv)
│ └── .pre-commit-config.yaml # Ruff linting/formatting + pytest
│
├── frontend/ # Next.js 15 web application
│ ├── src/
│ │ ├── app/ # App Router pages
│ │ │ ├── page.tsx # Landing page (hero, destinations, testimonials)
│ │ │ ├── flights/ # Flight search & results
│ │ │ ├── booking/ # Booking detail, payment, success
│ │ │ ├── auth/ # Login, register, forgot/reset password, Google OAuth
│ │ │ ├── admin/ # Admin dashboard & booking management
│ │ │ ├── my/ # User bookings ("My Bookings")
│ │ │ └── profile/ # User profile & settings
│ │ ├── components/ # Reusable React components
│ │ │ ├── BookingForm.tsx # Flight search form with autocomplete
│ │ │ ├── SeatMap.tsx # Interactive seat map viewer
│ │ │ ├── Navbar.tsx # Navigation with auth state
│ │ │ ├── NotificationBell.tsx # Real-time notification dropdown (SSE)
│ │ │ ├── SignupModal.tsx # Auth modal
│ │ │ └── ... # Hero, Footer, Skeletons, etc.
│ │ ├── store/ # Zustand stores (auth, flights)
│ │ ├── hooks/ # Custom hooks (useAuth, useNotifications)
│ │ ├── types/ # TypeScript type definitions
│ │ └── utils/ # Utility functions
│ ├── public/ # Static assets (logo, favicons, hero images)
│ └── package.json # npm dependencies
│
├── terraform/ # AWS infrastructure as code
│ ├── main.tf # EC2 instance, security groups, Elastic IP
│ ├── variables.tf # Input variables
│ ├── outputs.tf # Output values (public IP)
│ └── terraform.tf # Backend & provider config (S3 state)
│
├── .github/
│ └── workflows/
│ └── terraform.yml # CI/CD: Terraform plan/apply + SSH deploy
│
├── mobile/ # (Planned) Mobile application
└── shared/ # (Planned) Shared resources
- Docker & Docker Compose (v2)
- Node.js 20+ and npm
- Python 3.12+ and uv
- API credentials: Amadeus, Pesapal, Cloudinary, Google Cloud Console (for OAuth)
cd backend
# Copy and configure environment variables
cp .env.example .env
# Edit .env with your API keys and secrets (see Environment Variables section)
# Start all services (FastAPI, PostgreSQL, Redis, Kafka, Prometheus, Grafana, Loki)
docker compose up --buildThe API will be available at http://localhost:8000
cd frontend
# Install dependencies
npm install
# Copy and configure environment variables
cp .env.example .env.local
# Ensure NEXT_PUBLIC_API_BASE_URL=http://localhost:8000/api/v1
# Start development server (with Turbopack)
npm run devThe app will be available at http://localhost:3000
The backend includes a Typer-based CLI for user management:
# Create a superuser (full system access + admin group)
python -m backend.manage create-super-user
# Create an admin user (admin group permissions)
python -m backend.manage create-adminBoth commands prompt for email and password with validation (email format, password strength: uppercase, lowercase, digit, minimum length).
Once the backend is running, interactive API documentation is available at:
| Format | URL |
|---|---|
| Swagger UI | http://localhost:8000/docs |
| ReDoc | http://localhost:8000/redoc |
All endpoints are prefixed with /api/v1.
| Method | Endpoint | Description |
|---|---|---|
POST |
/users/register |
Register a new user |
POST |
/users/login |
Login (returns JWT in HTTP-only cookie) |
POST |
/users/logout |
Clear auth cookie |
POST |
/users/forgot-password |
Request password reset email |
POST |
/users/reset-password |
Reset password with token |
GET |
/users/me |
Get current user info |
PUT |
/users/change-password |
Change password |
GET |
/auth/google |
Initiate Google OAuth login |
GET |
/auth/google/callback |
Handle Google OAuth callback |
POST |
/flights/search |
Search flights (POST body) |
GET |
/flights/search |
Search flights (query params) |
POST |
/flights/pricing |
Confirm flight pricing |
POST |
/flights/order |
Create flight booking |
GET |
/flights/order/{booking_id} |
Get booking details |
DELETE |
/flights/order/{booking_id} |
Cancel flight booking |
GET |
/flights/seatmap |
View seat map for booked flight |
GET |
/flights/bookings |
Get user's bookings (cursor-paginated) |
GET |
/flights/airport-city-search |
Airport/city autocomplete |
POST |
/payments/initiate |
Initiate Pesapal payment |
GET |
/payments/callback |
Pesapal redirect callback |
GET |
/payments/ipn |
Pesapal IPN webhook |
GET |
/payments/status/{id} |
Check payment status |
POST |
/payments/refund |
Request refund |
POST |
/tickets/upload/{booking_id} |
Upload ticket PDF (admin) |
GET |
/notifications/ |
List notifications (cursor-paginated) |
GET |
/notifications/stream |
SSE notification stream |
GET |
/notifications/unread-count |
Get unread count |
PUT |
/notifications/mark-all-read |
Mark all as read |
GET |
/admin/stats/bookings |
Booking statistics (admin) |
GET |
/admin/bookings |
All bookings (admin, cursor-paginated) |
GET |
/admin/bookings/{id} |
Booking detail (admin) |
GET |
/health |
System health check |
# Database
DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres
# Security
SECRET_KEY=<your-secret-key>
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=60
# Amadeus Flight API (not needed when using mock mode)
AMADEUS_API_KEY=<your-key>
AMADEUS_API_SECRET=<your-secret>
AMADEUS_BASE_URL=test.api.amadeus.com
# Flight Service Provider: "amadeus" (live API) or "mock" (demo fixtures)
FLIGHT_SERVICE_PROVIDER=amadeus
# Pesapal Payment Gateway
PESAPAL_CONSUMER_KEY=<your-key>
PESAPAL_CONSUMER_SECRET=<your-secret>
PESAPAL_BASE_URL=https://cybqa.pesapal.com/pesapalv3
PESAPAL_IPN_ID=<your-ipn-id>
# Cloudinary (file uploads)
CLOUDINARY_URL=cloudinary://<api_key>:<api_secret>@<cloud_name>
# Redis
REDIS_URL=redis://redis:6379
# Email (SMTP)
MAIL_USERNAME=<your-email>
MAIL_PASSWORD=<your-app-password>
MAIL_FROM=<sender-email>
MAIL_PORT=587
MAIL_SERVER=smtp.gmail.com
# Google OAuth
GOOGLE_CLIENT_ID=<your-client-id>
GOOGLE_CLIENT_SECRET=<your-client-secret>
GOOGLE_REDIRECT_URI=http://localhost:8000/auth/google/callback
# Application
CORS_ORIGINS=http://localhost:3000,http://localhost
FRONTEND_URL=http://localhost:3000
ENVIRONMENT=developmentNEXT_PUBLIC_API_BASE_URL=http://localhost:8000/api/v1
NEXT_PUBLIC_WHATSAPP_NUMBER=<your-whatsapp-number>The backend uses Apache Kafka as an event bus to decouple core operations from side effects like email notifications and in-app alerts.
API Router Kafka Producer Kafka Consumer
┌──────────┐ publish() ┌──────────────┐ consume() ┌──────────────┐
│ User │ ───────────►│ user.events │ ──────────► │ Send welcome │
│ registers│ └──────────────┘ │ email + DB │
└──────────┘ │ notification │
└──────────────┘
┌──────────┐ publish() ┌──────────────┐ consume() ┌──────────────┐
│ Booking │ ───────────► │booking.events│ ──────────► │ Email user + │
│ created │ └──────────────┘ │ admin, SSE │
└──────────┘ │ push via │
│ Redis Pub/Sub│
└──────────────┘
| Topic | Events |
|---|---|
user.events |
user_registered, password_reset_requested, password_changed |
booking.events |
booking_created, booking_cancelled |
payment.events |
payment_successful, payment_failed, refund_requested |
ticket.events |
ticket_uploaded |
The frontend connects to SSE endpoints backed by Redis Pub/Sub. When a Kafka consumer processes an event, it:
- Saves a
Notificationrecord in PostgreSQL - Publishes the notification to a user-specific Redis channel
- The SSE stream picks it up and delivers it to the connected client
Heartbeats are sent every 30 seconds to keep connections alive.
The compose.yaml includes a full observability stack:
| Service | Port | Purpose |
|---|---|---|
| Prometheus | 9090 |
Scrapes FastAPI metrics via prometheus-fastapi-instrumentator |
| Grafana | 3001 |
Dashboards, alerting (default credentials: admin/admin) |
| Loki | 3100 |
Log aggregation backend |
| Promtail | — | Ships backend.log to Loki |
| Kafka UI | 8080 |
Inspect Kafka topics, consumers, and messages |
The backend uses a singleton LogManager with:
- Console output (
stdout) - Time-rotating file handler (
.logs/backend.log, 7-day retention) - Security logging from
fastapi-guardmiddleware
The project uses a single GitHub Actions workflow (.github/workflows/terraform.yml) that handles both infrastructure and application deployment.
Pull Request → Terraform Plan (review)
Main Branch Push → Terraform Apply → SSH Deploy to EC2
- Terraform Apply — provisions/updates AWS EC2 instance, security groups, and Elastic IP
- SSH into EC2 — installs Docker/Git if needed, creates swap space
- Clone/Pull — fetches latest code from the repository
- Generate
.env— injects secrets from GitHub Actions environment - Docker Compose — rebuilds and restarts all containers
- Nginx Setup — configures reverse proxy for
api.aeroboundventures.com - SSL — Certbot auto-configures HTTPS certificates
- EC2: Ubuntu 24.04, 16GB disk, configurable instance type
- Security Groups: Ports 80 (HTTP), 443 (HTTPS), 22 (SSH)
- Elastic IP: Stable public IP for DNS
- Nginx: Reverse proxy to
localhost:8000with SSL termination
cd backend
pytestThe test suite covers:
- Consumer event handlers
- CRUD operations
- External service integrations
- Health check endpoints
- Cursor-based pagination
- Password reset flow
- Seat map logic
The backend uses pre-commit with:
- Ruff — linting (
ruff-check --fix) and formatting (ruff-format) - Pytest — runs the full test suite before each commit
cd backend
pre-commit run --all-filescd backend
alembic upgrade head # Apply all pending migrations
alembic revision --autogenerate -m "description" # Generate new migrationMIT
