from contextlib import asynccontextmanager from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi.middleware.cors import CORSMiddleware from app.config import get_settings from app.database import engine from app.models import Base from app.routers import auth, users, children, subjects, schedules, sessions, logs, dashboard from app.websocket.manager import manager settings = get_settings() @asynccontextmanager async def lifespan(app: FastAPI): # Create tables on startup (Alembic handles migrations in prod, this is a safety net) async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield app = FastAPI( title="Homeschool API", version="1.0.0", docs_url="/api/docs", redoc_url="/api/redoc", openapi_url="/api/openapi.json", lifespan=lifespan, ) app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins_list, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Routers app.include_router(auth.router) app.include_router(users.router) app.include_router(children.router) app.include_router(subjects.router) app.include_router(schedules.router) app.include_router(sessions.router) app.include_router(logs.router) app.include_router(dashboard.router) @app.get("/api/health") async def health(): return {"status": "ok"} @app.websocket("/ws/{child_id}") async def websocket_endpoint(websocket: WebSocket, child_id: int): await manager.connect(websocket, child_id) try: while True: # Keep connection alive; TV clients are receive-only await websocket.receive_text() except WebSocketDisconnect: manager.disconnect(websocket, child_id)