"""FastAPI application entrypoint. Opens a database connection on startup, runs Alembic migrations, registers routers, locks CORS to the configured frontend origin(s), and attaches a rate-limiter to the app (applied per-endpoint via decorators). """ from contextlib import asynccontextmanager from collections.abc import AsyncIterator from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.errors import RateLimitExceeded from slowapi.util import get_remote_address from app.config import settings from app.db.connection import connect, run_migrations from app.routers import vacation_days from app.routers.analytics import router as analytics_router from app.routers.auth import router as auth_router from app.routers.fetch_data import router as fetch_data_router from app.routers.fetch_data import scraper_router from app.routers.planned_days import router as planned_days_router from app.routers.settings import break_router, child_router, settings_child_router, tips_router from app.routers.workspace import _inv_router as invitations_router from app.routers.workspace import router as workspace_router limiter = Limiter(key_func=get_remote_address) @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncIterator[None]: # Run pending migrations before accepting requests (dev convenience). # In production, run `alembic upgrade head` as a separate deploy step. if settings.database_type == "sqlite": try: run_migrations(f"sqlite:///{settings.sqlite_path}") except Exception: pass # migrations may fail in test envs without alembic installed app.state.db_connection = await connect(settings.sqlite_path) try: yield finally: await app.state.db_connection.close() def create_app() -> FastAPI: app = FastAPI(title="Föräldraledigplannerare API", lifespan=lifespan) app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) app.add_middleware( CORSMiddleware, allow_origins=list(settings.cors_origins), allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) _register_routers(app) return app def _register_routers(app: FastAPI) -> None: app.include_router(auth_router) app.include_router(workspace_router) app.include_router(invitations_router) app.include_router(fetch_data_router) app.include_router(scraper_router) app.include_router(analytics_router) app.include_router(tips_router) app.include_router(break_router) app.include_router(child_router) app.include_router(settings_child_router) app.include_router(planned_days_router) app.include_router(vacation_days.router) app = create_app() @app.get("/health", tags=["health"]) async def health() -> dict[str, str]: return {"status": "ok"}