feat(#392): publish track-like + request-status events to SSE bus

Slice 2 of #392 — wires the first producers onto the bus that slice 1
built. After this commit, an SSE subscriber sees real events fire:

- track.liked / track.unliked when the user toggles the heart on a track
  (handleLikeTrack / handleUnlikeTrack). Album + artist like events
  intentionally deferred — they're symmetric trivial follow-ups but the
  operator's primary like surface is tracks.
- request.status_changed when a Lidarr request is created, cancelled,
  approved, or rejected. Auto-approve will fire twice (pending then
  approved) in rapid succession, which is semantically correct; client
  invalidation handles that fine.

Events are user-scoped via row.UserID so admin approve/reject route to
the requester, not the admin acting. Helpers live in events_publish.go
so the wire shape (kind names, payload keys) stays in one place — future
producers in slice 3 reuse the same pattern.

events_publish.go is no-op when h.eventbus is nil so tests that
construct handlers without a bus continue to pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 20:55:33 -04:00
parent a5500aeeff
commit 3ffa5608d8
4 changed files with 74 additions and 0 deletions
+2
View File
@@ -60,6 +60,7 @@ func (h *handlers) handleLikeTrack(w http.ResponseWriter, r *http.Request) {
_ = playevents.CaptureContextualLikeIfPlaying(r.Context(), q, user.ID, id, h.logger)
}
h.logLikeChange(r, syncpkg.EntityLikeTrack, user.ID, id, syncpkg.OpUpsert)
h.publishLikeEvent(user.ID, id, "track", true)
w.WriteHeader(http.StatusNoContent)
}
@@ -83,6 +84,7 @@ func (h *handlers) handleUnlikeTrack(w http.ResponseWriter, r *http.Request) {
// Don't fail the response — soft-delete is best-effort.
}
h.logLikeChange(r, syncpkg.EntityLikeTrack, user.ID, id, syncpkg.OpDelete)
h.publishLikeEvent(user.ID, id, "track", false)
w.WriteHeader(http.StatusNoContent)
}