package api import ( "encoding/json" "errors" "net/http" "git.fabledsword.com/bvandeusen/minstrel/internal/apierror" "git.fabledsword.com/bvandeusen/minstrel/internal/library" ) // fingerprintSettingsBody is the wire shape for GET and PUT // /api/admin/library/fingerprint-settings (M400 #3913). The threshold travels as // the bit-error rate the matcher uses; the card presents it as a match percentage. type fingerprintSettingsBody struct { Enabled bool `json:"enabled"` ChromaprintLengthSec int32 `json:"chromaprint_length_sec"` AcousticMaxBitErrorRate float64 `json:"acoustic_max_bit_error_rate"` BackfillConcurrency int32 `json:"backfill_concurrency"` SweepIntervalHours int32 `json:"sweep_interval_hours"` // AutoResolve is a pointer so a client that predates it (M498) and leaves // it out keeps the saved value rather than switching the resolver off. AutoResolve *bool `json:"auto_resolve"` } func fingerprintSettingsBodyOf(s library.FingerprintSettings) fingerprintSettingsBody { return fingerprintSettingsBody{ Enabled: s.Enabled, ChromaprintLengthSec: s.ChromaprintLengthSec, AcousticMaxBitErrorRate: s.AcousticMaxBitErrorRate, BackfillConcurrency: s.BackfillConcurrency, SweepIntervalHours: s.SweepIntervalHours, AutoResolve: &s.AutoResolve, } } // handleGetFingerprintSettings implements GET /api/admin/library/fingerprint-settings. func (h *handlers) handleGetFingerprintSettings(w http.ResponseWriter, _ *http.Request) { writeJSON(w, http.StatusOK, fingerprintSettingsBodyOf(h.fingerprintSettings.Get())) } // handleUpdateFingerprintSettings implements PUT /api/admin/library/fingerprint-settings. // // A whole-row write. A body that leaves a field out decodes it as zero, which no // field accepts, so a partial save is refused rather than zeroing what it omitted. // auto_resolve is the exception: left out, it keeps its saved value. // The saved settings reach the scanner and both workers at once: they share the // service instance. func (h *handlers) handleUpdateFingerprintSettings(w http.ResponseWriter, r *http.Request) { var req fingerprintSettingsBody if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeErr(w, apierror.BadRequest("invalid_body", "malformed JSON")) return } autoResolve := h.fingerprintSettings.Get().AutoResolve if req.AutoResolve != nil { autoResolve = *req.AutoResolve } saved, err := h.fingerprintSettings.Set(r.Context(), library.FingerprintSettings{ Enabled: req.Enabled, ChromaprintLengthSec: req.ChromaprintLengthSec, AcousticMaxBitErrorRate: req.AcousticMaxBitErrorRate, BackfillConcurrency: req.BackfillConcurrency, SweepIntervalHours: req.SweepIntervalHours, AutoResolve: autoResolve, }) if err != nil { // Validation mirrors migration 0061's CHECKs and names the field. if errors.Is(err, library.ErrFingerprintSettingOutOfRange) { writeErr(w, apierror.BadRequest("invalid_setting", err.Error())) return } writeErrWithLog(w, h.logger, "admin fingerprint settings: update failed", apierror.Internal(err)) return } writeJSON(w, http.StatusOK, fingerprintSettingsBodyOf(saved)) }