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
52 lines
1.9 KiB
Python
52 lines
1.9 KiB
Python
"""FlowDeck — Board : sync.
|
|
|
|
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 json
|
|
import logging
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
|
|
from app.db import get_conn
|
|
from app.services.gitea_client import gitea
|
|
|
|
from ._common import _extract_ai_keywords, _issue_column
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(tags=["board"], prefix="/board")
|
|
|
|
|
|
|
|
|
|
@router.post("/api/sync/{owner}/{repo}")
|
|
async def sync_project(owner: str, repo: str):
|
|
"""Full bidirectional sync: fetch Gitea issues → update local DB."""
|
|
try:
|
|
issues = await gitea.get_issues(owner, repo, state="all")
|
|
issues_only = [i for i in issues if not i.get("pull_request")]
|
|
with get_conn() as conn:
|
|
board = conn.execute(
|
|
"SELECT id, columns_json FROM boards WHERE project_owner=? AND project_name=?",
|
|
(owner, repo),
|
|
).fetchone()
|
|
if board:
|
|
board_id = board["id"]
|
|
columns = json.loads(board["columns_json"])
|
|
# A23 : un seul executemany pour toutes les cards.
|
|
conn.executemany(
|
|
"INSERT OR REPLACE INTO cards (board_id, gitea_issue_id, column_name) VALUES (?,?,?)",
|
|
[
|
|
(board_id, issue["number"], _issue_column(issue, columns, board_id))
|
|
for issue in issues_only
|
|
],
|
|
)
|
|
for issue in issues_only:
|
|
# Extract AI keywords from each issue
|
|
_extract_ai_keywords(owner, repo, issue.get("labels", []), issue.get("body", ""))
|
|
conn.commit()
|
|
return {"status": "ok", "issues_synced": len(issues_only)}
|
|
except Exception as e:
|
|
raise HTTPException(500, str(e)) from e
|