fix: A21 phase 1 — 352 routes async sans await → threadpool (v7.8.0)
FlowDeck CI / lint (push) Canceled after 0s
FlowDeck CI / test (push) Canceled after 0s
FlowDeck CI / docker (push) Canceled after 0s

- Conversion `async def` → `def` de TOUTES les routes dont le corps ne contient
  ni `await`, ni `async with`, ni `async for`, ni `asyncio` (scan automatique
  corps par corps sur app/ : 352 converties, 0 dangereuses, vérifié
  `asyncio`/`run_coroutine`/`.result()` absents). FastAPI exécute ces handlers
  dans son threadpool → tout leur SQLite (`get_conn()` + `conn.execute`) quitte
  l'event loop, sans changer une ligne de logique.
- Répartition : api_v2 60, dashboard 40, collections 25, board 23,
  workspace 19, wiki 17, permissions 14, api 14, main.py 6, + 35 fichiers.
- Les 4 routers prioritaires de l'audit sont couverts par ce lot :
  api_v2 60 + dashboard 40 + collections 25 + board 23 = 148 conversions
  (le reste de leurs routes attend la phase 2 : elles ont de vrais `await`).
- Reste (phase 2) : les 311 routes avec de vrais `await` → enrouler les blocs
  DB dans `await anyio.to_thread.run_sync(...)` ; pas de wrapper partagé livré
  (rien ne l'appellerait — YAGNI jusqu'au premier usage).

