feat(subsonic): wire /rest/scrobble into playevents.Writer

submission=false → play_started (replaces in-memory nowPlayingMap).
submission=true (default) → synthetic completed play. The nowPlayingMap
struct + factory + the nowPlaying field on mediaHandlers are deleted;
play_events WHERE ended_at IS NULL is now the source of truth for
'currently playing,' reachable from M3+ work.

api.Mount now takes the shared *playevents.Writer instead of cfg so
the same writer instance feeds both surfaces.
This commit is contained in:
2026-04-26 00:23:20 -04:00
parent f4e73b81b4
commit 599a19f780
7 changed files with 189 additions and 94 deletions
+4 -2
View File
@@ -11,15 +11,17 @@ import (
"github.com/go-chi/chi/v5"
"github.com/jackc/pgx/v5/pgxpool"
"git.fabledsword.com/bvandeusen/minstrel/internal/playevents"
)
// Mount attaches Subsonic handlers at /rest on r. Endpoints are exposed at
// both /rest/foo and /rest/foo.view because client conventions vary. Both
// GET and POST are accepted; Subsonic's auth params live in the query string
// either way.
func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, cfg Config) {
func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, cfg Config, events *playevents.Writer) {
b := &browseHandlers{pool: pool}
m := newMediaHandlers(pool)
m := newMediaHandlers(pool, events)
r.Route("/rest", func(sub chi.Router) {
sub.Use(Middleware(pool, cfg))
register(sub, "/ping", handlePing)