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: every registry key with its // stored value (the DB is fully seeded with defaults at startup; the // registry default only fills in here for a key added since the last // boot, e.g. under httptest where main's seeding never ran), plus the env // snapshot. Overridden means "differs from the default", since every key // always has a row. func (s *Server) configView(r *http.Request) (map[string]any, error) { values, 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, ok := values[k.Key] if !ok { value = k.Default } app = append(app, configEntry{ Key: k.Key, Value: value, Default: k.Default, Overridden: value != k.Default, 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) }