""" Main FastAPI application for GSPro Remote backend. """ import logging from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from .api import actions, config, system, vision from .core.config import AppConfig, get_config from .core.mdns import MDNSService # Configure logging logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") logger = logging.getLogger(__name__) @asynccontextmanager async def lifespan(app: FastAPI): """ Application lifespan manager for startup/shutdown tasks. """ # Startup logger.info("Starting GSPro Remote backend v0.1.0") # Load configuration config = get_config() logger.info(f"Configuration loaded from {config.config_path}") # Start mDNS service if enabled mdns_service = None if config.server.mdns_enabled: try: mdns_service = MDNSService(name="gsproapp", port=config.server.port, service_type="_http._tcp.local.") mdns_service.start() logger.info(f"mDNS service started: gsproapp.local:{config.server.port}") except Exception as e: logger.warning(f"Failed to start mDNS service: {e}") yield # Shutdown logger.info("Shutting down GSPro Remote backend") # Stop mDNS service if mdns_service: mdns_service.stop() logger.info("mDNS service stopped") # Save configuration config.save() logger.info("Configuration saved") def create_app() -> FastAPI: """ Create and configure the FastAPI application. """ app = FastAPI( title="GSPro Remote", version="0.1.0", description="Remote control API for GSPro golf simulator", docs_url="/api/docs", redoc_url="/api/redoc", openapi_url="/api/openapi.json", lifespan=lifespan, ) # Configure CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], # In production, specify actual origins allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Mount API routers app.include_router(actions.router, prefix="/api/actions", tags=["Actions"]) app.include_router(config.router, prefix="/api/config", tags=["Configuration"]) app.include_router(vision.router, prefix="/api/vision", tags=["Vision"]) app.include_router(system.router, prefix="/api/system", tags=["System"]) # Serve frontend UI if built ui_path = Path(__file__).parent.parent / "ui" if ui_path.exists(): app.mount("/ui", StaticFiles(directory=str(ui_path), html=True), name="ui") logger.info(f"Serving UI from {ui_path}") # Root redirect @app.get("/") async def root(): return { "name": "GSPro Remote", "version": "0.1.0", "status": "running", "ui": "/ui" if ui_path.exists() else None, "docs": "/api/docs", } return app # Create the application instance app = create_app() if __name__ == "__main__": import uvicorn config = get_config() uvicorn.run("app.main:app", host=config.server.host, port=config.server.port, reload=True, log_level="info")