from fastapi import FastAPI, Depends, HTTPException, status from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from sqlalchemy.orm import Session import os from backend.core.config import settings from backend.api.v1 import ( auth as auth_router, challenges as challenges_router, health as health_router, zepp as zepp_router ) from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.util import get_remote_address from slowapi.errors import RateLimitExceeded # Rate limiting setup limiter = Limiter(key_func=get_remote_address) app = FastAPI(title=settings.APP_NAME, version=settings.APP_VERSION) app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) # Create database tables on startup @app.on_event("startup") def on_startup(): from backend.database import Base, engine from backend.models import User, Challenge, Tracking, DailyHealthMetrics, WorkoutSession Base.metadata.create_all(bind=engine) # CORS - Use configured origins app.add_middleware( CORSMiddleware, allow_origins=settings.CORS_ORIGINS, allow_credentials=settings.CORS_ALLOW_CREDENTIALS, allow_methods=settings.CORS_ALLOW_METHODS, allow_headers=settings.CORS_ALLOW_HEADERS, ) # Include API v1 routers app.include_router(auth_router.router) app.include_router(challenges_router.router) app.include_router(health_router.router) app.include_router(zepp_router.router) # Middleware to drop unexpected WebSocket connections (Fixes StaticFiles crash) @app.middleware("http") async def reject_websockets(request, call_next): return await call_next(request) from starlette.websockets import WebSocket @app.websocket("/{path:path}") async def websocket_endpoint(websocket: WebSocket): await websocket.accept() await websocket.close() @app.get("/api/static/images") def get_static_images(): """Retrieve list of available static images.""" static_dir = os.path.join("frontend", "static") images = [] if os.path.exists(static_dir): for filename in os.listdir(static_dir): if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp')): images.append(filename) return images # Mount static files last to avoid intercepting API calls app.mount("/", StaticFiles(directory="frontend", html=True), name="frontend")