feat(store): persist summary actions (watch/skip/save) — Stage-0 metric
The summary_actions table records the maintainer's act on a summary, the
column that makes the Stage-0 headline test ("acts on >=1 summary") queryable
(ui-spec.md §5, ADR-011). This is the gate lanes B/C build on.
- Migration 002: summary_actions (id, user_id, video_id TEXT, action, acted_at)
with a CHECK on action IN ('watched','skipped','saved') and a UNIQUE
(user_id, video_id, action). Per-user isolation: every row carries user_id.
- New actions.go: SetAction (idempotent, atomic watched<->skipped mutual
exclusion in one tx; saved independent), ClearAction, ActionsFor for the list
view, plus Go-side action validation.
- reads.go: additive SummaryRow.Actions, populated by composing ActionsFor
(Go-side, not a SQL join — summaries.video_id is UUID, actions.video_id TEXT).
- embedded-postgres tests: set/clear, mutual exclusion, saved coexistence,
idempotency, invalid rejection, user scoping, read-view surfacing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,7 @@ type SummaryRow struct {
|
||||
AIModel string
|
||||
FallbackUsed bool
|
||||
CreatedAt time.Time
|
||||
Actions []string // current active actions for this video; nil when none
|
||||
}
|
||||
|
||||
// selectSummary is the shared projection for both reads. videos is LEFT JOINed
|
||||
@@ -87,6 +88,9 @@ func (s *Store) ListSummaries(ctx context.Context, userID string, limit int) ([]
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("store: iterate summaries: %w", err)
|
||||
}
|
||||
if err := s.attachActions(ctx, userID, out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -113,7 +117,11 @@ func (s *Store) GetSummaryByVideo(ctx context.Context, userID, videoID string) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &row, nil
|
||||
holder := []SummaryRow{row}
|
||||
if err := s.attachActions(ctx, userID, holder); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &holder[0], nil
|
||||
}
|
||||
|
||||
// scanSummaryRow reads one row in the selectSummary column order. published_at is
|
||||
|
||||
Reference in New Issue
Block a user