Files
flowdeck/app/routers/gitea.py
T
bruno e9d1c32b4f
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
feat: Gitea — register, settings connect, tabs org, search
1. Pas de Gitea sans config: _require_gitea() → 401 si pas de token OAuth
2. Register Gitea: boutons "Login/Register with Gitea" dynamiques selon l'onglet
3. Settings → Integrations: ✅ Active / 🔗 Connect / Disconnect + API status/disconnect
4. Workspaces: tabs par org (All | org1 | org2) + barre recherche 🔍 filtrage live
5. API: GET /api/gitea/status, DELETE /api/gitea/disconnect
2026-07-13 15:17:17 -04:00

155 lines
5.4 KiB
Python

"""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"}