handleStar's track branch calls playevents.CaptureContextualLikeIfPlaying
when the underlying LikeTrack actually inserted a row. handleUnstar
calls SoftDeleteContextualLikes after every track-id unstar. Same
helpers as the api surface — single source of truth.
mediaHandlers struct gains a logger field threaded through Mount.
Both return user's starred artists/albums/songs sorted liked_at DESC.
Cap at 500 entries per category for v1 (Subsonic spec doesn't define
pagination on these endpoints; M3+ can revisit if needed).
Validate-all-first atomicity on star: a missing entity refuses the whole
call with Subsonic error 70. Unstar is a best-effort delete (missing
entities are no-ops, matching client expectations after library moves).
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.
Feishin's first-login flow hits getUser to discover the authenticated
identity's roles. We never registered the route, so chi returned a
plain-text 404 — Feishin's parser treats anything non-Subsonic as a
generic auth failure ("Failed to log in"), masking the real cause.
- Implement /rest/getUser with the full role bag. Admins get every
role; non-admins get the play-music subset. Single-user M1 means
cross-user lookups by admins return the caller's roles for now;
revisit when user management lands.
- Set sub.NotFound on /rest/* to emit a Subsonic envelope (code 0,
"Method not implemented") instead of plain text. Any future client
probing an unimplemented endpoint now sees a parseable failure.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>