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"` } func fingerprintSettingsBodyOf(s library.FingerprintSettings) fingerprintSettingsBody { return fingerprintSettingsBody{ Enabled: s.Enabled, ChromaprintLengthSec: s.ChromaprintLengthSec, AcousticMaxBitErrorRate: s.AcousticMaxBitErrorRate, BackfillConcurrency: s.BackfillConcurrency, SweepIntervalHours: s.SweepIntervalHours, } } // 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. // 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 } saved, err := h.fingerprintSettings.Set(r.Context(), library.FingerprintSettings{ Enabled: req.Enabled, ChromaprintLengthSec: req.ChromaprintLengthSec, AcousticMaxBitErrorRate: req.AcousticMaxBitErrorRate, BackfillConcurrency: req.BackfillConcurrency, SweepIntervalHours: req.SweepIntervalHours, }) 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)) }