docs: api comments say what the endpoints do, not how they came to be (#6277)
Rewrites the internal/api comment blocks to rule 32: task, milestone and rule ids gone, the stories behind them restated as the constraint they left, stale references corrected (the stream secret is wired from config, the timezone handler already refreshes the scheduler, the Android app is the native client, the design docs cited are not in the repo). Also rewraps comment lines over 80 columns left by the earlier batches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+47
-52
@@ -1,5 +1,5 @@
|
||||
// Package api implements Minstrel's native JSON surface under /api. It is
|
||||
// consumed by the built-in web SPA and (eventually) the Flutter client.
|
||||
// consumed by the built-in web SPA and the Android app.
|
||||
// Subsonic-compatible endpoints under /rest are intentionally separate —
|
||||
// see internal/subsonic — and the two packages must not depend on each other.
|
||||
package api
|
||||
@@ -105,15 +105,13 @@ func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, events *playev
|
||||
// query token (UPnP / Sonos path; see streamAuthOk). The
|
||||
// middleware attaches user to context when a valid cookie /
|
||||
// bearer is present but does NOT 401 on absence; the handler's
|
||||
// own streamAuthOk performs the actual auth check. See the
|
||||
// design at
|
||||
// docs/superpowers/specs/2026-06-03-android-output-picker-upnp-design.md.
|
||||
// own streamAuthOk performs the actual auth check.
|
||||
api.With(auth.OptionalUser(pool, logger)).Get("/tracks/{id}/stream", h.handleGetStream)
|
||||
// Extension-bearing alias so Sonos's URL probe can identify the
|
||||
// audio format from the path. The {ext} param is consumed by chi
|
||||
// and ignored by the handler (which keys off {id}). See task #610.
|
||||
// and ignored by the handler (which keys off {id}).
|
||||
api.With(auth.OptionalUser(pool, logger)).Get("/tracks/{id}/stream.{ext}", h.handleGetStream)
|
||||
// The leveled stream for Sonos/UPnP (M464 #5001): session or a
|
||||
// The leveled stream for Sonos/UPnP: session or a
|
||||
// leveled token, like the plain stream.
|
||||
api.With(auth.OptionalUser(pool, logger)).Get("/tracks/{id}/leveled.flac", h.handleGetLeveledStream)
|
||||
|
||||
@@ -154,7 +152,7 @@ func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, events *playev
|
||||
authed.Get("/albums/{id}/cover", h.handleGetCover)
|
||||
authed.Get("/library/shuffle", h.handleLibraryShuffle)
|
||||
authed.Get("/library/albums", h.handleListLibraryAlbums)
|
||||
// Browse indexes (#367). Genre filtering rides
|
||||
// Browse indexes. Genre filtering rides
|
||||
// /library/albums?genre= rather than a path segment, because raw
|
||||
// ID3 genres contain slashes ("Rock/Pop") that a path can't carry.
|
||||
authed.Get("/library/genres", h.handleListGenres)
|
||||
@@ -169,8 +167,8 @@ func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, events *playev
|
||||
authed.Get("/search", h.handleSearch)
|
||||
authed.Get("/radio", h.handleRadio)
|
||||
authed.Get("/discover/suggestions", h.handleListSuggestions)
|
||||
// Snooze = "not right now", time-boxed and self-expiring
|
||||
// (#2374). Not a dislike — see the migration for why.
|
||||
// Snooze = "not right now", time-boxed and self-expiring.
|
||||
// Not a dislike — see the migration for why.
|
||||
authed.Post("/discover/suggestions/{mbid}/snooze", h.handleSnoozeSuggestion)
|
||||
authed.Delete("/discover/suggestions/{mbid}/snooze", h.handleUnsnoozeSuggestion)
|
||||
authed.Get("/discover/snoozes", h.handleListSuggestionSnoozes)
|
||||
@@ -178,10 +176,8 @@ func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, events *playev
|
||||
authed.Get("/home/index", h.handleGetHomeIndex)
|
||||
authed.Post("/events", h.handleEvents)
|
||||
authed.Get("/events/stream", h.handleEventsStream)
|
||||
// UPnP / Sonos cast slice: issue a short-lived HMAC stream URL
|
||||
// the speaker can fetch without the user's session. See the
|
||||
// design at
|
||||
// docs/superpowers/specs/2026-06-03-android-output-picker-upnp-design.md.
|
||||
// UPnP / Sonos casting: issue a short-lived HMAC stream URL the
|
||||
// speaker can fetch without the user's session.
|
||||
authed.Post("/cast/stream-token", h.handleCastStreamToken)
|
||||
authed.Post("/likes/tracks/{id}", h.handleLikeTrack)
|
||||
authed.Delete("/likes/tracks/{id}", h.handleUnlikeTrack)
|
||||
@@ -194,7 +190,7 @@ func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, events *playev
|
||||
authed.Get("/likes/artists", h.handleListLikedArtists)
|
||||
authed.Get("/likes/ids", h.handleGetLikedIDs)
|
||||
|
||||
// The release catalogue (M502 #5519): request search asks
|
||||
// The release catalogue: request search asks
|
||||
// MusicBrainz, and art for what isn't held comes through
|
||||
// Minstrel from the enabled cover-art sources.
|
||||
authed.Get("/catalogue/search", h.handleCatalogueSearch)
|
||||
@@ -213,13 +209,13 @@ func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, events *playev
|
||||
// load failures). Admin-only inbox; any user can report.
|
||||
authed.Post("/playback-errors", h.handleReportPlaybackError)
|
||||
|
||||
// Device diagnostics ingest (M9). Any signed-in user can
|
||||
// Device diagnostics ingest. Any signed-in user can
|
||||
// POST a batch, but events are only stored when the
|
||||
// account's debug_mode_enabled flag is on (handler no-ops
|
||||
// otherwise). Admin views live under /admin/diagnostics.
|
||||
authed.Post("/diagnostics", h.handleReportDiagnostics)
|
||||
|
||||
// Self-hosted in-app update channel (#397). Auth-gated to
|
||||
// Self-hosted in-app update channel. Auth-gated to
|
||||
// prevent anonymous bandwidth abuse on the APK stream;
|
||||
// /apk additionally per-user rate-limited.
|
||||
authed.Get("/client/version", h.handleClientVersion)
|
||||
@@ -236,14 +232,15 @@ func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, events *playev
|
||||
admin.Delete("/acquisition/services/{code}/api-key", h.handleSetAcquisitionSecret("api_key"))
|
||||
admin.Put("/acquisition/services/{code}/password", h.handleSetAcquisitionSecret("password"))
|
||||
admin.Delete("/acquisition/services/{code}/password", h.handleSetAcquisitionSecret("password"))
|
||||
// The queues that wait on an admin, for the rail and the Overview (#5605).
|
||||
// The queues that wait on an admin, for the rail and the
|
||||
// Overview.
|
||||
admin.Get("/summary", h.handleGetAdminSummary)
|
||||
admin.Get("/status", h.handleGetAdminStatus)
|
||||
admin.Get("/requests", h.handleListAdminRequests)
|
||||
admin.Post("/requests/{id}/approve", h.handleApproveRequest)
|
||||
admin.Post("/requests/{id}/reject", h.handleRejectRequest)
|
||||
|
||||
// The wanted list (M502 #5520): what Minstrel is looking
|
||||
// The wanted list: what Minstrel is looking
|
||||
// for, the artists it follows, and the release-type
|
||||
// profiles that decide which of their releases count.
|
||||
admin.Get("/wanted", h.handleListWants)
|
||||
@@ -253,7 +250,7 @@ func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, events *playev
|
||||
admin.Post("/wanted/unmatched/{id}/leave-off", h.handleLeaveUnmatchedOff)
|
||||
admin.Post("/wanted/unmatched/{id}/put-back", h.handlePutUnmatchedBack)
|
||||
admin.Delete("/wanted/{id}", h.handleRemoveWant)
|
||||
// Searching and grabbing (M502 #5521).
|
||||
// Searching and grabbing.
|
||||
admin.Get("/acquisition/settings", h.handleGetSearchSettings)
|
||||
admin.Put("/acquisition/settings", h.handlePutSearchSettings)
|
||||
admin.Get("/wanted/{id}/candidates", h.handleListCandidates)
|
||||
@@ -294,12 +291,12 @@ func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, events *playev
|
||||
admin.Put("/network-settings", h.handleUpdateNetworkSettings)
|
||||
|
||||
// Policy for wanting back an album whose files went missing
|
||||
// (#290). Sits beside the missing-files list it governs,
|
||||
// because the operator meets it on that surface.
|
||||
// Sits beside the missing-files list it governs, because an
|
||||
// admin meets it on that surface.
|
||||
admin.Get("/library/reacquisition", h.handleGetReacquisitionSettings)
|
||||
admin.Put("/library/reacquisition", h.handleUpdateReacquisitionSettings)
|
||||
|
||||
// Where the library lives (#6129). A changed folder moves its
|
||||
// Where the library lives. A changed folder moves its
|
||||
// tracks rather than re-adding them.
|
||||
admin.Get("/library/folders", h.handleGetLibraryFolders)
|
||||
admin.Put("/library/folders", h.handlePutLibraryFolders)
|
||||
@@ -309,7 +306,7 @@ func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, events *playev
|
||||
// Sits under /library rather than /tracks because what it
|
||||
// reports is a property of the library's relationship to disk,
|
||||
// and because the destructive /tracks/{id} route above must
|
||||
// not be mistaken for it (#2527).
|
||||
// not be mistaken for it.
|
||||
admin.Get("/library/missing", h.handleListMissingTracks)
|
||||
admin.Get("/library/suspect-sources", h.handleListSuspectSources)
|
||||
admin.Put("/library/suspect-sources/{id}", h.handleSetSuspectSourceVerdict)
|
||||
@@ -325,9 +322,10 @@ func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, events *playev
|
||||
admin.Put("/library/acoustid-settings", h.handleUpdateAcoustIDSettings)
|
||||
admin.Post("/library/acoustid/run", h.handleRunAcoustIDLookup)
|
||||
admin.Get("/library/acoustid/unsettled", h.handleListUnsettledAcoustID)
|
||||
// Duplicates report (#3912): proposals from the duplicate sweep, a
|
||||
// trigger to sweep now, dismissal, and the merge (#3911), which deletes
|
||||
// the removed copies' files after moving their history onto the kept one.
|
||||
// Duplicates report: proposals from the duplicate sweep, a
|
||||
// trigger to sweep now, dismissal, and the merge, which deletes
|
||||
// the removed copies' files after moving their history onto the
|
||||
// kept one.
|
||||
admin.Get("/library/duplicates", h.handleListDuplicates)
|
||||
admin.Get("/library/duplicates/resolved", h.handleListResolvedDuplicates)
|
||||
admin.Post("/library/duplicates/sweep", h.handleRunDuplicateSweep)
|
||||
@@ -346,7 +344,7 @@ func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, events *playev
|
||||
admin.Put("/users/{id}/auto-approve", h.handleAdminAutoApproveToggle)
|
||||
admin.Put("/users/{id}/debug-mode", h.handleAdminDebugModeToggle)
|
||||
|
||||
// Device diagnostics timeline + device overview (M9).
|
||||
// Device diagnostics timeline + device overview.
|
||||
admin.Get("/diagnostics", h.handleListAdminDiagnostics)
|
||||
admin.Get("/diagnostics/devices", h.handleListAdminDiagnosticDevices)
|
||||
|
||||
@@ -366,12 +364,12 @@ func Mount(r chi.Router, pool *pgxpool.Pool, logger *slog.Logger, events *playev
|
||||
admin.Get("/notification-email", h.handleGetNotificationEmail)
|
||||
admin.Put("/notification-email", h.handleUpdateNotificationEmail)
|
||||
|
||||
// Recommendation tuning lab (#1250): scoring-weight
|
||||
// Recommendation tuning: scoring-weight
|
||||
// profiles + taste-build knobs, DB-backed, live effect.
|
||||
admin.Get("/recommendation-tuning", h.handleGetRecommendationTuning)
|
||||
admin.Patch("/recommendation-tuning/{scope}", h.handlePatchRecommendationTuning)
|
||||
admin.Post("/recommendation-tuning/{scope}/reset", h.handleResetRecommendationTuning)
|
||||
// Weekly outcome trends + knob-turn markers (#1251).
|
||||
// Weekly outcome trends + knob-turn markers.
|
||||
admin.Get("/recommendation-trends", h.handleGetRecommendationTrends)
|
||||
})
|
||||
|
||||
@@ -398,7 +396,7 @@ type handlers struct {
|
||||
recSettings *recsettings.Service
|
||||
rng func() float64
|
||||
// librarySize memoises the track count that sizes the candidate pool
|
||||
// (#3880). Held here rather than counted per request: the count is a
|
||||
// Held here rather than counted per request: the count is a
|
||||
// full table scan, and library size only moves when a scan runs.
|
||||
librarySize *recommendation.LibrarySize
|
||||
requests *requests.Service
|
||||
@@ -413,47 +411,47 @@ type handlers struct {
|
||||
dataDir string
|
||||
mailer mailer.Sender
|
||||
eventbus *eventbus.Bus
|
||||
// notifier writes the notifications inbox (M489). Nil-safe: a nil
|
||||
// notifier writes the notifications inbox. Nil-safe: a nil
|
||||
// notifier records nothing, which is what most handler tests want.
|
||||
notifier *notifications.Notifier
|
||||
playlistScheduler *playlists.Scheduler
|
||||
// reacqSettings is the DB-backed policy for auto re-acquisition of
|
||||
// missing files (milestone #290) — grace window, backoff, attempt caps.
|
||||
// missing files — grace window, backoff, attempt caps.
|
||||
// Cached in the service, so the admin card reads it without a query.
|
||||
reacqSettings *reacquisition.SettingsService
|
||||
// fingerprintSettings is the fingerprinting policy (M400 #3913), the same
|
||||
// fingerprintSettings is the fingerprinting policy, the same
|
||||
// instance the scanner and the fingerprint workers read, so a save from the
|
||||
// admin card reaches them without a restart. Nil serves the defaults.
|
||||
fingerprintSettings *library.FingerprintSettingsService
|
||||
// loudnessSettings is the loudness analysis policy (M464 #4995), the same
|
||||
// loudnessSettings is the loudness analysis policy, the same
|
||||
// instance the loudness backfill reads. Nil serves the defaults.
|
||||
loudnessSettings *library.LoudnessSettingsService
|
||||
// acoustIDLookup is the AcoustID lookup worker (M401): its settings, its
|
||||
// acoustIDLookup is the AcoustID lookup worker: its settings, its
|
||||
// status and "look up now". Nil serves the defaults (off).
|
||||
acoustIDLookup *library.AcoustIDLookupWorker
|
||||
// acquisition is the operator's indexers and download clients (M502).
|
||||
// acquisition is the configured indexers and download clients.
|
||||
acquisition *acquire.Store
|
||||
// catalogue is the release catalogue read from MusicBrainz, and
|
||||
// catalogueArt the images for what isn't held (M502 #5519).
|
||||
// catalogueArt the images for what isn't held.
|
||||
catalogue *catalogue.Service
|
||||
catalogueArt *catalogue.Art
|
||||
// wanted is the wanted list: follows, release-type profiles and wants
|
||||
// (M502 #5520). Its worker runs in main; the API reads and edits.
|
||||
// wanted is the wanted list: follows, release-type profiles and wants.
|
||||
// Its worker runs in main; the API reads and edits.
|
||||
wanted *wanted.Service
|
||||
// searcher searches for wants and grabs releases for them (#5521).
|
||||
// searcher searches for wants and grabs releases for them.
|
||||
searcher *wanted.Searcher
|
||||
// importer refuses a download at a person's word (#5522). The import
|
||||
// importer refuses a download at a person's word. The import
|
||||
// loop itself runs in main; this one only acts on a request.
|
||||
importer *importer.Importer
|
||||
// lifecycle gives up on a download at a person's word (#5523); its loop
|
||||
// lifecycle gives up on a download at a person's word; its loop
|
||||
// runs in main.
|
||||
lifecycle *lifecycle.Service
|
||||
// catalogueSearches reuses recent MusicBrainz answers; nil asks every
|
||||
// time, which is what handler tests get.
|
||||
catalogueSearches *searchCache
|
||||
// leveled renders the leveled streams handed to Sonos/UPnP speakers
|
||||
// (M464 #5001). Nil when its cache directory cannot be made: a level
|
||||
// request then gets the plain stream.
|
||||
// leveled renders the leveled streams handed to Sonos/UPnP speakers. Nil
|
||||
// when its cache directory cannot be made: a level request then gets the
|
||||
// plain stream.
|
||||
leveled *library.LeveledRenderer
|
||||
// setupToken must accompany the first registration while no users exist
|
||||
// (see auth.SetupToken). requireSetupToken is set by Mount, the only
|
||||
@@ -473,13 +471,10 @@ type handlers struct {
|
||||
// middleware on every request and edited from the admin network card.
|
||||
netSettings *netsettings.Service
|
||||
// streamSecret is the HMAC key used by SignStreamToken /
|
||||
// VerifyStreamToken to authenticate the UPnP-speaker stream path
|
||||
// (see internal/api/stream_token.go and the design at
|
||||
// docs/superpowers/specs/2026-06-03-android-output-picker-upnp-design.md).
|
||||
// nil in slice 1; slice 2 wires the env-var-with-app_preferences-
|
||||
// fallback loader. A nil secret leaves the cookie path intact and
|
||||
// makes the token path unreachable (HMAC of empty key won't match
|
||||
// anything a client mints), which is the desired slice-1 default.
|
||||
// VerifyStreamToken to authenticate the UPnP-speaker stream path (see
|
||||
// internal/api/stream_token.go), from config.Config.StreamSecret. A nil
|
||||
// secret leaves the cookie path intact and makes the token path
|
||||
// unreachable (HMAC of empty key won't match anything a client mints).
|
||||
streamSecret []byte
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user