feat(#392): SSE event stream foundation — eventbus + /api/events/stream
Slice 1 of the #392 hybrid live-refresh work. Ships the in-process pub/sub bus and the SSE subscriber endpoint; no producers wired yet, so the stream emits only heartbeats today. Verifiable in isolation by curl-ing the endpoint with a valid Bearer token — the connection opens, ": heartbeat" lines arrive every 15s, the connection closes cleanly on client disconnect. eventbus.Bus is a small fan-out broadcaster: subscribers register through Subscribe (returns a receive channel + an unsubscribe closure), writers call Publish, and the bus drops events for any subscriber whose buffer is full rather than blocking the writer. No persistence — clients are expected to resync via normal /api/* fetches on (re)connect. The SSE handler emits an initial ": connected" comment so the client sees the connection open immediately, then forwards events whose UserID matches the authenticated user (or is empty for broadcast). Heartbeat comments keep proxy connections alive. Context cancellation cleanly tears down the subscription on client disconnect. Producers (likes, request status, quarantine, scanner, playlist mutations) land in subsequent slices. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+5
-1
@@ -14,6 +14,7 @@ import (
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/auth"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/config"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/coverart"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/eventbus"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/library"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/lidarrconfig"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/lidarrquarantine"
|
||||
@@ -27,7 +28,7 @@ import (
|
||||
// Mount attaches /api/* handlers to r. Public endpoints (login) are outside
|
||||
// RequireUser; everything else is gated by the middleware. The events writer
|
||||
// is shared with the Subsonic mount so /rest/scrobble feeds the same store.
|
||||
func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, events *playevents.Writer, recCfg config.RecommendationConfig, lidarrCfg *lidarrconfig.Service, lidarrReqs *lidarrrequests.Service, lidarrQuar *lidarrquarantine.Service, tracksSvc *tracks.Service, playlistsSvc *playlists.Service, coverEnricher *coverart.Enricher, coverBackfillCap int, coverSettings *coverart.SettingsService, scanner *library.Scanner, scanCfg library.RunScanConfig, scheduler *library.Scheduler, dataDir string, sender mailer.Sender) {
|
||||
func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, events *playevents.Writer, recCfg config.RecommendationConfig, lidarrCfg *lidarrconfig.Service, lidarrReqs *lidarrrequests.Service, lidarrQuar *lidarrquarantine.Service, tracksSvc *tracks.Service, playlistsSvc *playlists.Service, coverEnricher *coverart.Enricher, coverBackfillCap int, coverSettings *coverart.SettingsService, scanner *library.Scanner, scanCfg library.RunScanConfig, scheduler *library.Scheduler, dataDir string, sender mailer.Sender, bus *eventbus.Bus) {
|
||||
rng := rand.New(rand.NewSource(rand.Int63()))
|
||||
h := &handlers{
|
||||
pool: pool, logger: logger, events: events, recCfg: recCfg,
|
||||
@@ -45,6 +46,7 @@ func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, events *playev
|
||||
scheduler: scheduler,
|
||||
dataDir: dataDir,
|
||||
mailer: sender,
|
||||
eventbus: bus,
|
||||
}
|
||||
|
||||
r.Route("/api", func(api chi.Router) {
|
||||
@@ -79,6 +81,7 @@ func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, events *playev
|
||||
authed.Get("/discover/suggestions", h.handleListSuggestions)
|
||||
authed.Get("/home", h.handleGetHome)
|
||||
authed.Post("/events", h.handleEvents)
|
||||
authed.Get("/events/stream", h.handleEventsStream)
|
||||
authed.Post("/likes/tracks/{id}", h.handleLikeTrack)
|
||||
authed.Delete("/likes/tracks/{id}", h.handleUnlikeTrack)
|
||||
authed.Post("/likes/albums/{id}", h.handleLikeAlbum)
|
||||
@@ -191,4 +194,5 @@ type handlers struct {
|
||||
scheduler *library.Scheduler
|
||||
dataDir string
|
||||
mailer mailer.Sender
|
||||
eventbus *eventbus.Bus
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user