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
+11 -2
View File
@@ -11,10 +11,13 @@ import (
"github.com/go-chi/chi/v5/middleware"
"github.com/jackc/pgx/v5/pgxpool"
"time"
"git.fabledsword.com/bvandeusen/minstrel/internal/api"
"git.fabledsword.com/bvandeusen/minstrel/internal/auth"
"git.fabledsword.com/bvandeusen/minstrel/internal/config"
"git.fabledsword.com/bvandeusen/minstrel/internal/library"
"git.fabledsword.com/bvandeusen/minstrel/internal/playevents"
"git.fabledsword.com/bvandeusen/minstrel/internal/subsonic"
"git.fabledsword.com/bvandeusen/minstrel/web"
)
@@ -45,14 +48,20 @@ func (s *Server) Router() http.Handler {
r.Get("/healthz", s.handleHealthz)
if s.Pool != nil {
api.Mount(r, s.Pool, s.Logger, s.EventsCfg)
writer := playevents.NewWriter(
s.Pool, s.Logger,
time.Duration(s.EventsCfg.SessionTimeoutMinutes)*time.Minute,
s.EventsCfg.SkipMaxCompletionRatio,
s.EventsCfg.SkipMaxDurationPlayedMs,
)
api.Mount(r, s.Pool, s.Logger, writer)
r.Route("/api/admin", func(admin chi.Router) {
admin.Use(auth.RequireAdmin(s.Pool))
if s.Scanner != nil {
admin.Post("/scan", s.handleAdminScan)
}
})
subsonic.Mount(r, s.Pool, s.Logger, s.SubsonicCfg)
subsonic.Mount(r, s.Pool, s.Logger, s.SubsonicCfg, writer)
}
spa := web.Handler()