"""FastAPI entrypoint for the ImpactFlow self-discovery module.""" import os from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import RedirectResponse from fastapi.staticfiles import StaticFiles from starlette.middleware.sessions import SessionMiddleware from app import models # noqa: F401 - register ORM models with Base.metadata from app.auth import ensure_api_key_admin from app.database import AsyncSessionLocal, Base, engine from app.routers import ( activity, auth as auth_router, coaching, discovery, integration, ) from app.routers.activity import prune_old_activity from app.tracking import ActivityTrackingMiddleware # Path to the static directory, resolved relative to this file so it works # regardless of the current working directory. STATIC_DIR = os.path.join(os.path.dirname(__file__), "static") @asynccontextmanager async def lifespan(app: FastAPI): # Alembic owns schema migrations in Docker; this create_all is an # idempotent safety net so the app also runs cleanly in local/dev where # migrations may not have been applied. async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) async with AsyncSessionLocal() as db: await ensure_api_key_admin(db) await prune_old_activity(db) yield app = FastAPI(title="ImpactFlow Self-Discovery", lifespan=lifespan) # add_middleware wraps the previous app, so the LAST call becomes the # OUTERMOST middleware. Register inner-to-outer: # tracking (innermost — sees request.state.user set by deps) -> # session (needs to wrap routes for the OAuth state cookie) -> # CORS (outermost — preflights must short-circuit before anything else). app.add_middleware(ActivityTrackingMiddleware) _session_secret = os.getenv("JWT_SECRET") or "dev-session-secret-change-me" app.add_middleware(SessionMiddleware, secret_key=_session_secret) _cors_origins = [ o.strip() for o in os.getenv( "CORS_ALLOWED_ORIGINS", "http://localhost:8011" ).split(",") if o.strip() ] app.add_middleware( CORSMiddleware, allow_origins=_cors_origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.include_router(auth_router.router) app.include_router(activity.router) app.include_router(discovery.router) app.include_router(coaching.router) app.include_router(integration.router) app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") @app.get("/health") async def health(): return {"status": "ok"} @app.get("/") async def root(): return RedirectResponse(url="/static/discovery.html")