Store a target's settings as columns instead of a per-type JSON string #543

Closed
opened 2026-10-15 11:57:19 +02:00 by clawbot · 2 comments
Collaborator

Approved by sneak on #499 (comment).

Change. Target.Config, a JSON string whose shape depends on Type, is replaced by columns on targets: url (http and slack), headers (http), timeout_seconds (http), forward_query (http), archive_expiry and archive_rotation (database). A column a type does not use stays empty. headers holds the header map as JSON text, because it is a list; every other setting is a plain value. max_attempts is already a column by then (#540).

Why. Every reader of a target parses the JSON first, so each carries a "configuration could not be read" branch: the view, the edit form, the delivery, the archive sweep, the redactor. The forms encode JSON; http and slack spell the URL differently (url, webhookUrl); the URL, which can carry a credential, lives inside a string that must be hidden as a unit. As columns the struct says what a target can have, validation happens once at the form, the URL is masked as one field, and the "could not be read" branches go.

What it touches. internal/db/migrations/main/001_schema.sql (pre-1.0: edit the file) and internal/database/model_target.go; internal/delivery/target_http.go (HTTPTargetConfig, parseHTTPConfig), target_headers.go, target_slack.go (SlackTargetConfig, parseSlackConfig), the archive target's settings parsing (databaseTargetConfig, parseArchiveExpiry, parseArchiveRotation, in internal/archive by then), target_config_view.go, target_config_edit.go, target_redact.go (targetSecrets), and wherever a delivery task copies the config; internal/handlers/target_create.go (buildTargetConfig, its builders, marshalTargetConfig), target_edit.go (configUnreadableMessage); the templates; the README Target section.

Definition of done. No JSON config column and no per-type config struct remain; a target's settings are read without parsing, and no "could not be read" branch remains. The URL is masked wherever it is shown or logged today. Every input the forms refuse today is still refused, with the same message. Tests cover create, edit, view and delivery for each type. The README Target section describes the columns.

Builds after #540 and #541.

Model: opus-5-5

Approved by sneak on https://git.eeqj.de/sneak/webhooker/issues/499#issuecomment-142500. **Change.** `Target.Config`, a JSON string whose shape depends on `Type`, is replaced by columns on `targets`: `url` (http and slack), `headers` (http), `timeout_seconds` (http), `forward_query` (http), `archive_expiry` and `archive_rotation` (database). A column a type does not use stays empty. `headers` holds the header map as JSON text, because it is a list; every other setting is a plain value. `max_attempts` is already a column by then (https://git.eeqj.de/sneak/webhooker/issues/540). **Why.** Every reader of a target parses the JSON first, so each carries a "configuration could not be read" branch: the view, the edit form, the delivery, the archive sweep, the redactor. The forms encode JSON; http and slack spell the URL differently (`url`, `webhookUrl`); the URL, which can carry a credential, lives inside a string that must be hidden as a unit. As columns the struct says what a target can have, validation happens once at the form, the URL is masked as one field, and the "could not be read" branches go. **What it touches.** `internal/db/migrations/main/001_schema.sql` (pre-1.0: edit the file) and `internal/database/model_target.go`; `internal/delivery/target_http.go` (`HTTPTargetConfig`, `parseHTTPConfig`), `target_headers.go`, `target_slack.go` (`SlackTargetConfig`, `parseSlackConfig`), the archive target's settings parsing (`databaseTargetConfig`, `parseArchiveExpiry`, `parseArchiveRotation`, in `internal/archive` by then), `target_config_view.go`, `target_config_edit.go`, `target_redact.go` (`targetSecrets`), and wherever a delivery task copies the config; `internal/handlers/target_create.go` (`buildTargetConfig`, its builders, `marshalTargetConfig`), `target_edit.go` (`configUnreadableMessage`); the templates; the README Target section. **Definition of done.** No JSON config column and no per-type config struct remain; a target's settings are read without parsing, and no "could not be read" branch remains. The URL is masked wherever it is shown or logged today. Every input the forms refuse today is still refused, with the same message. Tests cover create, edit, view and delivery for each type. The README Target section describes the columns. Builds after https://git.eeqj.de/sneak/webhooker/issues/540 and https://git.eeqj.de/sneak/webhooker/issues/541. Model: opus-5-5
clawbot self-assigned this 2026-10-15 11:57:19 +02:00
Author
Collaborator

Plan, for the choices the issue leaves open:

  • archive_expiry holds a time.Duration (an integer), 0 meaning never, so the archive writer, the sweep and the target list read it without parsing. The forms still take the text they take today (never, 720h, ...) and refuse the same inputs with the same messages; the archive package's check of that text now also returns the duration.
  • headers is a map[string]string on the model, stored as JSON text through GORM's own JSON serializer, so no code of ours parses it.
  • archive_rotation is stored as one of none, monthly, daily, hourly for a database target; an empty value from the form is stored as none.
  • The edit form is filled from the target's fields in the handler, so internal/delivery/target_config_edit.go goes, and with it the "could not be read" message on the edit page.
  • A delivery task carries the six settings where it carried the JSON string; it still copies the target as it was when the task was made.

Model: opus-5-5

Plan, for the choices the issue leaves open: - `archive_expiry` holds a `time.Duration` (an integer), 0 meaning never, so the archive writer, the sweep and the target list read it without parsing. The forms still take the text they take today (`never`, `720h`, ...) and refuse the same inputs with the same messages; the archive package's check of that text now also returns the duration. - `headers` is a `map[string]string` on the model, stored as JSON text through GORM's own JSON serializer, so no code of ours parses it. - `archive_rotation` is stored as one of `none`, `monthly`, `daily`, `hourly` for a database target; an empty value from the form is stored as `none`. - The edit form is filled from the target's fields in the handler, so `internal/delivery/target_config_edit.go` goes, and with it the "could not be read" message on the edit page. - A delivery task carries the six settings where it carried the JSON string; it still copies the target as it was when the task was made. Model: opus-5-5
Author
Collaborator

Built as planned above in #554, on next after #542 landed.

Model: opus-5-5

Built as planned above in https://git.eeqj.de/sneak/webhooker/pulls/554, on `next` after https://git.eeqj.de/sneak/webhooker/issues/542 landed. Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/webhooker#543