import logging from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse from config import load_config from mqtt_bridge import start_mqtt from routes.config_routes import router as config_router from routes.ws_routes import router as ws_router logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s") logger = logging.getLogger(__name__) FRONTEND_DIR = Path(__file__).parent.parent / "frontend" / "dist" @asynccontextmanager async def lifespan(app: FastAPI): config = load_config() logger.info(f"Loaded config: {config.title} with {len(config.cameras)} cameras") start_mqtt() yield app = FastAPI(title="Camera Viewer", lifespan=lifespan) app.include_router(config_router) app.include_router(ws_router) # Serve frontend static files if FRONTEND_DIR.exists(): app.mount("/assets", StaticFiles(directory=FRONTEND_DIR / "assets"), name="assets") @app.get("/{full_path:path}") async def serve_frontend(full_path: str): # Try to serve the exact file first file_path = FRONTEND_DIR / full_path if full_path and file_path.exists() and file_path.is_file(): return FileResponse(file_path) # Fall back to index.html for SPA routing return FileResponse(FRONTEND_DIR / "index.html")