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
86 lines
3.2 KiB
Python
86 lines
3.2 KiB
Python
"""FlowDeck — Board : import_.
|
|
|
|
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, HTTPException, Request
|
|
|
|
from app.services.automations import fire_event
|
|
|
|
from ._common import _create_page_from_markdown
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(tags=["board"], prefix="/board")
|
|
|
|
|
|
|
|
|
|
@router.post("/api/pages/import")
|
|
async def import_page(request: Request):
|
|
"""v5.4.0: import markdown text as a new page (blocks) in the workspace."""
|
|
try:
|
|
body = await request.json()
|
|
except Exception:
|
|
raise HTTPException(400, "Invalid JSON body") from None
|
|
markdown = body.get("markdown", "")
|
|
title = body.get("title", "")
|
|
if not markdown and not body.get("csv"):
|
|
raise HTTPException(400, "markdown field required")
|
|
if not markdown.strip():
|
|
raise HTTPException(400, "markdown is empty")
|
|
page_id = await _create_page_from_markdown(request, markdown, title)
|
|
await fire_event("page.created", {"page_id": page_id, "title": title or "Import",
|
|
"workspace": ""})
|
|
return {"status": "ok", "id": page_id}
|
|
|
|
|
|
|
|
|
|
@router.post("/api/pages/import/file")
|
|
async def import_file(request: Request):
|
|
"""v5.4.0: import an uploaded .md file (or a Notion export .zip containing
|
|
markdown pages) into the workspace. Returns the created page ids."""
|
|
import io as _io
|
|
import zipfile
|
|
|
|
form = await request.form()
|
|
upload = form.get("file")
|
|
if upload is None or not hasattr(upload, "filename"):
|
|
raise HTTPException(400, "file field required")
|
|
filename = (upload.filename or "import.md").replace("\\", "/").rsplit("/", 1)[-1]
|
|
data = await upload.read()
|
|
created_ids = []
|
|
|
|
if filename.lower().endswith(".zip"):
|
|
try:
|
|
zf = zipfile.ZipFile(_io.BytesIO(data))
|
|
except zipfile.BadZipFile:
|
|
raise HTTPException(400, "Invalid zip archive") from None
|
|
md_entries = sorted(
|
|
(n for n in zf.namelist() if n.lower().endswith((".md", ".markdown"))),
|
|
key=lambda n: (n.count("/"), n.lower()),
|
|
)
|
|
if not md_entries:
|
|
raise HTTPException(400, "No .md files found in archive")
|
|
for name in md_entries:
|
|
raw = zf.read(name).decode("utf-8", errors="replace")
|
|
title = name.replace("\\", "/").rsplit("/", 1)[-1][:-3]
|
|
try:
|
|
created_ids.append(await _create_page_from_markdown(request, raw, title))
|
|
except Exception as exc: # noqa: BLE001 - keep importing the rest
|
|
logger.warning("import failed for %s: %s", name, exc)
|
|
else:
|
|
try:
|
|
raw = data.decode("utf-8")
|
|
except UnicodeDecodeError:
|
|
raise HTTPException(400, "Only text/markdown files are supported") from None
|
|
title = filename.replace(".md", "").replace(".markdown", "").replace(".txt", "")
|
|
created_ids.append(await _create_page_from_markdown(request, raw, title))
|
|
|
|
if not created_ids:
|
|
raise HTTPException(422, "No pages could be imported")
|
|
return {"status": "ok", "ids": created_ids, "count": len(created_ids)}
|