feat: local workspace — file/folder tree with CRUD, empty for local accounts
- /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:
@@ -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"}
|
||||
|
||||
Reference in New Issue
Block a user