diff --git a/app/db.py b/app/db.py index aa83900..ca466ec 100644 --- a/app/db.py +++ b/app/db.py @@ -395,6 +395,20 @@ def init_db(): except sqlite3.OperationalError: pass conn.commit() + # v2.7: Private pages for Gitea workspaces + conn.execute(""" + CREATE TABLE IF NOT EXISTS gitea_private_pages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL REFERENCES users(id), + gitea_owner TEXT NOT NULL, + gitea_repo TEXT NOT NULL, + title TEXT NOT NULL DEFAULT 'Untitled', + content TEXT DEFAULT '', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """) + conn.commit() @contextmanager diff --git a/app/routers/gitea.py b/app/routers/gitea.py index 3ff7698..8983746 100644 --- a/app/routers/gitea.py +++ b/app/routers/gitea.py @@ -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"} diff --git a/app/templates/gitea_workspace.html b/app/templates/gitea_workspace.html index bb56534..f2c9753 100644 --- a/app/templates/gitea_workspace.html +++ b/app/templates/gitea_workspace.html @@ -82,11 +82,52 @@ -