workspaces.html: - Section 📁 Local Workspaces (existante, avec badges) - Section 🔗 Gitea Projects (par organisation, lazy load) - États: loading, not_linked (lien pour login OAuth), error (retry), ok gitea_workspace.html: - Arbre fichiers (lazy: un dossier à la fois) - Vue fichier (lecture seule → bouton Edit) - Éditeur avec zone de commit (message + historique dropdown) - Section 🔒 Private Pages (placeholder pour pages FlowDeck locales) - Bouton Delete fichier Routing: /gitea-workspace?owner=X&repo=Y Fallback: token admin serveur si pas de token OAuth utilisateur
132 lines
4.5 KiB
Python
132 lines
4.5 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 fallback to admin token."""
|
|
from app.services.gitea_client import get_user_gitea_client, GiteaClient
|
|
client = get_user_gitea_client(request)
|
|
if not client:
|
|
# Fallback to server admin token
|
|
client = GiteaClient()
|
|
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)
|