fix: side peek des bases repasse en vanilla JS + largeur 1100px standard (v7.49.0)
FlowDeck CI / test (push) Failing after 3h13m58s
FlowDeck CI / lint (push) Successful in 2m12s
FlowDeck CI / docker (push) Skipped

- Panneau peek: les bindings Alpine (x-data absent du conteneur) rendaient
  loovverture et le redimensionnement inoperants -> cblage direct sur le document.
- Helper unique window.fdWirePeekResize (app.js): pointer capture, 300px-90vw,
  clic=fermer, largeur persiste fd_peek_width partagee entre les 4 peeks.
- database-table-container margin:0 (tableau colle a gauche, marge Library).
- .lib-container remonte dans app.css (trash etait pleine largeur), .db-index 1100px.
- ObsiGate verifie sans code: creation .xlsx OK (openpyxl, #186).
This commit is contained in:
2026-10-05 22:47:36 -04:00
parent 894004a1f5
commit 1d1cdbd618
63 changed files with 9473 additions and 393 deletions
+43 -7
View File
@@ -6,18 +6,47 @@ from __future__ import annotations
import logging
from fastapi import APIRouter, Body, Request
from fastapi import APIRouter, Body, HTTPException, Request
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
from app.auth.session import SessionManager
from app.db import get_conn
from app.services.collection_lifecycle import (
delete_collections,
workspace_collection_ids,
)
from ._common import WORKSPACE_COOKIE, _get_active_workspace, _get_user_id, _sidebar_data
from ._common import (
WORKSPACE_COOKIE,
_get_active_workspace,
_get_user_id,
_require_user_id,
_sidebar_data,
)
logger = logging.getLogger(__name__)
router = APIRouter(tags=["dashboard"])
def _require_ws_owner(request: Request, ws_id: int) -> int:
"""A3/A4 — session obligatoire + l'appelant doit posseder l'espace
(ou être admin global). Sans ce garde, un POST/DELETE anonyme créait ou
supprimait des espaces au nom de l'utilisateur 1."""
uid = _require_user_id(request)
with get_conn() as conn:
row = conn.execute(
"SELECT owner_id FROM workspaces WHERE id=?", (ws_id,)
).fetchone()
if not row:
raise HTTPException(404, "Workspace not found")
is_admin = conn.execute(
"SELECT is_admin FROM users WHERE id=?", (uid,)
).fetchone()
if row["owner_id"] != uid and not (is_admin and is_admin["is_admin"]):
raise HTTPException(403, "Not your workspace")
return uid
@router.get("/workspaces", response_class=HTMLResponse)
@@ -42,8 +71,7 @@ def workspaces_page(request: Request):
@router.get("/api/workspaces")
def list_workspaces(request: Request):
"""List all workspaces for the current user."""
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
uid = user["id"] if user and user.get("id") else 1
uid = _require_user_id(request)
with get_conn() as conn:
rows = conn.execute(
"SELECT w.*, (SELECT COUNT(*) FROM pages WHERE workspace_id=w.id AND collection_row_id IS NULL) as page_count "
@@ -67,7 +95,7 @@ def create_workspace(request: Request, body: dict = Body(default={})):
if not name:
return {"error": "Name required"}
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
uid = user["id"] if user and user.get("id") else 1
uid = _require_user_id(request)
with get_conn() as conn:
# Ensure user exists (FK constraint)
uid_ok = conn.execute("SELECT id FROM users WHERE id=?", (uid,)).fetchone()
@@ -99,6 +127,7 @@ def rename_workspace(request: Request, ws_id: int, body: dict = Body(default={})
name = body.get("name", "").strip()
if not name:
return {"error": "Name required"}
_require_ws_owner(request, ws_id)
with get_conn() as conn:
conn.execute("UPDATE workspaces SET name=? WHERE id=?", (name, ws_id))
conn.commit()
@@ -109,13 +138,19 @@ def rename_workspace(request: Request, ws_id: int, body: dict = Body(default={})
@router.delete("/api/workspaces/{ws_id:int}")
def delete_workspace(request: Request, ws_id: int):
"""Delete a workspace and all its pages."""
"""Delete a workspace, its pages and its databases."""
_require_ws_owner(request, ws_id)
with get_conn() as conn:
# Les bases du workspace partaient avec ses pages : les laisser créait
# des collections orphelines (workspace_id pointant sur rien) que
# /db et My Tasks continuaient de lister.
collections = workspace_collection_ids(conn, ws_id)
delete_collections(conn, collections)
conn.execute("DELETE FROM pages WHERE workspace_id=?", (ws_id,))
conn.execute("DELETE FROM workspace_members WHERE workspace_id=?", (ws_id,))
conn.execute("DELETE FROM workspaces WHERE id=?", (ws_id,))
conn.commit()
return {"status": "ok"}
return {"status": "ok", "collections": len(collections)}
@@ -123,6 +158,7 @@ def delete_workspace(request: Request, ws_id: int):
@router.post("/api/workspaces/{ws_id:int}/select")
def select_workspace(request: Request, ws_id: int):
"""Set the active workspace via cookie."""
_require_ws_owner(request, ws_id)
response = JSONResponse({"status": "ok", "workspace_id": ws_id})
response.set_cookie(WORKSPACE_COOKIE, str(ws_id), max_age=86400 * 30, httponly=True, path="/")
return response