US02-04: Expose Job APIs and Activity Events (#57)

This commit was merged in pull request #57.
This commit is contained in:
2026-07-16 20:15:57 +02:00
parent d054dcbe61
commit de20e6c8ce
8 changed files with 328 additions and 3 deletions

View File

@@ -23,7 +23,7 @@ from collections import Counter
from datetime import datetime, timedelta, timezone
from collections.abc import Iterable, Sequence
from sqlalchemy import select, update
from sqlalchemy import select, text, update
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import sessionmaker
@@ -357,6 +357,20 @@ class JobService:
).scalars()
return [{"type": e.event_type, "message": e.message} for e in rows]
def events_after(self, job_id: str, after: int = 0) -> list[dict]:
"""Durable events with ``seq`` > ``after``, ordered — the resumable cursor for
SSE and polling. SQLite's monotonic rowid is the sequence (job_events is a
rowid table), so no extra column is needed."""
with self._session_factory() as session:
rows = session.execute(
text(
"SELECT rowid AS seq, event_type, message FROM job_events "
"WHERE job_id = :jid AND rowid > :after ORDER BY rowid"
),
{"jid": job_id, "after": after},
).mappings().all()
return [{"seq": r["seq"], "type": r["event_type"], "message": r["message"]} for r in rows]
# ── helpers ──────────────────────────────────────────────────────────────────
@staticmethod
def _event(session, job_id: str, event_type: str, message: str | None = None) -> None: