fyliu's picture
Add flight booking flow with payment validation
d6f7eeb
"""FastAPI application — flight search backend."""
import os
import time
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from .api import airports, auth, booking, calendar, search
from .data_loader import get_route_graph
from .hub_detector import compute_hub_scores
app = FastAPI(title="Flight Search API", version="1.0.0")
# CORS for development
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Passkey middleware — protects /api/* routes when REQUIRE_PASSKEY is enabled
class PasskeyMiddleware(BaseHTTPMiddleware):
EXEMPT_PREFIXES = ("/api/auth/", "/api/health")
async def dispatch(self, request: Request, call_next):
if not auth._require_passkey():
return await call_next(request)
path = request.url.path
if path.startswith("/api/") and not any(
path.startswith(p) for p in self.EXEMPT_PREFIXES
):
token = request.cookies.get("flight_auth")
if token != auth.AUTH_TOKEN:
return JSONResponse(
status_code=401, content={"detail": "Not authenticated"}
)
return await call_next(request)
app.add_middleware(PasskeyMiddleware)
# Register API routers
app.include_router(auth.router)
app.include_router(airports.router)
app.include_router(search.router)
app.include_router(calendar.router)
app.include_router(booking.router)
@app.on_event("startup")
async def startup():
"""Load data and compute hub scores on startup."""
t0 = time.time()
graph = get_route_graph()
hubs = compute_hub_scores(graph)
elapsed = time.time() - t0
print(f"Loaded {len(graph.airports)} airports, {len(hubs)} hubs in {elapsed:.1f}s")
@app.get("/api/health")
async def health():
graph = get_route_graph()
return {"status": "ok", "airports": len(graph.airports)}
# Serve frontend static files (production)
STATIC_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "frontend", "dist")
if os.path.isdir(STATIC_DIR):
app.mount("/assets", StaticFiles(directory=os.path.join(STATIC_DIR, "assets")), name="assets")
@app.get("/{full_path:path}")
async def serve_frontend(full_path: str):
"""Serve the React SPA for all non-API routes."""
file_path = os.path.join(STATIC_DIR, full_path)
if os.path.isfile(file_path):
return FileResponse(file_path)
return FileResponse(os.path.join(STATIC_DIR, "index.html"))