Files
geniusrun/backend/internal/api/config.go
Christophe Vila e2b2bf9611 refactor: merge internal/sync into internal/garmin, regroup api files and routes
Garmin auth/sync routes move under /api/garmin/*; sync.Service becomes
garmin.Sync with garmin.SyncConfig/ClientConfig; applog becomes
internal/log; the test mock moves into the garmin package as MockClient
(breaking the test-only import cycle the merge created); stale test
URLs and type names updated to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 16:04:18 +02:00

89 lines
2.4 KiB
Go

package api
import (
"encoding/json"
"net/http"
"geniusrun/backend/internal/config"
)
// EnvVar is one read-only environment-configuration entry, already
// display-safe (masking happens in config.Config.DisplayEnv).
type EnvVar struct {
Name string `json:"name"`
Value string `json:"value"`
}
type configEntry struct {
Key string `json:"key"`
Value string `json:"value"`
Default string `json:"default"`
Overridden bool `json:"overridden"`
Description string `json:"description"`
}
// configView assembles the GET/PUT response: registry defaults overlaid
// with DB overrides, plus the env snapshot.
func (s *Server) configView(r *http.Request) (map[string]any, error) {
overrides, err := s.DB.ConfigValues(r.Context())
if err != nil {
return nil, err
}
registry := config.AppRegistry()
app := make([]configEntry, 0, len(registry))
for _, k := range registry {
value, overridden := overrides[k.Key]
if !overridden {
value = k.Default
}
app = append(app, configEntry{
Key: k.Key, Value: value, Default: k.Default,
Overridden: overridden, Description: k.Description,
})
}
envVars := s.EnvVars
if envVars == nil {
envVars = []EnvVar{}
}
return map[string]any{"application": app, "environment": envVars}, nil
}
func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
resp, err := s.configView(r)
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
writeJSON(w, http.StatusOK, resp)
}
// handlePutConfig updates application configuration. Cold: saved values
// take effect on the next backend restart; the response is just the
// refreshed view, same shape as GET.
func (s *Server) handlePutConfig(w http.ResponseWriter, r *http.Request) {
var body map[string]string
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeError(w, http.StatusBadRequest, "invalid request body")
return
}
// Validate every pair before writing anything -- all-or-nothing.
for key, value := range body {
if err := config.ValidateAppValue(key, value); err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
}
for key, value := range body {
if err := s.DB.SetConfigValue(r.Context(), key, value); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
}
resp, err := s.configView(r)
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
writeJSON(w, http.StatusOK, resp)
}