feat(store): TranscriptStatus read field + status setter/loader

Surfaces videos.transcript_status (migration 007) on SummaryRow and adds
SetTranscriptStatus / GetTranscriptStatus / RateLimitedVideoIDs.

SetTranscriptStatus is the single choke point for the rate-limit lifecycle:
"rate_limited" stamps rate_limited_at = NOW(), every other status clears it,
so the runner's backoff window and the UI badge read one consistent source.
RateLimitedVideoIDs is the per-pass loader (mirrors SeenVideoIDs) the runner
uses to skip still-throttled videos without re-hitting the caption endpoint.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 22:52:48 +02:00
co-authored by Claude Opus 4.8
parent f50c072d65
commit 1e81965519
3 changed files with 189 additions and 1 deletions
+7 -1
View File
@@ -49,6 +49,10 @@ type SummaryRow struct {
// set by the web "Summarize" button and cleared by the next `tapir run`. Only
// populated by ListVideos/GetVideoRow (summary-only reads leave it false).
SummarizeRequested bool
// TranscriptStatus mirrors videos.transcript_status (migration 007): "" (unset),
// "none", "rate_limited", or "fetched". Drives the "Retrying later" list badge.
// Only populated by ListVideos/GetVideoRow ("" on summary-only reads).
TranscriptStatus string
}
// selectSummary is the shared projection for both reads. videos is LEFT JOINed
@@ -132,7 +136,8 @@ const selectVideo = `
COALESCE(s.fallback_used, FALSE),
COALESCE(s.created_at, v.seen_at),
(s.id IS NOT NULL) AS summarized,
v.summarize_requested
v.summarize_requested,
COALESCE(v.transcript_status, '')
FROM videos v
LEFT JOIN summaries s ON s.video_id = v.id AND s.user_id = v.user_id`
@@ -245,6 +250,7 @@ func scanVideoRow(rows pgx.Row) (SummaryRow, error) {
&row.CreatedAt,
&row.Summarized,
&row.SummarizeRequested,
&row.TranscriptStatus,
); err != nil {
return SummaryRow{}, fmt.Errorf("store: scan video: %w", err)
}