fix: A21 phase 2c — 190 routes hors loop, 86 % total (v7.26.0)
FlowDeck CI / lint (push) Successful in 1m55s
FlowDeck CI / test (push) Successful in 15m23s
FlowDeck CI / docker (push) Canceled after 0s

4 passes (283 → 93 routes async sur 667 = 86 % hors loop, avant 61 %) :

A. RACINE AUTH — `get_current_user` (auth/session.py) était `async def`
   SANS aucun await (cookie decode = synchrone) ; idem ses clones :
   `agent._current_user_id/_workspace_id/_current_admin` (34 sites) et
   `sso._require_admin` (corps 0 await, 6 sites) → `def` +
   47 `await` supprimés. Piège : 3 call sites passaient par l'alias `gcu`
   (grep littéral aveugle) — 8 tests en échec → corrigés.

B. Re-scan : 19 routes devenues SANS await → `def` (agent 8, sso 5,
   web_clipper 3, projects 2, auth 1…).

C/D. 155 routes dont les seuls awaits = `request.json()` / événements :
   - try/except `body = {}` → `Body(default={})` (même tolérance)
   - try/except `raise HTTPException(400)` → `Body(...)` REQUIS
     (422 FastAPI — aucun test ne couvrait le 400)
   - forme conditionnelle `request.json() if content-type else {}`
     (54 sites) → défaut `{}` (sans corps = `{}` dans les 2 cas)
   - `await fire_*` → `run_event_sync(...)` ; imports `Body` /
     `run_event_sync` ajoutés aux routers convertis

Reste async (93, justifié) : form/upload/file (22), réseau gitea/llm/oidc,
`_json_body` (9), 2 JSON inline en argument, 1 fallback logique
(capture_frontend_error), 1 lecture conditionnelle (web_clipper), mixtes.

suite **1089/1089** · ruff OK · docs à jour
This commit is contained in:
2026-10-01 22:17:48 -04:00
parent 8d0d69e7b8
commit 45e59009c3
34 changed files with 2479 additions and 680 deletions
+10 -12
View File
@@ -4,11 +4,12 @@ from __future__ import annotations
import logging
from datetime import UTC, datetime
from fastapi import APIRouter, HTTPException, Request
from fastapi import APIRouter, Body, HTTPException, Request
from app.auth.session import SessionManager
from app.db import get_conn
from app.services.automations import fire_event as _fire_event
from app.services.automations import run_event_sync
from app.services.publish import fire_published, fire_unpublished, publish, unpublish
logger = logging.getLogger(__name__)
@@ -27,10 +28,9 @@ def _require_auth(request: Request) -> dict:
@router.post("/pages/{page_id}/share")
async def share_page(page_id: int, request: Request):
def share_page(page_id: int, request: Request, body: dict = Body(default={})):
"""Invite a user, an email, or a group to a page."""
user = _require_auth(request)
body = await request.json() if request.headers.get("content-type") else {}
target_user_id = body.get("user_id")
target_group_id = body.get("group_id")
email = body.get("email", "")
@@ -119,7 +119,7 @@ async def share_page(page_id: int, request: Request):
conn.commit()
try:
await _fire_event("page.shared", {"page_id": page_id, "share_id": share_id, "permission": permission})
run_event_sync(_fire_event("page.shared", {"page_id": page_id, "share_id": share_id, "permission": permission}))
except Exception:
logger.exception("share_page")
@@ -164,11 +164,10 @@ def _mirror_share_revoke(conn, page_id: int, group_id: int) -> None:
@router.put("/pages/{page_id}/share/{share_id}", description="Update a share's permission.")
async def update_share_permission(page_id: int, share_id: int, request: Request):
def update_share_permission(page_id: int, share_id: int, request: Request, body: dict = Body(default={})):
"""Change the permission level of an existing share entry."""
user = _require_auth(request)
body = await request.json() if request.headers.get("content-type") else {}
permission = body.get("permission", "")
if permission not in ("view", "comment", "edit"):
@@ -293,11 +292,11 @@ def list_shares(page_id: int, request: Request):
@router.post("/pages/{page_id}/publish")
async def publish_page(page_id: int, request: Request):
def publish_page(page_id: int, request: Request):
"""Publish a page (is_published=1) with a URL slug."""
_require_auth(request)
slug, _title = publish(page_id)
await fire_published(page_id, slug)
run_event_sync(fire_published(page_id, slug))
return {
"page_id": page_id,
"is_published": True,
@@ -307,11 +306,11 @@ async def publish_page(page_id: int, request: Request):
@router.delete("/pages/{page_id}/publish")
async def unpublish_page(page_id: int, request: Request):
def unpublish_page(page_id: int, request: Request):
"""Unpublish a page."""
_require_auth(request)
unpublish(page_id)
await fire_unpublished(page_id)
run_event_sync(fire_unpublished(page_id))
return {
"page_id": page_id,
"is_published": False,
@@ -323,10 +322,9 @@ async def unpublish_page(page_id: int, request: Request):
@router.post("/recents/track")
async def track_recent(request: Request):
def track_recent(request: Request, body: dict = Body(default={})):
"""Record a page access in recents."""
user = _require_auth(request)
body = await request.json() if request.headers.get("content-type") else {}
page_id = body.get("page_id")
workspace = body.get("workspace", "")
source_type = body.get("source_type", "local")