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
77 lines
2.4 KiB
Python
77 lines
2.4 KiB
Python
"""FlowDeck — Board : wiki.
|
|
|
|
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, Query, Request
|
|
|
|
from app.db import get_conn
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(tags=["board"], prefix="/board")
|
|
|
|
|
|
|
|
|
|
@router.get("/api/wiki/pages")
|
|
def wiki_page_search(request: Request, q: str = Query(default="")):
|
|
"""v5.11.0: page picker for [[ wiki-links. Fuzzy-ish title search across
|
|
every non-deleted page the current user can see (single source: pages)."""
|
|
q = (q or "").strip().lower()
|
|
with get_conn() as conn:
|
|
rows = conn.execute(
|
|
"""SELECT id, title, page_icon, workspace FROM pages
|
|
WHERE deleted_at IS NULL
|
|
ORDER BY updated_at DESC LIMIT 500"""
|
|
).fetchall()
|
|
results = []
|
|
for r in rows:
|
|
title = r["title"] or "Untitled"
|
|
if q:
|
|
# subsequence match ("mtg" → "Meeting notes") or plain substring.
|
|
hay = title.lower()
|
|
it = iter(hay)
|
|
subseq = all(ch in it for ch in q)
|
|
if q not in hay and not subseq:
|
|
continue
|
|
results.append({
|
|
"id": r["id"],
|
|
"title": title,
|
|
"icon": r["page_icon"] or "",
|
|
"workspace": r["workspace"] or "",
|
|
})
|
|
if len(results) >= 20:
|
|
break
|
|
return {"pages": results}
|
|
|
|
|
|
|
|
|
|
@router.get("/api/wiki/titles")
|
|
def wiki_titles(request: Request, ids: str = Query(default="")):
|
|
"""v5.11.0: resolve page-id lists to current labels (rename propagation)."""
|
|
parsed: list[int] = []
|
|
for part in (ids or "").split(","):
|
|
part = part.strip()
|
|
if part.isdigit():
|
|
parsed.append(int(part))
|
|
parsed = parsed[:200]
|
|
out: dict[str, str] = {}
|
|
if parsed:
|
|
placeholders = ",".join("?" * len(parsed))
|
|
with get_conn() as conn:
|
|
rows = conn.execute(
|
|
f"SELECT id, title, page_icon, deleted_at FROM pages WHERE id IN ({placeholders})",
|
|
parsed,
|
|
).fetchall()
|
|
for r in rows:
|
|
if r["deleted_at"]:
|
|
out[str(r["id"])] = "Deleted page"
|
|
else:
|
|
icon = (r["page_icon"] or "")
|
|
out[str(r["id"])] = (icon + " " if icon else "") + (r["title"] or "Untitled")
|
|
return {"titles": out}
|