- 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).
166 lines
5.6 KiB
Python
166 lines
5.6 KiB
Python
"""FlowDeck — Board : synced.
|
|
|
|
Découpe A28 de l'ancien app/routers/board.py (2 101 lignes, 53 routes) — un module par concern, contrat inchangé.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Body, HTTPException, Query, Request
|
|
|
|
from app.auth.session import SessionManager
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(tags=["board"], prefix="/board")
|
|
|
|
|
|
|
|
|
|
# ═══════════════ v5.14.0: Synced Blocks ─═══════════════════# These routes MUST be registered before the catch-all /{owner}/{repo} below.
|
|
|
|
def _session_user(request: Request) -> dict:
|
|
"""Session obligatoire — sans garde, un appel anonyme levait
|
|
``AttributeError: 'NoneType' object has no attribute 'get'`` (HTTP 500)
|
|
au lieu d'un 401."""
|
|
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
|
|
if not user or not user.get("id"):
|
|
raise HTTPException(401, "Authentication required")
|
|
return user
|
|
|
|
|
|
def _assert_can_edit_block(request: Request, sb: dict) -> None:
|
|
"""Seul l'auteur du bloc (ou un admin global) peut le modifier/supprimer."""
|
|
user = _session_user(request)
|
|
if sb.get("created_by") in (None, user.get("id")):
|
|
return
|
|
if user.get("is_admin"):
|
|
return
|
|
raise HTTPException(403, "Not the author of this synced block")
|
|
|
|
|
|
@router.get("/api/synced-blocks")
|
|
def list_synced_blocks_api(request: Request, workspace: str = Query(default="")):
|
|
"""List synced blocks for a workspace."""
|
|
user = _session_user(request)
|
|
from app.services.synced_blocks import list_synced_blocks
|
|
return {"synced_blocks": list_synced_blocks(workspace or user.get("login", ""))}
|
|
|
|
|
|
|
|
@router.post("/api/synced-blocks")
|
|
def create_synced_block_api(request: Request, body: dict = Body(...)):
|
|
"""Create a new synced block."""
|
|
user = _session_user(request)
|
|
from app.services.synced_blocks import create_synced_block
|
|
sid = create_synced_block(
|
|
workspace=body.get("workspace", ""),
|
|
title=body.get("title", "Synced block"),
|
|
content=body.get("content", []),
|
|
created_by=user.get("id"),
|
|
)
|
|
return {"status": "ok", "synced_block_id": sid}
|
|
|
|
|
|
|
|
|
|
@router.put("/api/synced-blocks/{sid}")
|
|
async def update_synced_block_api(request: Request, sid: int):
|
|
"""Update a synced block's content (propagates to all pages)."""
|
|
try:
|
|
body = await request.json()
|
|
except Exception:
|
|
raise HTTPException(400, "Invalid JSON body") from None
|
|
from app.services.synced_blocks import (
|
|
get_synced_block,
|
|
page_ids_for_synced,
|
|
sync_synced_blocks_in_page,
|
|
update_synced_block,
|
|
)
|
|
sb = get_synced_block(sid)
|
|
if not sb:
|
|
raise HTTPException(404, "Synced block not found")
|
|
_assert_can_edit_block(request, sb)
|
|
update_synced_block(sid, body.get("title", sb["title"]), body.get("content", []))
|
|
# v6.5.0: rewrite every referencing page's stored content first (DB row
|
|
# content pages included), THEN push the realtime update so open rooms
|
|
# reload the fresh content from the DB.
|
|
for pid in page_ids_for_synced(sid):
|
|
sync_synced_blocks_in_page(pid)
|
|
from app.services.realtime_server import manager
|
|
await manager._propagate_synced(sid)
|
|
return {"status": "ok"}
|
|
|
|
|
|
|
|
|
|
@router.delete("/api/synced-blocks/{sid}")
|
|
async def delete_synced_block_api(request: Request, sid: int):
|
|
"""Delete a synced block."""
|
|
from app.services.synced_blocks import (
|
|
delete_synced_block,
|
|
get_synced_block,
|
|
mark_synced_block_deleted,
|
|
page_ids_for_synced,
|
|
)
|
|
sb = get_synced_block(sid)
|
|
if not sb:
|
|
raise HTTPException(404, "Synced block not found")
|
|
_assert_can_edit_block(request, sb)
|
|
# v6.5.0: collect referencing pages BEFORE the FK cascade wipes the
|
|
# refs, rewrite their stored content (deleted state), then broadcast.
|
|
pids = page_ids_for_synced(sid)
|
|
delete_synced_block(sid)
|
|
mark_synced_block_deleted(sid, pids)
|
|
from app.services.realtime_server import manager
|
|
await manager._broadcast_synced_to(pids, sid)
|
|
return {"status": "ok"}
|
|
|
|
|
|
|
|
|
|
@router.get("/api/synced-blocks/{sid}")
|
|
def get_synced_block_api(request: Request, sid: int):
|
|
"""Get a synced block by id."""
|
|
_session_user(request)
|
|
from app.services.synced_blocks import get_synced_block
|
|
sb = get_synced_block(sid)
|
|
if not sb:
|
|
raise HTTPException(404, "Synced block not found")
|
|
return dict(sb)
|
|
|
|
|
|
|
|
|
|
@router.post("/api/pages/{page_id}/synced")
|
|
def add_synced_to_page(request: Request, page_id: int, body: dict = Body(...)):
|
|
"""Add a synced block reference to a page."""
|
|
from app.services.synced_blocks import add_page_synced, get_synced_block
|
|
sid = body.get("synced_block_id")
|
|
sb = get_synced_block(sid)
|
|
if not sb:
|
|
raise HTTPException(404, "Synced block not found")
|
|
add_page_synced(page_id, sid, body.get("block_index", 0))
|
|
return {"status": "ok", "synced_block_id": sid}
|
|
|
|
|
|
|
|
|
|
@router.delete("/api/pages/{page_id}/synced/{sid}")
|
|
def remove_synced_from_page(request: Request, page_id: int, sid: int):
|
|
"""Remove a synced block reference from a page (unsync)."""
|
|
from app.services.synced_blocks import remove_page_synced
|
|
remove_page_synced(page_id, sid)
|
|
return {"status": "ok"}
|
|
|
|
|
|
|
|
|
|
@router.get("/api/pages/{page_id}/synced")
|
|
def get_page_synced_refs(request: Request, page_id: int):
|
|
"""Get all synced block references for a page."""
|
|
from app.services.synced_blocks import get_page_synced
|
|
return {"synced_blocks": get_page_synced(page_id)}
|
|
|
|
|
|
# ═══════════ Board page ═══════════
|