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>
89 lines
2.4 KiB
Go
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)
|
|
}
|