Files
flowdeck/app/routers/board/synced.py
T
bruno 0bc74ad728
FlowDeck CI / lint (push) Successful in 2m1s
FlowDeck CI / test (push) Successful in 15m17s
FlowDeck CI / docker (push) Canceled after 0s
refactor: A28 TERMINÉ — board.py (2 101 L) → package 14 fichiers (v7.32.0)
Lot 4/4 de l'A28 (god files) : l'ancien app/routers/board.py (2 101
lignes, 53 routes) devient le package `app/routers/board/` :

- 12 modules de routes : pages 271 L (7 r.), page_api 229 (5),
  board_views 223 (8), page_ops 176 (3), sharing 175 (10), synced 144 (8),
  page_media 122 (4), import_ 85 (2), wiki 76 (2), library 66 (1),
  embed 64 (2), sync 51 (1)
- _common.py (878 L) : 23 helpers dont 4 async + les 4 constantes
  (STATUS_COLORS, STATUS_LABELS, AI_KEYWORD_COLORS, _REPO_REF_RE)
- __init__.py : __all__ complet — importateurs inchangés (api.py ×4
  top-level, webhooks top-level, dashboard ×5 lazy, tests ×4)

Preuve contractuelle : docs/openapi-v2.json régénéré = IDENTIQUE
byte-à-byte (509 chemins, ordre préservé).

Pièges rattrapés :
- constantes d'état oubliées dans _common à la 1ʳᵉ passe (F821 +
  ImportError au chargement) → ré-insérées avec les valeurs exactes
- docstring du header copié → F404 → slice [1:21]
- helpers `async def` non détectés par `def ` seul

A28 TERMINÉ en 4 lots : api_v2 (7.29.0), dashboard (7.30.0),
collections (7.31.0), board (7.32.0) — 0 changement d'URL sur les 4.

suite **1091/1091** · ruff OK · OpenAPI 509 identique · docs à jour
2026-10-02 08:27:57 -04:00

145 lines
4.8 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.
@router.get("/api/synced-blocks")
def list_synced_blocks_api(request: Request, workspace: str = Query(default="")):
"""List synced blocks for a workspace."""
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
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 = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
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")
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")
# 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(sid: int):
"""Get a synced block by id."""
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 ═══════════