patristic-be / src /api /routers /processing_log.py
Mario33333's picture
deploy: reader-access security hardening (feature/audiobook@06a5ed8)
7c2d250 verified
Raw
History Blame Contribute Delete
3.07 kB
"""``/processing-log`` (admin) β€” filterable Operations log view.
Path is kebab-case per CONTRACT.md Β§9. Filters are camelCase queries:
- ``bookId`` (not ``book_id``)
- ``stage``
- ``status`` β€” one of ``success | failed | in_flight``
- ``since`` β€” ISO timestamp lower bound on ``started_at``
- ``limit`` (default 100, max 500)
Admin-only because the log surfaces error messages from every book the
user has ingested β€” sensitive enough to keep behind ``require_admin``.
"""
from __future__ import annotations
from fastapi import APIRouter, Depends, Query
from src.api.deps import require_admin
from src.api.dto.processing_log import ProcessingLogEntryDTO, ProcessingOutcomeEnum
from src.lib.auth.models import User
from src.lib.processing.models import StageRun
from src.lib.processing.repo import query
router = APIRouter(tags=["processing-log"])
_STATUS_TO_OUTCOME: dict[ProcessingOutcomeEnum, str] = {
ProcessingOutcomeEnum.success: "success",
ProcessingOutcomeEnum.failed: "failed",
ProcessingOutcomeEnum.in_flight: "in-flight",
}
def _to_dto(run: StageRun) -> ProcessingLogEntryDTO:
if run.success == 1:
outcome = ProcessingOutcomeEnum.success
elif run.success == 0:
outcome = ProcessingOutcomeEnum.failed
else:
outcome = ProcessingOutcomeEnum.in_flight
return ProcessingLogEntryDTO(
id=run.id,
book_id=run.book_id,
stage=run.stage,
started_at=run.started_at,
finished_at=run.finished_at,
outcome=outcome,
pages_done=run.pages_done,
engine=run.engine,
provider=run.provider,
backend=run.backend,
where=run.where,
error=run.error,
duration_ms=run.duration_ms,
)
@router.get("/processing-log", response_model=list[ProcessingLogEntryDTO])
def list_processing_log(
_: User = Depends(require_admin),
book_id: str | None = Query(default=None, alias="bookId"),
stage: str | None = Query(default=None),
status: ProcessingOutcomeEnum | None = Query(default=None),
since: str | None = Query(
default=None,
description="ISO timestamp lower bound on started_at; applied client-side",
),
limit: int = Query(default=100, ge=1, le=500),
) -> list[ProcessingLogEntryDTO]:
"""Filterable list β€” newest first.
The repo's :func:`query` does book/stage/outcome filtering in SQL;
the ``since`` filter applies in Python since the repo signature
doesn't expose it (a Stage 3 router shouldn't grow the lib's
signature for one filter; if the FE wants this pushed down later we
revisit).
"""
outcome_str = _STATUS_TO_OUTCOME[status] if status is not None else None
runs = query(
book_id=book_id,
stage=stage,
outcome=outcome_str,
limit=limit if since is None else max(limit * 2, 500),
)
if since:
runs = [r for r in runs if r.started_at >= since]
return [_to_dto(r) for r in runs[:limit]]