Lets the M7 Flutter client refuse to operate against an incompatible
server (e.g., new endpoints the client depends on, breaking schema
shifts). The version constant is bumped on each release where the
client/server contract changes; old clients show the user a blocking
"update required" modal pointed at the latest APK / TestFlight build.
Bug: r.Route("/api/admin", ...) in server.go for the /scan endpoint
created a second chi subtree at the same prefix as the nested admin
Route inside api.Mount. chi.Walk shows both subtrees registered, but
runtime routing dispatch only matched one — so every /api/admin/*
endpoint registered by api.Mount (Lidarr config, quarantine admin,
requests admin) silently returned 404 to authenticated callers.
Has shipped this way since 06a1fe1; M5a/b/c admin tests didn't catch
it because they call api.Mount on a fresh chi.NewRouter() rather than
going through Server.Router(), so only one /api/admin registration
existed in those tests.
Fix: register /api/admin/scan as a single inline-middleware route
(r.With(...).Post(...)) instead of a Route subtree, eliminating the
duplicate /api/admin mount.
Adds a regression test that uses a real seeded admin session to make
an authenticated GET /api/admin/lidarr/config and asserts != 404.
Verified the test fails without the fix and passes with it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Validates seed_track + optional limit (default cfg.Recommendation.RadioSize,
clamped to RadioSizeMax). Calls recommendation.LoadCandidates +
recommendation.Shuffle. Prepends seed to result. Server seeds a
math/rand source at startup; handlers package threads that as a
func() float64 so tests inject deterministic RNGs.
Mount + server.New gain a RecommendationConfig parameter.
Discriminated-union JSON over three event types: play_started,
play_ended, play_skipped. Auth via existing RequireUser. play_started
returns play_event_id + session_id; the other two return { ok: true }.
Validates ownership: play_ended/play_skipped on another user's row
returns 403. Mount signature now takes EventsConfig and constructs
the playevents.Writer; server.New propagates it through.
- NotFound wrapper routes unknown /api/* and /rest/* to a JSON 404
- Everything else falls through to the embedded web handler, which
resolves static assets or returns index.html for SPA deep links