from fastapi import HTTPException, APIRouter from pydantic import BaseModel from typing import Optional router = APIRouter() class CommitPayload(BaseModel): message: str repo: Optional[str] = None class PullPayload(BaseModel): repo: str @router.get("/repos") async def get_repos(): # Part 19: real GitHub API call via the vaulted token (same source the commit # engine already uses). No token -> honest 503, never a mocked repo list. from backend.services.usb_vault import get_secret token = (get_secret("GITHUB_TOKEN") or "").strip() if not token: raise HTTPException(status_code=503, detail="GITHUB_TOKEN is not configured in the vault/environment.") import httpx async with httpx.AsyncClient(timeout=30.0) as client: resp = await client.get( "https://api.github.com/user/repos?per_page=50&sort=updated", headers={"Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json"}, ) if resp.status_code != 200: raise HTTPException(status_code=502, detail=f"GitHub API returned {resp.status_code}.") return [{"name": r.get("name"), "url": r.get("html_url"), "private": bool(r.get("private"))} for r in resp.json()] @router.post("/commit") async def do_commit(p: CommitPayload): from backend.services.github_service import execute_commit result = await execute_commit(p.message) if result.get("status") == "error": raise HTTPException(status_code=500, detail=result.get("message", "Commit failed")) return result @router.get("/oauth/start") async def oauth_start(): # Part 19: real client id from the vault/env (GITHUB_OAUTH_CLIENT_ID is a # registered key domain). Unconfigured -> honest 503, not a placeholder URL. from backend.services.usb_vault import get_secret client_id = (get_secret("GITHUB_OAUTH_CLIENT_ID") or "").strip() if not client_id: raise HTTPException(status_code=503, detail="GITHUB_OAUTH_CLIENT_ID is not configured.") return {"url": f"https://github.com/login/oauth/authorize?client_id={client_id}&scope=repo"} @router.post("/oauth/callback") async def oauth_callback(code: str): # Part 19: real code->token exchange, storing the token in the vault. The old # mock returned {"status":"ok","token":"gho_mock_..."} — faking success AND # echoing a token-shaped string to the client. Real tokens are never echoed. from backend.services.usb_vault import get_secret, set_secret client_id = (get_secret("GITHUB_OAUTH_CLIENT_ID") or "").strip() client_secret = (get_secret("GITHUB_OAUTH_CLIENT_SECRET") or "").strip() if not client_id or not client_secret: raise HTTPException(status_code=503, detail="GitHub OAuth app credentials are not configured.") import httpx async with httpx.AsyncClient(timeout=30.0) as client: resp = await client.post( "https://github.com/login/oauth/access_token", data={"client_id": client_id, "client_secret": client_secret, "code": code}, headers={"Accept": "application/json"}, ) tok = resp.json().get("access_token") if resp.status_code == 200 else None if not tok: raise HTTPException(status_code=502, detail="GitHub token exchange failed.") set_secret("GITHUB_TOKEN", tok) return {"status": "ok"}