feat(api): dashboard poll optimisation — T1, T2, T3

T1: Cache-Control max-age=60 on /topics/, /repos/, /domains/ list endpoints
    so repeated dashboard polls within a minute are served from browser cache.

T2: ETag middleware (md5 hash) on all JSON GET responses with conditional-GET
    (304 Not Modified) support; If-None-Match and ETag added to CORS headers.
    ETag registered inside CORS so 304s automatically carry CORS headers.

T3: GET /state/deps — lightweight dep-graph endpoint returning open workstreams
    with depends_on/blocks edges only, skipping the 10-table full-summary query.
    Prerequisite for T4 (switching workstreams.md and dependencies.md off /state/summary).

Workplan: CUST-WP-0039-dashboard-poll-optimization.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-11 17:26:30 +02:00
parent 88ff588fb0
commit b832032cc3
5 changed files with 131 additions and 4 deletions

View File

@@ -1,6 +1,6 @@
import uuid
from fastapi import APIRouter, Depends, HTTPException, Query, status
from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
@@ -18,9 +18,11 @@ router = APIRouter(prefix="/domains", tags=["domains"])
@router.get("/", response_model=list[DomainRead])
async def list_domains(
response: Response,
status: str | None = Query(None, description="active | archived | all"),
session: AsyncSession = Depends(get_session),
) -> list[Domain]:
response.headers["Cache-Control"] = "max-age=60, stale-while-revalidate=30"
q = select(Domain).order_by(Domain.name)
if status and status != "all":
q = q.where(Domain.status == status)