Skip to content

Repository files navigation

Smart Travel Aggregator

Live Demo CI Python Coverage Ruff License: MIT

A production-minded travel aggregation platform. It fans out to multiple travel providers (flights, hotels, weather, currency), normalizes their responses, ranks them, and serves the result through one clean, versioned, resilient REST API — with authentication, rate limiting, structured logging and a strong test suite baked in from the start.

Built with FastAPI + async SQLAlchemy 2.x, following a clean, layered architecture with dependency injection. The design goal is a codebase that could be handed to a team and deployed for real users, not a tutorial.


Live demo

▶ Try it live: https://smart-travel-aggregator.onrender.com/docs — hosted on Render's free tier (first request after idle wakes the instance, ~30s).

Interactive API docs (Swagger UI at /docs, ReDoc at /redoc):

API documentation — Swagger UI and ReDoc

Try it yourself in one click — the app falls back to SQLite + an in-memory cache when no Postgres/Redis is configured, so it runs on a single free web service with no external services to set up:

Deploy to Render

Flight and hotel search return data from bundled sample providers; weather and currency call free public APIs. SECRET_KEY is generated automatically by the render.yaml blueprint — nothing to paste.

First request after a period of inactivity wakes the free instance and may take ~30s; subsequent requests are fast.


Highlights

  • Aggregation core — provider adapters behind stable ports; concurrent fan-out, de-duplication, ranking, and graceful degradation (a failing provider is dropped and the response is flagged degraded, never a 500).
  • Security first — Argon2id password hashing, JWT access/refresh tokens with a type claim, email verification, password reset, logout via token revocation (denylist), RBAC, per-client rate limiting, hardened HTTP headers, and an append-only audit log. Behind a load balancer, set TRUSTED_PROXY_CIDRS so the limiter, the audit log and the access log see the real caller — X-Forwarded-For is believed only from those networks, never from a caller who simply sends the header.
  • Input validation everywhere — Pydantic v2 models and typed query parameters reject bad input at the edge; errors come back as RFC 7807 application/problem+json with a trace_id.
  • Resilience — per-provider circuit breaker, retry with exponential backoff, timeouts, and a pluggable cache (in-memory for dev/tests, Redis for prod).
  • Observability — structured JSON logs (structlog) with a request-id stamped on every line, plus access logs with latency.
  • Tested — unit + integration + API tests running the full ASGI stack, with a 90 %+ coverage gate enforced in CI.

Architecture

app/
  api/v1/         presentation layer — routers, request/response wiring
  services/       business logic — aggregation, auth, price monitoring
  domain/         entities, value objects (Money), provider ports (interfaces)
  providers/      external API adapters + the resilient HTTP client
  repositories/   data access (SQLAlchemy)
  resilience/     circuit breaker, cache
  db/             engine, session, ORM models
  core/           config, security, logging, errors, middleware, rate limiting
  workers/        Celery app + tasks (price monitoring, cleanup)
  container.py    composition root (dependency injection)
  main.py         application factory

Dependencies point inward through interfaces: routers depend on services, services depend on ports, and only adapters know about a concrete vendor. Adding a new provider is a registry change, not a rewrite.

See docs/ARCHITECTURE.md for the layer diagram and request flow.

API

Method Path Auth Description
POST /api/v1/auth/register – Create an account (sends verification)
POST /api/v1/auth/login – Obtain access + refresh tokens
POST /api/v1/auth/refresh – Exchange a refresh token
POST /api/v1/auth/logout bearer Revoke the current access/refresh token
POST /api/v1/auth/verify-email – Confirm an email with a verification token
POST /api/v1/auth/resend-verification – Re-send the verification email
POST /api/v1/auth/password-reset/request – Request a reset token by email
POST /api/v1/auth/password-reset/confirm – Set a new password with a reset token
GET /api/v1/auth/me bearer Current profile
GET /api/v1/flights/search optional Aggregated, ranked flight search
GET /api/v1/hotels/search – Aggregated, ranked hotel search
GET /api/v1/weather – Forecast for a location & date
GET /api/v1/currency/convert – Currency conversion
POST/GET/DELETE /api/v1/price-alerts bearer Manage price alerts
GET /api/v1/admin/metrics admin Dashboard metrics
GET /api/v1/health/live, /ready – Probes
GET /metrics – Prometheus exposition (RED metrics per route)

Interactive docs are served at /docs (Swagger) and /redoc when the app runs.

Example

curl "http://localhost:8000/api/v1/flights/search?origin=THR&destination=IST&departure_date=2026-08-10&sort=price"
{
  "data": [
    {
      "id": "fl_1a2b...", "airline": "Turkish Airlines", "stops": 0,
      "departure_time": "2026-08-10T06:20:00Z", "duration_minutes": 165,
      "price": { "amount_minor": 12900, "currency": "USD" },
      "provider": "globehop", "score": 0.92
    }
  ],
  "page": { "next_cursor": null, "has_more": false },
  "meta": { "degraded": false, "providers_ok": 2, "providers_failed": 0, "total": 7 }
}

Quick start

Local (Python 3.12+)

make install                 # create .venv and install deps
cp .env.example .env         # then edit SECRET_KEY etc.
make test                    # run the suite with the coverage gate
make run                     # start uvicorn on :8000

Out of the box the app uses SQLite and an in-memory cache, so it runs with no external services. Point DATABASE_URL/REDIS_URL at Postgres/Redis for a production-like setup.

REDIS_URL requires a Redis server 7.0 or newer. The shared rate limiter renews its window with EXPIRE ... NX, which older servers reject; on Redis 6 every rate-limited request fails. Leave REDIS_URL unset and the in-process limiter and cache are used instead, which is correct for a single node.

Docker

docker compose up --build

This starts the API, a Celery worker+beat, PostgreSQL 16 and Redis 7.

Providers

Real inventory (flights/hotels) sits behind paid APIs; those adapters plug into the same ports as the bundled deterministic sample provider, which lets the whole pipeline run and be tested without credentials. Free adapters are wired for weather (Open-Meteo) and currency (exchangerate.host). Recommended production providers: Amadeus (flights/hotels), OpenWeatherMap, Open Exchange Rates, OpenRouteService.

Email delivery

Verification and password-reset messages are sent over SMTP as soon as SMTP_HOST (plus SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_SENDER) is configured — the container selects the SMTP notifier from those settings.

With no SMTP host set — the default, and what the hosted demo runs — nothing is delivered: the console notifier logs that a message was produced and drops it. Those endpoints still behave correctly, but the token never reaches an inbox, so point the app at a real mail server before inviting real users.

Testing & quality

make lint      # ruff
make type      # mypy (strict-ish)
make test      # pytest + coverage (fails under 90%)

123 tests exercise security (hashing, tokens), resilience (circuit breaker, retries, cache), the aggregation algorithm, and every endpoint through the real ASGI stack against an isolated SQLite database.

Notes on scope

  • Booking & payment are intentionally out of scope — the platform searches, aggregates, plans and alerts; it never charges a card (no PCI-DSS surface).
  • The admin dashboard is exposed as JSON metrics intended for a separate SPA.
  • Python 3.14 is the aspirational target from the original brief; the code targets 3.12+ and runs on it today.

License

MIT

About

Production-grade travel aggregation API — FastAPI, async SQLAlchemy, resilient provider adapters, JWT/RBAC, structured logging, 90%+ tested.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages