"""FlowDeck — Gitea integration API routes.""" from fastapi import APIRouter, Request, HTTPException from fastapi.responses import JSONResponse router = APIRouter(tags=["gitea"], prefix="/api/gitea") def _require_gitea(request: Request): """Return a per-user GiteaClient or raise 401.""" from app.services.gitea_client import get_user_gitea_client client = get_user_gitea_client(request) if not client: raise HTTPException(status_code=401, detail="Gitea not connected. Go to Settings → Integrations or register with Gitea.") return client # ── Orgs ── @router.get("/orgs") async def list_orgs(request: Request): """List organizations the user belongs to.""" gitea = _require_gitea(request) try: orgs = await gitea.get_user_orgs() return {"orgs": orgs} except Exception as e: return JSONResponse({"error": str(e)}, status_code=502) # ── Projects (repos) ── @router.get("/projects") async def list_projects(request: Request, org: str = ""): """List Gitea repos: user repos (org='') or org repos.""" gitea = _require_gitea(request) try: if org: repos = await gitea.get_org_repos(org) else: repos = await gitea.get_user_repos() return {"projects": repos} except Exception as e: return JSONResponse({"error": str(e)}, status_code=502) # ── Repo tree ── @router.get("/projects/{owner}/{repo}/tree") async def repo_tree(request: Request, owner: str, repo: str, path: str = ""): """Get contents of a repo directory (one level).""" gitea = _require_gitea(request) try: items = await gitea.get_repo_contents(owner, repo, path) tree = [] for item in items: tree.append({ "name": item.get("name"), "path": item.get("path"), "type": "folder" if item.get("type") == "dir" else "file", "size": item.get("size", 0), "sha": item.get("sha"), }) return {"tree": tree} except Exception as e: return JSONResponse({"error": str(e)}, status_code=502) # ── File content ── @router.get("/projects/{owner}/{repo}/file") async def get_file(request: Request, owner: str, repo: str, path: str): """Get file content (decoded).""" gitea = _require_gitea(request) try: content = await gitea.get_file_content(owner, repo, path) return {"content": content, "path": path} except Exception as e: return JSONResponse({"error": str(e)}, status_code=502) # ── Save file (create or update) ── @router.put("/projects/{owner}/{repo}/file") async def save_file(request: Request, owner: str, repo: str): """Create or update a file in the repo.""" import json gitea = _require_gitea(request) try: body = await request.json() except Exception: body = {} path = body.get("path", "").strip("/") content = body.get("content", "") message = body.get("message", "Update via FlowDeck") sha = body.get("sha") if not path: return JSONResponse({"error": "Path required"}, status_code=400) try: result = await gitea.create_or_update_file(owner, repo, path, content, message, sha) return {"status": "ok", "commit": result.get("commit", {})} except Exception as e: return JSONResponse({"error": str(e)}, status_code=502) # ── Delete file ── @router.delete("/projects/{owner}/{repo}/file") async def delete_file(request: Request, owner: str, repo: str): """Delete a file from the repo.""" import json gitea = _require_gitea(request) path = request.query_params.get("path", "") sha = request.query_params.get("sha", "") message = request.query_params.get("message", "Delete via FlowDeck") if not path or not sha: return JSONResponse({"error": "Path and SHA required"}, status_code=400) try: await gitea.delete_file(owner, repo, path, sha, message) return {"status": "ok"} except Exception as e: return JSONResponse({"error": str(e)}, status_code=502) # ── Labels ── @router.get("/projects/{owner}/{repo}/labels") async def get_labels(request: Request, owner: str, repo: str): """Get labels for a repo (mapped to tags).""" gitea = _require_gitea(request) try: labels = await gitea.get_labels(owner, repo) return {"labels": [ {"id": l["id"], "name": l["name"], "color": l.get("color", "#787774")} for l in labels ]} except Exception as e: return JSONResponse({"error": str(e)}, status_code=502) # ── Account linking ── @router.get("/status") async def gitea_status(request: Request): """Check if the current user has Gitea linked.""" from app.services.gitea_client import get_user_gitea_client client = get_user_gitea_client(request) return {"linked": client is not None} @router.delete("/disconnect") async def disconnect_gitea(request: Request): """Remove all Gitea OAuth tokens for the current user.""" from app.auth.session import SessionManager from app.db import get_conn user = SessionManager.decode_session(request.cookies.get("flowdeck_session", "")) if not user: return JSONResponse({"error": "Not authenticated"}, status_code=401) with get_conn() as conn: conn.execute("DELETE FROM user_oauth_tokens WHERE user_id=? AND provider='gitea'", (user["id"],)) conn.commit() return {"status": "ok"}