feat: fallback token admin GET + Private Pages persistantes
FlowDeck CI / test (push) Failing after 17s
FlowDeck CI / docker (push) Has been skipped

1. _require_gitea: fallback token admin pour les GET (orgs, projects, tree, file)
   _require_user_gitea: token OAuth requis pour les write (save, delete)

2. Private Pages: table gitea_private_pages (user_id, owner, repo, title, content)
   API CRUD: GET/POST/PUT/DELETE /api/gitea/projects/{o}/{r}/private-pages
   UI: liste + éditeur dans le workspace Gitea, lien 🔒 Private Pages dans sidebar
   Lié logiquement au projet (owner+repo), conservé entre sessions
This commit is contained in:
2026-07-13 17:01:19 -04:00
parent 6b000d892b
commit caa00c54bc
3 changed files with 257 additions and 6 deletions
+117 -3
View File
@@ -6,7 +6,16 @@ router = APIRouter(tags=["gitea"], prefix="/api/gitea")
def _require_gitea(request: Request):
"""Return a per-user GiteaClient or raise 401."""
"""Return a per-user GiteaClient, falling back to admin token for read-only ops."""
from app.services.gitea_client import get_user_gitea_client, GiteaClient
client = get_user_gitea_client(request)
if not client:
client = GiteaClient() # fallback to server admin token
return client
def _require_user_gitea(request: Request):
"""Return a per-user GiteaClient or raise 401 (required for write ops)."""
from app.services.gitea_client import get_user_gitea_client
client = get_user_gitea_client(request)
if not client:
@@ -79,7 +88,7 @@ async def get_file(request: Request, owner: str, repo: str, path: str):
async def save_file(request: Request, owner: str, repo: str):
"""Create or update a file in the repo."""
import json
gitea = _require_gitea(request)
gitea = _require_user_gitea(request)
try:
body = await request.json()
except Exception:
@@ -102,7 +111,7 @@ async def save_file(request: Request, owner: str, repo: str):
async def delete_file(request: Request, owner: str, repo: str):
"""Delete a file from the repo."""
import json
gitea = _require_gitea(request)
gitea = _require_user_gitea(request)
path = request.query_params.get("path", "")
sha = request.query_params.get("sha", "")
message = request.query_params.get("message", "Delete via FlowDeck")
@@ -152,3 +161,108 @@ async def disconnect_gitea(request: Request):
conn.execute("DELETE FROM user_oauth_tokens WHERE user_id=? AND provider='gitea'", (user["id"],))
conn.commit()
return {"status": "ok"}
# ── Private Pages (local FlowDeck pages linked to Gitea project) ──
@router.get("/projects/{owner}/{repo}/private-pages")
async def list_private_pages(owner: str, repo: str, request: Request):
"""List private pages for this Gitea project."""
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({"pages": []})
with get_conn() as conn:
rows = conn.execute(
"SELECT id, title, updated_at FROM gitea_private_pages WHERE user_id=? AND gitea_owner=? AND gitea_repo=? ORDER BY updated_at DESC",
(user["id"], owner, repo),
).fetchall()
return {"pages": [{"id": r["id"], "title": r["title"], "updated_at": r["updated_at"]} for r in rows]}
@router.post("/projects/{owner}/{repo}/private-pages")
async def create_private_page(owner: str, repo: str, request: Request):
"""Create a new private page for this Gitea project."""
from app.auth.session import SessionManager
from app.db import get_conn
import json
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
if not user:
return JSONResponse({"error": "Not authenticated"}, status_code=401)
try:
body = await request.json()
except Exception:
body = {}
title = body.get("title", "Untitled").strip() or "Untitled"
with get_conn() as conn:
cursor = conn.execute(
"INSERT INTO gitea_private_pages (user_id, gitea_owner, gitea_repo, title) VALUES (?,?,?,?)",
(user["id"], owner, repo, title),
)
conn.commit()
return {"status": "ok", "page": {"id": cursor.lastrowid, "title": title}}
@router.get("/projects/{owner}/{repo}/private-pages/{page_id}")
async def get_private_page(owner: str, repo: str, page_id: int, request: Request):
"""Get a single private page."""
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:
row = conn.execute(
"SELECT * FROM gitea_private_pages WHERE id=? AND user_id=? AND gitea_owner=? AND gitea_repo=?",
(page_id, user["id"], owner, repo),
).fetchone()
if not row:
return JSONResponse({"error": "Not found"}, status_code=404)
return {"page": {"id": row["id"], "title": row["title"], "content": row["content"], "updated_at": row["updated_at"]}}
@router.put("/projects/{owner}/{repo}/private-pages/{page_id}")
async def update_private_page(owner: str, repo: str, page_id: int, request: Request):
"""Update a private page."""
from app.auth.session import SessionManager
from app.db import get_conn
import json
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
if not user:
return JSONResponse({"error": "Not authenticated"}, status_code=401)
try:
body = await request.json()
except Exception:
body = {}
title = body.get("title", "").strip()
content = body.get("content", "")
with get_conn() as conn:
if title:
conn.execute(
"UPDATE gitea_private_pages SET title=?, updated_at=CURRENT_TIMESTAMP WHERE id=? AND user_id=? AND gitea_owner=? AND gitea_repo=?",
(title, page_id, user["id"], owner, repo),
)
conn.execute(
"UPDATE gitea_private_pages SET content=?, updated_at=CURRENT_TIMESTAMP WHERE id=? AND user_id=? AND gitea_owner=? AND gitea_repo=?",
(content, page_id, user["id"], owner, repo),
)
conn.commit()
return {"status": "ok"}
@router.delete("/projects/{owner}/{repo}/private-pages/{page_id}")
async def delete_private_page(owner: str, repo: str, page_id: int, request: Request):
"""Delete a private page."""
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 gitea_private_pages WHERE id=? AND user_id=? AND gitea_owner=? AND gitea_repo=?",
(page_id, user["id"], owner, repo),
)
conn.commit()
return {"status": "ok"}