Spaces:
Runtime error
Runtime error
| """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) | |
| 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") | |
| 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") | |
| 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")) | |