"""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