fix: side peek des bases repasse en vanilla JS + largeur 1100px standard (v7.49.0)
- 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:
+50
-19
@@ -8,7 +8,7 @@ import json
|
||||
import logging
|
||||
import sqlite3
|
||||
|
||||
from fastapi import APIRouter, Body, HTTPException, Request
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, Request
|
||||
from fastapi.responses import HTMLResponse, StreamingResponse
|
||||
|
||||
from app.auth.session import SessionManager
|
||||
@@ -16,14 +16,32 @@ from app.db import get_conn
|
||||
from app.services.automations import fire_event, run_event_sync
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(tags=["workspace"], prefix="/workspace")
|
||||
|
||||
ROLES = ["admin", "editor", "commenter", "viewer"]
|
||||
|
||||
|
||||
def _require_session(request: Request) -> dict:
|
||||
"""A3/A4 — session obligatoire sur TOUT le router `/workspace`.
|
||||
|
||||
Avant, ces routes s'exécutaient sans session : `GET /workspace/collections/
|
||||
{id}/export/csv` renvoyait le contenu réel d'une collection à un visiteur
|
||||
anonyme, et les POST créaient des données au nom de l'utilisateur 1.
|
||||
La seule route publique (`/workspace/public/...`) vit désormais dans
|
||||
``public_router``, sans dépendance.
|
||||
"""
|
||||
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
|
||||
if not user or not user.get("id"):
|
||||
raise HTTPException(401, "Authentication required")
|
||||
return user
|
||||
|
||||
|
||||
router = APIRouter(tags=["workspace"], prefix="/workspace", dependencies=[Depends(_require_session)])
|
||||
# Route de partage public : AUCUNE dépendance d'authentification.
|
||||
public_router = APIRouter(tags=["workspace-public"], prefix="/workspace")
|
||||
|
||||
|
||||
def _current_user(request: Request) -> dict:
|
||||
s = request.cookies.get("flowdeck_session", "")
|
||||
return SessionManager.decode_session(s) or {"login": "admin", "id": 1}
|
||||
"""Session obligatoire — plus de repli « admin implicite » (A3/A4)."""
|
||||
return _require_session(request)
|
||||
|
||||
|
||||
def _require_admin(request: Request) -> dict:
|
||||
@@ -209,45 +227,58 @@ def record_history(request: Request, page_id: int, body: dict = Body(default={})
|
||||
|
||||
@router.get("/favorites")
|
||||
def list_favorites(request: Request):
|
||||
user = _current_user(request)
|
||||
uid = user.get("id", 1)
|
||||
"""List the caller's favorites.
|
||||
|
||||
v7.46.0 — aligne sur le schéma v2.2.0 (``app/db.py``) : la table
|
||||
``favorites`` n'a plus de colonne ``collection_id`` et ``page_id``
|
||||
référence ``pages(id)``. L'ancienne requête (jointure ``collection_pages``)
|
||||
levait ``sqlite3.OperationalError: no such column: f.collection_id`` → 500.
|
||||
"""
|
||||
user = _require_session(request)
|
||||
uid = user["id"]
|
||||
with get_conn() as conn:
|
||||
rows = conn.execute(
|
||||
"""SELECT f.*, cp.title as page_title, c.name as collection_name
|
||||
"""SELECT f.*, p.title AS page_title
|
||||
FROM favorites f
|
||||
LEFT JOIN collection_pages cp ON f.page_id=cp.id
|
||||
LEFT JOIN collections c ON f.collection_id=c.id
|
||||
LEFT JOIN pages p ON f.page_id=p.id
|
||||
WHERE f.user_id=? ORDER BY f.position""",
|
||||
(uid,),
|
||||
).fetchall()
|
||||
return {"favorites": [dict(r) for r in rows]}
|
||||
|
||||
|
||||
|
||||
@router.post("/favorites")
|
||||
def add_favorite(request: Request, body: dict = Body(default={})):
|
||||
user = _current_user(request)
|
||||
uid = user.get("id", 1)
|
||||
user = _require_session(request)
|
||||
uid = user["id"]
|
||||
page_id = body.get("page_id")
|
||||
collection_id = body.get("collection_id")
|
||||
if not page_id:
|
||||
raise HTTPException(400, "page_id required")
|
||||
with get_conn() as conn:
|
||||
conn.execute("INSERT OR IGNORE INTO users (id, login, is_admin) VALUES (?,?,1)", (uid, user.get("login", "admin")))
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO favorites (user_id, page_id, collection_id) VALUES (?,?,?)",
|
||||
(uid, page_id, collection_id),
|
||||
"INSERT OR IGNORE INTO favorites (user_id, page_id) VALUES (?,?)",
|
||||
(uid, page_id),
|
||||
)
|
||||
conn.commit()
|
||||
try:
|
||||
run_event_sync(fire_event("favorite.added", {"page_id": page_id, "collection_id": collection_id, "user_id": uid}))
|
||||
run_event_sync(fire_event("favorite.added", {"page_id": page_id, "user_id": uid}))
|
||||
except Exception:
|
||||
logger.exception("add_favorite")
|
||||
return {"status": "favorited"}
|
||||
|
||||
|
||||
|
||||
@router.delete("/favorites/{fav_id}")
|
||||
def remove_favorite(request: Request, fav_id: int):
|
||||
user = _require_session(request)
|
||||
with get_conn() as conn:
|
||||
conn.execute("DELETE FROM favorites WHERE id=?", (fav_id,))
|
||||
cur = conn.execute(
|
||||
"DELETE FROM favorites WHERE id=? AND user_id=?", (fav_id, user["id"])
|
||||
)
|
||||
conn.commit()
|
||||
if cur.rowcount == 0:
|
||||
raise HTTPException(404, "Favorite not found")
|
||||
return {"status": "removed"}
|
||||
|
||||
|
||||
@@ -714,7 +745,7 @@ def delete_webhook(request: Request, wh_id: int):
|
||||
|
||||
# ── Public Sharing ──
|
||||
|
||||
@router.get("/public/{collection_id}")
|
||||
@public_router.get("/public/{collection_id}")
|
||||
def public_view(request: Request, collection_id: int):
|
||||
"""Simple public read-only view — no auth required.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user