package control import ( "encoding/json" "net/http" "sync" "github.com/jan/fm-rds-tx/internal/config" drypkg "github.com/jan/fm-rds-tx/internal/dryrun" ) type Server struct { mu sync.RWMutex cfg config.Config } type ConfigPatch struct { FrequencyMHz *float64 `json:"frequencyMHz,omitempty"` OutputDrive *float64 `json:"outputDrive,omitempty"` ToneLeftHz *float64 `json:"toneLeftHz,omitempty"` ToneRightHz *float64 `json:"toneRightHz,omitempty"` ToneAmplitude *float64 `json:"toneAmplitude,omitempty"` PS *string `json:"ps,omitempty"` RadioText *string `json:"radioText,omitempty"` } func NewServer(cfg config.Config) *Server { return &Server{cfg: cfg} } func (s *Server) Handler() http.Handler { mux := http.NewServeMux() mux.HandleFunc("/healthz", s.handleHealth) mux.HandleFunc("/status", s.handleStatus) mux.HandleFunc("/dry-run", s.handleDryRun) mux.HandleFunc("/config", s.handleConfig) return mux } func (s *Server) handleHealth(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]any{"ok": true}) } func (s *Server) handleStatus(w http.ResponseWriter, _ *http.Request) { s.mu.RLock() cfg := s.cfg s.mu.RUnlock() w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]any{ "service": "fm-rds-tx", "backend": cfg.Backend.Kind, "frequencyMHz": cfg.FM.FrequencyMHz, "stereoEnabled": cfg.FM.StereoEnabled, "rdsEnabled": cfg.RDS.Enabled, "toneLeftHz": cfg.Audio.ToneLeftHz, "toneRightHz": cfg.Audio.ToneRightHz, }) } func (s *Server) handleDryRun(w http.ResponseWriter, _ *http.Request) { s.mu.RLock() cfg := s.cfg s.mu.RUnlock() w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(drypkg.Generate(cfg)) } func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: s.mu.RLock() cfg := s.cfg s.mu.RUnlock() w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(cfg) case http.MethodPost: var patch ConfigPatch if err := json.NewDecoder(r.Body).Decode(&patch); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } s.mu.Lock() next := s.cfg if patch.FrequencyMHz != nil { next.FM.FrequencyMHz = *patch.FrequencyMHz } if patch.OutputDrive != nil { next.FM.OutputDrive = *patch.OutputDrive } if patch.ToneLeftHz != nil { next.Audio.ToneLeftHz = *patch.ToneLeftHz } if patch.ToneRightHz != nil { next.Audio.ToneRightHz = *patch.ToneRightHz } if patch.ToneAmplitude != nil { next.Audio.ToneAmplitude = *patch.ToneAmplitude } if patch.PS != nil { next.RDS.PS = *patch.PS } if patch.RadioText != nil { next.RDS.RadioText = *patch.RadioText } if err := next.Validate(); err != nil { s.mu.Unlock() http.Error(w, err.Error(), http.StatusBadRequest) return } s.cfg = next s.mu.Unlock() w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]any{"ok": true}) default: http.Error(w, "method not allowed", http.StatusMethodNotAllowed) } }