feat(api): GET/PUT /api/config serving app + env configuration
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
88
backend/internal/api/appconfig.go
Normal file
88
backend/internal/api/appconfig.go
Normal file
@@ -0,0 +1,88 @@
|
||||
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 appConfigEntry 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([]appConfigEntry, 0, len(registry))
|
||||
for _, k := range registry {
|
||||
value, overridden := overrides[k.Key]
|
||||
if !overridden {
|
||||
value = k.Default
|
||||
}
|
||||
app = append(app, appConfigEntry{
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user