Files
minstrel/internal/api/admin_loudness.go
T
bvandeusenandClaude Opus 5.5 08a2c06b85 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>
2026-10-23 12:23:58 -04:00

90 lines
3.2 KiB
Go

package api
import (
"encoding/json"
"errors"
"net/http"
"git.fabledsword.com/bvandeusen/minstrel/internal/apierror"
"git.fabledsword.com/bvandeusen/minstrel/internal/library"
)
// loudnessCoverageResp is the wire shape for GET /api/admin/library/loudness.
// measured + silent + unreadable + pending = total; missing tracks are not
// counted. Enabled travels with the counts because with
// analysis off, pending never shrinks, and a gauge implying progress would be
// promising work nothing is doing.
type loudnessCoverageResp struct {
Total int64 `json:"total"`
Measured int64 `json:"measured"`
Silent int64 `json:"silent"`
Unreadable int64 `json:"unreadable"`
Pending int64 `json:"pending"`
Enabled bool `json:"enabled"`
}
// handleGetLoudnessCoverage implements GET /api/admin/library/loudness: how
// far the loudness backfill has got. Always 200; zeros on an empty library.
func (h *handlers) handleGetLoudnessCoverage(w http.ResponseWriter, r *http.Request) {
row, err := library.LoudnessCoverage(r.Context(), h.pool)
if err != nil {
writeErrWithLog(w, h.logger, "admin: get loudness coverage", apierror.InternalMsg("lookup failed", err))
return
}
writeJSON(w, http.StatusOK, loudnessCoverageResp{
Total: row.Total,
Measured: row.Measured,
Silent: row.Silent,
Unreadable: row.Unreadable,
Pending: row.Pending,
Enabled: h.loudnessSettings.Get().Enabled,
})
}
// loudnessSettingsBody is the wire shape for GET and PUT
// /api/admin/library/loudness-settings.
type loudnessSettingsBody struct {
Enabled bool `json:"enabled"`
BackfillConcurrency int32 `json:"backfill_concurrency"`
LeveledCacheMB int32 `json:"leveled_cache_mb"`
}
func loudnessSettingsBodyOf(s library.LoudnessSettings) loudnessSettingsBody {
return loudnessSettingsBody{
Enabled: s.Enabled,
BackfillConcurrency: s.BackfillConcurrency,
LeveledCacheMB: s.LeveledCacheMB,
}
}
// handleGetLoudnessSettings implements GET
// /api/admin/library/loudness-settings.
func (h *handlers) handleGetLoudnessSettings(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, http.StatusOK, loudnessSettingsBodyOf(h.loudnessSettings.Get()))
}
// handleUpdateLoudnessSettings implements PUT
// /api/admin/library/loudness-settings. A whole-row write: a body that leaves
// out the concurrency decodes it as zero, which is refused rather than saved.
func (h *handlers) handleUpdateLoudnessSettings(w http.ResponseWriter, r *http.Request) {
var req loudnessSettingsBody
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeErr(w, apierror.BadRequest("invalid_body", "malformed JSON"))
return
}
saved, err := h.loudnessSettings.Set(r.Context(), library.LoudnessSettings{
Enabled: req.Enabled,
BackfillConcurrency: req.BackfillConcurrency,
LeveledCacheMB: req.LeveledCacheMB,
})
if err != nil {
if errors.Is(err, library.ErrLoudnessSettingOutOfRange) {
writeErr(w, apierror.BadRequest("invalid_setting", err.Error()))
return
}
writeErrWithLog(w, h.logger, "admin loudness settings: update failed", apierror.Internal(err))
return
}
writeJSON(w, http.StatusOK, loudnessSettingsBodyOf(saved))
}