""" GigLez FastAPI Application Environment-aware API server supporting both development (Termux) and production (Server) modes """ from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.gzip import GZipMiddleware from fastapi.responses import JSONResponse, HTMLResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from contextlib import asynccontextmanager from loguru import logger from pathlib import Path import time import sys # Add project root to Python path sys.path.insert(0, str(Path(__file__).parent.parent.parent)) from config.settings import settings from config.database import get_db_config # ============================================================================= # LIFESPAN MANAGEMENT # ============================================================================= @asynccontextmanager async def lifespan(app: FastAPI): """ Application lifespan manager Handles startup and shutdown events """ # Startup logger.info("=" * 80) logger.info("GigLez API Starting") logger.info("=" * 80) # Display configuration settings.display() # Validate configuration errors = settings.validate() if errors: logger.error("Configuration validation failed:") for error in errors: logger.error(f" ❌ {error}") raise RuntimeError("Invalid configuration") # Test database connection db_config = get_db_config() if not db_config.test_connection(): raise RuntimeError("Database connection failed") if not db_config.test_postgis(): raise RuntimeError("PostGIS extension not available") logger.success("✅ Database connection established") logger.success("✅ PostGIS extension available") # Initialize storage backend from src.core.storage import get_storage_backend storage = get_storage_backend() logger.success(f"✅ Storage backend initialized: {type(storage).__name__}") # Initialize background task system if settings.use_celery: logger.info("Using Celery for background tasks") # Celery app will be imported when needed else: logger.info("Using synchronous background tasks") logger.success("=" * 80) logger.success(f"🚀 GigLez API ready on {settings.api_host}:{settings.api_port}") logger.success(f"📡 Mode: {settings.mode.value}") logger.success("=" * 80) yield # Shutdown logger.info("=" * 80) logger.info("GigLez API Shutting Down") logger.info("=" * 80) # Close database connections db_config.close() logger.success("✅ Database connections closed") logger.info("Shutdown complete") # ============================================================================= # APPLICATION INSTANCE # ============================================================================= app = FastAPI( title="GigLez API", description="IoT RF Device Mapping Platform - Wigle for Sub-GHz Signals", version="1.0.0", lifespan=lifespan, docs_url="/docs" if settings.debug_endpoints else None, redoc_url="/redoc" if settings.debug_endpoints else None, openapi_url="/openapi.json" if settings.debug_endpoints else None, ) # Mount static files and templates BASE_DIR = Path(__file__).parent.parent.parent app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static") templates = Jinja2Templates(directory=str(BASE_DIR / "templates")) # ============================================================================= # MIDDLEWARE # ============================================================================= # CORS Middleware app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], expose_headers=["X-Total-Count", "X-Page-Size"], ) # GZip compression app.add_middleware(GZipMiddleware, minimum_size=1000) # Request timing middleware @app.middleware("http") async def add_process_time_header(request: Request, call_next): """Add request processing time header""" start_time = time.time() response = await call_next(request) process_time = time.time() - start_time response.headers["X-Process-Time"] = f"{process_time:.4f}" return response # Request logging middleware @app.middleware("http") async def log_requests(request: Request, call_next): """Log all requests""" logger.info(f"{request.method} {request.url.path}") response = await call_next(request) logger.info(f"{request.method} {request.url.path} - {response.status_code}") return response # ============================================================================= # EXCEPTION HANDLERS # ============================================================================= @app.exception_handler(Exception) async def global_exception_handler(request: Request, exc: Exception): """Global exception handler""" logger.error(f"Unhandled exception: {exc}", exc_info=True) if settings.debug_errors: # Detailed error in development return JSONResponse( status_code=500, content={ "error": "Internal Server Error", "detail": str(exc), "type": type(exc).__name__ } ) else: # Generic error in production return JSONResponse( status_code=500, content={"error": "Internal Server Error"} ) # ============================================================================= # ROOT ENDPOINTS # ============================================================================= @app.get("/", response_class=HTMLResponse) async def root(request: Request): """Root endpoint - Serve web interface""" return templates.TemplateResponse("index.html", {"request": request}) @app.get("/api") async def api_root(): """API information endpoint""" return { "name": "GigLez API", "version": "1.0.0", "description": "IoT RF Device Mapping Platform", "mode": settings.mode.value, "docs": "/docs" if settings.debug_endpoints else "disabled", "status": "operational" } @app.get("/health") async def health(): """Health check endpoint""" # Test database connection db_config = get_db_config() db_healthy = db_config.test_connection() return { "status": "healthy" if db_healthy else "unhealthy", "database": "connected" if db_healthy else "disconnected", "mode": settings.mode.value, "hardware_enabled": settings.enable_hardware } @app.get("/version") async def version(): """Version information""" return { "version": "1.0.0", "mode": settings.mode.value, "api": "v1" } # ============================================================================= # IMPORT ROUTERS # ============================================================================= # Import and register route modules from src.api.routes import captures, devices, query, stats # Register routers app.include_router(captures.router, prefix="/api/v1", tags=["captures"]) app.include_router(devices.router, prefix="/api/v1", tags=["devices"]) app.include_router(query.router, prefix="/api/v1", tags=["query"]) app.include_router(stats.router, prefix="/api/v1", tags=["statistics"]) # Hardware endpoints (development mode only) if settings.enable_hardware: from src.api.routes import hardware app.include_router(hardware.router, prefix="/api/v1", tags=["hardware"]) logger.info("✅ Hardware endpoints enabled") # Debug endpoints (development mode only) if settings.debug_endpoints: from src.api.routes import debug app.include_router(debug.router, prefix="/api/debug", tags=["debug"]) logger.info("✅ Debug endpoints enabled") # ============================================================================= # STARTUP MESSAGE # ============================================================================= logger.info("FastAPI application initialized") logger.info(f"Mode: {settings.mode.value}") logger.info(f"Authentication: {'Required' if settings.require_auth else 'Optional'}") logger.info(f"CORS Origins: {settings.cors_origins}") logger.info(f"Storage: {settings.storage_type.value}") # ============================================================================= # RUN WITH UVICORN (for development) # ============================================================================= if __name__ == "__main__": import uvicorn uvicorn.run( "src.api.main:app", host=settings.api_host, port=settings.api_port, reload=settings.auto_reload, log_level=settings.log_level.lower(), access_log=True, )