import asyncio from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from fastapi.staticfiles import StaticFiles from src.gitea_proxy import activity_events, current_user, repos, issues, pull_requests from src.models import User, Repo, Issue, PullRequest from src.suggestion_engine import compute from src.views import router as frontend_router app = FastAPI(title="Stackchain Dashboard") CONTEXT_TIMEOUT_SECONDS = 5.0 app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=False, allow_methods=["*"], allow_headers=["*"], ) app.include_router(frontend_router) @app.get("/healthz") def health() -> dict[str, str]: """Return process liveness without depending on Gitea.""" return {"status": "ok", "service": "stackchain-dashboard"} @app.get("/readyz") async def readiness(): """Return readiness after verifying the configured Gitea connection.""" try: user = await current_user() except Exception as exc: return JSONResponse( { "status": "not_ready", "service": "stackchain-dashboard", "error": str(exc), }, status_code=503, ) return { "status": "ready", "service": "stackchain-dashboard", "gitea_user": user["login"], } @app.get("/api/v1/context") async def context() -> JSONResponse: try: user_data, repo_data, issues_data, prs_data = await asyncio.wait_for( asyncio.gather(current_user(), repos(), issues(), pull_requests()), timeout=CONTEXT_TIMEOUT_SECONDS, ) except Exception as e: error_message = ( f"Gitea context request timed out after {CONTEXT_TIMEOUT_SECONDS:g}s" if isinstance(e, TimeoutError) else str(e) ) return JSONResponse({ "user": {"id": None, "login": "timmy", "full_name": "Timmy Jr", "email": ""}, "repos": [], "issues": [], "pull_requests": [], "view": "dashboard", "deltas": [ {"panel": "auth", "action": "connect", "target": "stackchain-dashboard backend", "priority": "high"}, {"panel": "gitea", "action": "set GITEA_URL + GITEA_TOKEN", "target": "/root/stackchain-dashboard/.env or systemd env", "priority": "high"}, ], "error": error_message, }) user_model = User(id=user_data["id"], login=user_data["login"], full_name=user_data.get("full_name", ""), email=user_data.get("email", "")) repo_models = [ Repo(id=r["id"], name=r["name"], full_name=r["full_name"], description=r.get("description", ""), url=r["html_url"], updated_at=r.get("updated_at", "")) for r in repo_data[:50] if isinstance(r, dict) ] issue_models = [ Issue(id=i["id"], number=i["number"], title=i["title"], state=i["state"], labels=[l.get("name", "") for l in i.get("labels", [])], assignees=[a.get("login", "") for a in i.get("assignees", [])], url=i["html_url"]) for i in issues_data[:50] if isinstance(i, dict) ] pr_models = [ PullRequest(id=p["id"], number=p["number"], title=p["title"], state=p["state"], user=p.get("user", {}).get("login", ""), url=p["html_url"]) for p in prs_data[:50] if isinstance(p, dict) ] ctx = compute(user_model, repo_models, issue_models, pr_models) return JSONResponse({ "user": ctx.user.dict(), "repos": [r.dict() for r in ctx.repos], "issues": [i.dict() for i in ctx.issues], "pull_requests": [p.dict() for p in ctx.pull_requests], "view": ctx.view, "deltas": [d.dict() for d in ctx.deltas], }) @app.get("/api/v1/events") async def event_stream() -> list[dict]: return await activity_events()