suite **1037/1037** (233 s) · `ruff check app tests` OK · docs à jour
This commit is contained in:
2026-10-01 10:53:26 -04:00
parent c718fe06de
commit 224bda74d5
48 changed files with 379 additions and 358 deletions
+19 -19
View File
@@ -79,7 +79,7 @@ async def create_workspace(request: Request):
# ── Members ──
@router.get("/{ws_id}/members")
async def list_members(request: Request, ws_id: int):
def list_members(request: Request, ws_id: int):
if not SessionManager.decode_session(request.cookies.get("flowdeck_session", "")):
raise HTTPException(401, "Authentication required")
with get_conn() as conn:
@@ -122,7 +122,7 @@ async def update_member_role(request: Request, ws_id: int, user_id: int):
@router.delete("/{ws_id}/members/{user_id}")
async def remove_member(request: Request, ws_id: int, user_id: int):
def remove_member(request: Request, ws_id: int, user_id: int):
_require_ws_admin(request, ws_id)
with get_conn() as conn:
conn.execute("DELETE FROM workspace_members WHERE workspace_id=? AND user_id=?", (ws_id, user_id))
@@ -133,7 +133,7 @@ async def remove_member(request: Request, ws_id: int, user_id: int):
# ── Comments ──
@router.get("/pages/{page_id}/comments")
async def list_comments(request: Request, page_id: int):
def list_comments(request: Request, page_id: int):
with get_conn() as conn:
rows = conn.execute(
"SELECT c.*, u.login, u.avatar_url FROM comments c JOIN users u ON c.user_id=u.id WHERE c.page_id=? ORDER BY c.created_at",
@@ -187,7 +187,7 @@ async def update_comment(request: Request, comment_id: int):
# ── Page History ──
@router.get("/pages/{page_id}/history")
async def page_history(request: Request, page_id: int):
def page_history(request: Request, page_id: int):
with get_conn() as conn:
rows = conn.execute(
"SELECT * FROM page_history WHERE page_id=? ORDER BY created_at DESC LIMIT 50",
@@ -214,7 +214,7 @@ async def record_history(request: Request, page_id: int):
# ── Favorites ──
@router.get("/favorites")
async def list_favorites(request: Request):
def list_favorites(request: Request):
user = _current_user(request)
uid = user.get("id", 1)
with get_conn() as conn:
@@ -251,7 +251,7 @@ async def add_favorite(request: Request):
@router.delete("/favorites/{fav_id}")
async def remove_favorite(request: Request, fav_id: int):
def remove_favorite(request: Request, fav_id: int):
with get_conn() as conn:
conn.execute("DELETE FROM favorites WHERE id=?", (fav_id,))
conn.commit()
@@ -261,7 +261,7 @@ async def remove_favorite(request: Request, fav_id: int):
# ── Templates ──
@router.get("/templates/database")
async def list_db_templates(request: Request):
def list_db_templates(request: Request):
with get_conn() as conn:
rows = conn.execute("SELECT * FROM database_templates ORDER BY name").fetchall()
return {"templates": [dict(r) for r in rows]}
@@ -296,7 +296,7 @@ async def apply_db_template(request: Request, tid: int):
@router.get("/collections/{collection_id}/templates/page")
async def list_page_templates(request: Request, collection_id: int):
def list_page_templates(request: Request, collection_id: int):
with get_conn() as conn:
rows = conn.execute(
"SELECT * FROM page_templates WHERE collection_id=? ORDER BY name", (collection_id,)
@@ -369,7 +369,7 @@ async def update_page_template(request: Request, collection_id: int, tid: int):
@router.delete("/collections/{collection_id}/templates/page/{tid}")
async def delete_page_template(request: Request, collection_id: int, tid: int):
def delete_page_template(request: Request, collection_id: int, tid: int):
"""Delete a page template."""
with get_conn() as conn:
tmpl = conn.execute(
@@ -385,7 +385,7 @@ async def delete_page_template(request: Request, collection_id: int, tid: int):
# ── v4.2.0: Dashboards ──
@router.get("/collections/{collection_id}/dashboards")
async def list_dashboards(request: Request, collection_id: int):
def list_dashboards(request: Request, collection_id: int):
"""List all dashboards for a collection."""
with get_conn() as conn:
rows = conn.execute(
@@ -436,7 +436,7 @@ async def update_dashboard(request: Request, collection_id: int, did: int):
@router.delete("/collections/{collection_id}/dashboards/{did}")
async def delete_dashboard(request: Request, collection_id: int, did: int):
def delete_dashboard(request: Request, collection_id: int, did: int):
"""Delete a dashboard."""
with get_conn() as conn:
dash = conn.execute(
@@ -452,7 +452,7 @@ async def delete_dashboard(request: Request, collection_id: int, did: int):
# ── v4.5.0: Sprints ──
@router.get("/collections/{collection_id}/sprints")
async def list_sprints(request: Request, collection_id: int):
def list_sprints(request: Request, collection_id: int):
"""List all sprints for a collection."""
with get_conn() as conn:
rows = conn.execute(
@@ -528,7 +528,7 @@ async def update_sprint(request: Request, collection_id: int, sid: int):
@router.delete("/collections/{collection_id}/sprints/{sid}")
async def delete_sprint(request: Request, collection_id: int, sid: int):
def delete_sprint(request: Request, collection_id: int, sid: int):
"""Delete a sprint."""
with get_conn() as conn:
sprint = conn.execute(
@@ -572,7 +572,7 @@ async def assign_page_to_sprint(request: Request, collection_id: int, sid: int):
@router.delete("/collections/{collection_id}/sprints/{sid}/assign/{page_id}")
async def remove_page_from_sprint(request: Request, collection_id: int, sid: int, page_id: int):
def remove_page_from_sprint(request: Request, collection_id: int, sid: int, page_id: int):
"""Remove a page from a sprint."""
with get_conn() as conn:
existing = conn.execute(
@@ -586,7 +586,7 @@ async def remove_page_from_sprint(request: Request, collection_id: int, sid: int
@router.get("/collections/{collection_id}/sprints/burndown/{sid}")
async def sprint_burndown(request: Request, collection_id: int, sid: int):
def sprint_burndown(request: Request, collection_id: int, sid: int):
"""Calculate burndown data for a sprint."""
with get_conn() as conn:
sprint = conn.execute(
@@ -660,7 +660,7 @@ async def import_csv(request: Request, collection_id: int):
@router.get("/collections/{collection_id}/export/csv")
async def export_csv(request: Request, collection_id: int):
def export_csv(request: Request, collection_id: int):
with get_conn() as conn:
pages = conn.execute(
"SELECT * FROM collection_pages WHERE collection_id=? ORDER BY position", (collection_id,)
@@ -690,7 +690,7 @@ async def export_csv(request: Request, collection_id: int):
# ── Webhooks Outbound Management ──
@router.get("/webhooks")
async def list_webhooks(request: Request):
def list_webhooks(request: Request):
_require_admin(request)
with get_conn() as conn:
rows = conn.execute("SELECT * FROM webhook_subscriptions ORDER BY created_at DESC").fetchall()
@@ -723,7 +723,7 @@ async def create_webhook(request: Request):
@router.delete("/webhooks/{wh_id}")
async def delete_webhook(request: Request, wh_id: int):
def delete_webhook(request: Request, wh_id: int):
_require_admin(request)
with get_conn() as conn:
conn.execute("DELETE FROM webhook_subscriptions WHERE id=?", (wh_id,))
@@ -734,7 +734,7 @@ async def delete_webhook(request: Request, wh_id: int):
# ── Public Sharing ──
@router.get("/public/{collection_id}")
async def public_view(request: Request, collection_id: int):
def public_view(request: Request, collection_id: int):
"""Simple public read-only view — no auth required.
A18 : les bases ``restricted``/``private`` (``permission_type``) restent