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
155 lines
5.4 KiB
Python
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"}
|