generated from coulomb/repo-seed
EP catalogue (all domains): - EP-RAIL-001 ep_id patched (schema fix: add ep_id to EPUpdate) - EP-RAIL-003 (git bare-repo mirrors) and EP-RAIL-004 (offsite secondary backup) registered from railiance-cluster/docs/backup-restore.md - EP-CUST-003..007 ep_ids assigned to existing custodian EPs - EP-CUST-008 (State Hub API auth) and EP-CUST-009 (update_workstream MCP tool) registered as new custodian extension points TD catalogue (railiance — first 5 items): - TD-RAIL-001: backup cron runs as root without audit trail (high/security) - TD-RAIL-002: k3s kubeconfig world-readable mode 644 (medium/security) - TD-RAIL-003: no Ansible role unit tests (medium/test) - TD-RAIL-004: age key extracted via awk — fragile (medium/impl) - TD-RAIL-005: etcd snapshot retention uncoordinated (low/impl) Dashboard (T08 + T10): - Extract API URL and POLL to src/components/config.js; all 15 pages now import from the shared module (contributions/goals keep custom POLL) - Shared .kpi-infobox, .filter-bar, .filter-search/.filter-owner CSS moved to observablehq.config.js head <style> block; removed from 9 pages - Build: 0 errors, 0 warnings API (T09): - progress.py: limit param now Query(100, le=1000) — prevents unbounded list requests; closes TD-CUST-004 for the only endpoint that had limit CUST-WP-0004 marked completed (all 10 tasks done). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
51 lines
1.7 KiB
Python
51 lines
1.7 KiB
Python
import uuid
|
|
from datetime import datetime
|
|
|
|
from fastapi import APIRouter, Depends, Query, status
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from api.database import get_session
|
|
from api.models.progress_event import ProgressEvent
|
|
from api.schemas.progress_event import ProgressEventCreate, ProgressEventRead
|
|
|
|
router = APIRouter(prefix="/progress", tags=["progress"])
|
|
|
|
|
|
@router.get("/", response_model=list[ProgressEventRead])
|
|
async def list_progress(
|
|
topic_id: uuid.UUID | None = None,
|
|
workstream_id: uuid.UUID | None = None,
|
|
task_id: uuid.UUID | None = None,
|
|
event_type: str | None = None,
|
|
since: datetime | None = None,
|
|
limit: int = Query(100, le=1000),
|
|
session: AsyncSession = Depends(get_session),
|
|
) -> list[ProgressEvent]:
|
|
q = select(ProgressEvent)
|
|
if topic_id:
|
|
q = q.where(ProgressEvent.topic_id == topic_id)
|
|
if workstream_id:
|
|
q = q.where(ProgressEvent.workstream_id == workstream_id)
|
|
if task_id:
|
|
q = q.where(ProgressEvent.task_id == task_id)
|
|
if event_type:
|
|
q = q.where(ProgressEvent.event_type == event_type)
|
|
if since:
|
|
q = q.where(ProgressEvent.created_at >= since)
|
|
q = q.order_by(ProgressEvent.created_at.desc()).limit(limit)
|
|
result = await session.execute(q)
|
|
return list(result.scalars().all())
|
|
|
|
|
|
@router.post("/", response_model=ProgressEventRead, status_code=status.HTTP_201_CREATED)
|
|
async def append_progress(
|
|
body: ProgressEventCreate,
|
|
session: AsyncSession = Depends(get_session),
|
|
) -> ProgressEvent:
|
|
event = ProgressEvent(**body.model_dump())
|
|
session.add(event)
|
|
await session.commit()
|
|
await session.refresh(event)
|
|
return event
|