refactor: POST env vars as JSON array instead of KEY=value string
All checks were successful
Check / check (pull_request) Successful in 4s

Replace the string-serialized KEY=value format with a proper JSON array
of {key, value} objects for the env var save endpoint.

Frontend changes:
- envVarEditor.submitAll() now uses fetch() with Content-Type:
  application/json and X-CSRF-Token header instead of form submission
- Sends JSON array: [{"key":"FOO","value":"bar"}, ...]
- Hidden bulk form replaced with hidden div holding CSRF token
- envVarEditor now receives appId parameter for the fetch URL

Backend changes:
- HandleEnvVarSave reads JSON body via json.NewDecoder instead of
  parsing form values with parseEnvPairs
- Returns JSON {"ok": true} instead of HTTP redirect
- Removed parseEnvPairs function and envPair struct entirely
- Added envPairJSON struct with json tags for deserialization

Tests updated to POST JSON arrays instead of form-encoded strings.

Closes #163
This commit is contained in:
clawbot
2026-03-10 11:37:55 -07:00
parent 3f96f4f81b
commit df6aad9b21
4 changed files with 54 additions and 74 deletions

View File

@@ -903,9 +903,16 @@ func (h *Handlers) addKeyValueToApp(
http.Redirect(writer, request, "/apps/"+application.ID, http.StatusSeeOther)
}
// envPairJSON represents a key-value pair in the JSON request body.
type envPairJSON struct {
Key string `json:"key"`
Value string `json:"value"`
}
// HandleEnvVarSave handles bulk saving of all environment variables.
// It deletes all existing env vars for the app and inserts the full
// submitted set (monolithic list approach).
// It reads a JSON array of {key, value} objects from the request body,
// deletes all existing env vars for the app, and inserts the full
// submitted set.
func (h *Handlers) HandleEnvVarSave() http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
appID := chi.URLParam(request, "id")
@@ -917,14 +924,15 @@ func (h *Handlers) HandleEnvVarSave() http.HandlerFunc {
return
}
parseErr := request.ParseForm()
if parseErr != nil {
var pairs []envPairJSON
decodeErr := json.NewDecoder(request.Body).Decode(&pairs)
if decodeErr != nil {
http.Error(writer, "Bad Request", http.StatusBadRequest)
return
}
pairs := parseEnvPairs(request.FormValue("env_vars"))
ctx := request.Context()
// Delete all existing env vars for this app
@@ -944,59 +952,23 @@ func (h *Handlers) HandleEnvVarSave() http.HandlerFunc {
for _, p := range pairs {
envVar := models.NewEnvVar(h.db)
envVar.AppID = application.ID
envVar.Key = p.key
envVar.Value = p.value
envVar.Key = p.Key
envVar.Value = p.Value
saveErr := envVar.Save(ctx)
if saveErr != nil {
h.log.Error(
"failed to save env var",
"key", p.key,
"key", p.Key,
"error", saveErr,
)
}
}
http.Redirect(
writer,
request,
"/apps/"+appID+"?success=env-updated",
http.StatusSeeOther,
)
h.respondJSON(writer, request, map[string]bool{"ok": true}, http.StatusOK)
}
}
// envPair holds a parsed key-value pair from the env vars textarea.
type envPair struct {
key string
value string
}
// parseEnvPairs parses KEY=VALUE lines from a textarea string.
// Blank lines and lines starting with # are skipped.
func parseEnvPairs(text string) []envPair {
var pairs []envPair
for line := range strings.SplitSeq(text, "\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
key, value, ok := strings.Cut(line, "=")
if !ok || strings.TrimSpace(key) == "" {
continue
}
pairs = append(pairs, envPair{
key: strings.TrimSpace(key),
value: value,
})
}
return pairs
}
// HandleLabelAdd handles adding a label.
func (h *Handlers) HandleLabelAdd() http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {