Allow retention_days of 0 to mean retain forever (closes #79)
All checks were successful
check / check (push) Successful in 3m4s
All checks were successful
check / check (push) Successful in 3m4s
RetentionDays carried gorm:"default:30", so GORM substituted 30 for a zero value while building the insert. A webhook could therefore never be configured to keep its events indefinitely: the reaper's retain-forever branch existed but was unreachable from the normal create and edit flows. Introduce database.RetentionForeverDays = 365 * 1000 as the sentinel for "retain forever" and a Webhook.BeforeSave hook that rewrites any non-positive RetentionDays to it. The rewrite has to live in the hook rather than at the call sites: GORM applies the column default while converting the model to insert values, which happens after BeforeSave, so anything later loses that race. Putting it on the model also means a future call site, such as the planned REST API, cannot bypass it. The reaper now skips a webhook when Webhook.RetainsForever reports true, which recognises the sentinel and keeps honouring the old <= 0 values for rows written before it existed. Without this the sentinel, being positive, would have produced a cutoff a thousand years in the past and a DELETE matching nothing on every sweep. Form handling is shared by create and edit through parseRetentionDays so the two cannot drift: an empty field keeps the previous behaviour (default on create, unchanged on edit), 0 is honoured, and an unparseable or negative value is a 400 that re-renders the form rather than a silently substituted default. The retention inputs drop max="365". That cap was not cosmetic: the edit form pre-fills the stored value, so a retain-forever webhook rendered 365000 into an input capped at 365 and browser validation would have blocked saving any edit to it. min becomes 0 with a hint explaining what 0 does, and the list and detail views render a RetentionLabel of "forever" instead of a raw day count. The 30-day default is consolidated into database.DefaultRetentionDays, referenced from the handler and from the create form's pre-filled value, with a test asserting it agrees with the struct tag that cannot reference it.
This commit is contained in:
11
README.md
11
README.md
@@ -307,13 +307,20 @@ event routing.
|
||||
| `user_id` | UUID | Foreign key → User |
|
||||
| `name` | string | Human-readable name |
|
||||
| `description` | string | Optional description |
|
||||
| `retention_days` | integer | Days to retain events (default: 30) |
|
||||
| `retention_days` | integer | Days to retain events (default: 30; 0 means retain forever) |
|
||||
|
||||
**Relations:** Belongs to User. Has many Entrypoints. Has many Targets.
|
||||
|
||||
The `retention_days` field controls how long event data is kept in the
|
||||
webhook's dedicated database before automatic cleanup.
|
||||
|
||||
Setting `retention_days` to `0` means "retain events forever". Because
|
||||
the column carries a default of 30, a literal zero cannot survive an
|
||||
insert, so a zero is rewritten on save to a sentinel of `365 * 1000`
|
||||
days (`database.RetentionForeverDays`). The retention reaper recognises
|
||||
that sentinel and skips the webhook entirely, and the web UI displays
|
||||
such a webhook's retention as "forever" rather than as a day count.
|
||||
|
||||
#### Entrypoint
|
||||
|
||||
A receiver URL where external services POST webhook events. Each
|
||||
@@ -509,7 +516,7 @@ This separation provides:
|
||||
DB; the event database file is hard-deleted (permanently removed).
|
||||
- **Per-webhook retention** — the `retention_days` field on each webhook
|
||||
controls automatic cleanup of old events in that webhook's database
|
||||
only.
|
||||
only, or disables cleanup entirely when set to `0` (retain forever).
|
||||
- **Performance** — each webhook's database has its own WAL, its own
|
||||
page cache, and its own lock, so concurrent event ingestion across
|
||||
webhooks won't contend.
|
||||
|
||||
Reference in New Issue
Block a user