feat: local workspace — file/folder tree with CRUD, empty for local accounts
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped

- /local-workspace: page with file tree for local accounts (no forge)
- API: GET/POST/PUT/DELETE /api/local-workspace/items
- Workspace is empty when no pages exist, shows create buttons
- File tree shows nested items with rename/delete actions
- Local accounts are NOT linked to Gitea (separate workspace space)
This commit is contained in:
2026-07-10 16:23:14 -04:00
parent 47463a62e0
commit 3417506062
2 changed files with 238 additions and 0 deletions
+79
View File
@@ -454,3 +454,82 @@ async def remove_member(request: Request, ws_id: int, user_id: int):
)
conn.commit()
return {"status": "ok"}
# ═══════════ Local Workspace (file/folder CRUD) ═══════════
@router.get("/local-workspace", response_class=HTMLResponse)
async def local_workspace_page(request: Request):
"""Local workspace page with file/folder tree."""
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader("app/templates"))
sidebar = _sidebar_data(request, [])
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
if not user:
return RedirectResponse("/auth/login?provider=local", status_code=302)
with get_conn() as conn:
rows = conn.execute(
"SELECT id, title FROM pages WHERE parent_id IS NULL ORDER BY created_at DESC"
).fetchall()
items = [{"id": r["id"], "name": r["title"] or "Untitled", "type": "page"} for r in rows]
ctx = {**sidebar, "user": user, "workspace_name": "My Workspace", "items": items}
template = env.get_template("local_workspace.html")
return template.render(**ctx)
@router.get("/api/local-workspace/tree")
async def local_workspace_tree(request: Request):
"""Return the file/folder tree."""
with get_conn() as conn:
roots = conn.execute(
"SELECT id, title FROM pages WHERE parent_id IS NULL ORDER BY created_at DESC"
).fetchall()
tree = []
for r in roots:
with get_conn() as conn:
children = conn.execute(
"SELECT id, title FROM pages WHERE parent_id=? ORDER BY created_at", (r["id"],)
).fetchall()
item = {"id": r["id"], "name": r["title"] or "Untitled", "type": "page",
"children": [{"id": c["id"], "name": c["title"] or "Untitled", "type": "page"} for c in children]}
tree.append(item)
return {"tree": tree}
@router.post("/api/local-workspace/items")
async def create_local_workspace_item(request: Request):
"""Create a new file."""
import json
body = await request.json()
name = body.get("name", "Untitled").strip()
with get_conn() as conn:
cursor = conn.execute(
"INSERT INTO pages (workspace, title, content, content_format, parent_section) VALUES ('', ?, '', 'blocks', 'Private')",
(name,),
)
conn.commit()
pid = cursor.lastrowid
return {"id": pid, "name": name, "type": "page"}
@router.put("/api/local-workspace/items/{item_id:int}")
async def rename_local_workspace_item(request: Request, item_id: int):
"""Rename a file."""
import json
body = await request.json()
name = body.get("name", "Untitled").strip()
with get_conn() as conn:
conn.execute("UPDATE pages SET title=? WHERE id=?", (name, item_id))
conn.commit()
return {"status": "ok"}
@router.delete("/api/local-workspace/items/{item_id:int}")
async def delete_local_workspace_item(request: Request, item_id: int):
"""Delete a file (and its children)."""
with get_conn() as conn:
conn.execute("DELETE FROM pages WHERE id=? OR parent_id=?", (item_id, item_id))
conn.commit()
return {"status": "ok"}