Store a target's settings as columns (closes #543)
check / check (push) Canceled after 0s

A target's settings move from the per-type JSON string in
targets.config to columns: url, headers, timeout_seconds,
forward_query, archive_expiry and archive_rotation. A column a type
does not use stays empty. headers holds the header map as JSON text
through GORM's JSON serializer, and archive_expiry holds a
time.Duration, 0 meaning never, so nothing that reads a target parses
it. The forms check each setting once, with the same refusals and
messages, and the edit form is filled from the columns, so the
per-type config structs, their parsers and the "could not be read"
branches are gone. A delivery task carries the six settings in place
of the JSON string.

Model: opus-5-5
This commit is contained in:
2026-10-15 19:54:42 +00:00
parent 0cdda43e6c
commit 82bab57dfe
69 changed files with 1404 additions and 2159 deletions
+20 -33
View File
@@ -14,16 +14,12 @@ import (
"sneak.berlin/go/webhooker/internal/database/databasetest"
)
// The archive rotations, and the configs of a daily target and of one
// whose rotation is not one of the four.
// The archive rotations.
const (
rotationNone = "none"
rotationMonthly = "monthly"
rotationDaily = "daily"
rotationHourly = "hourly"
dailyConfig = `{"rotation":"daily"}`
weeklyConfig = `{"rotation":"weekly"}`
)
// The periods the tests archive into most: two days, and an hour of
@@ -108,7 +104,7 @@ func TestWrite_RotatesAtEachPeriodBoundary(t *testing.T) {
t.Parallel()
env := setupArchiveTest(t)
tgt := env.seedDatabaseTarget(t, `{"rotation":"`+tc.rotation+`"}`)
tgt := env.seedDatabaseTarget(t, 0, tc.rotation)
path := env.archivePath(tgt)
first := env.deliverReceivedAt(t, tgt, tc.before)
@@ -127,7 +123,7 @@ func TestWrite_RotatesAtEachPeriodBoundary(t *testing.T) {
t.Parallel()
env := setupArchiveTest(t)
tgt := env.seedDatabaseTarget(t, `{"rotation":"`+rotationNone+`"}`)
tgt := env.seedDatabaseTarget(t, 0, rotationNone)
first := env.deliverReceivedAt(t, tgt, cases[0].before)
second := env.deliverReceivedAt(t, tgt, cases[0].after)
@@ -144,7 +140,7 @@ func TestWrite_EventWithoutReceiveTime(t *testing.T) {
t.Parallel()
env := setupArchiveTest(t)
tgt := env.seedDatabaseTarget(t, `{"rotation":"`+rotationMonthly+`"}`)
tgt := env.seedDatabaseTarget(t, 0, rotationMonthly)
path := env.archivePath(tgt)
before := time.Now().UTC().Format("2006-01")
@@ -169,13 +165,13 @@ func TestWrite_RotationChangeKeepsOldFiles(t *testing.T) {
t.Parallel()
env := setupArchiveTest(t)
tgt := env.seedDatabaseTarget(t, "")
tgt := env.seedDatabaseTarget(t, 0, rotationNone)
path := env.archivePath(tgt)
at := time.Date(2026, 3, 4, 12, 0, 0, 0, time.UTC)
first := env.deliverReceivedAt(t, tgt, at)
tgt.Config = dailyConfig
tgt.ArchiveRotation = rotationDaily
second := env.deliverReceivedAt(t, tgt, at)
assert.Equal(t, []string{first}, archivedEventIDs(t, path))
@@ -193,9 +189,7 @@ func TestArchiveSweep_PrunesEveryFile(t *testing.T) {
t.Parallel()
env := setupArchiveTest(t)
tgt := env.seedDatabaseTarget(
t, `{"expiry":"1h","rotation":"`+rotationDaily+`"}`,
)
tgt := env.seedDatabaseTarget(t, time.Hour, rotationDaily)
path := env.archivePath(tgt)
emptied := periodPath(path, dayPeriod)
kept := periodPath(path, nextDayPeriod)
@@ -225,13 +219,13 @@ func TestRename_MovesEveryFile(t *testing.T) {
t.Parallel()
env := setupArchiveTest(t)
tgt := env.seedDatabaseTarget(t, "")
tgt := env.seedDatabaseTarget(t, 0, rotationNone)
oldPath := env.archivePath(tgt)
day := time.Date(2026, 3, 4, 12, 0, 0, 0, time.UTC)
unrotated := env.deliverReceivedAt(t, tgt, day)
tgt.Config = dailyConfig
tgt.ArchiveRotation = rotationDaily
first := env.deliverReceivedAt(t, tgt, day)
second := env.deliverReceivedAt(t, tgt, day.Add(24*time.Hour))
@@ -267,7 +261,7 @@ func TestRename_NeverReplacesARotatedFile(t *testing.T) {
t.Parallel()
env := setupArchiveTest(t)
tgt := env.seedDatabaseTarget(t, dailyConfig)
tgt := env.seedDatabaseTarget(t, 0, rotationDaily)
oldPath := env.archivePath(tgt)
day := time.Date(2026, 3, 4, 12, 0, 0, 0, time.UTC)
@@ -341,34 +335,27 @@ func TestArchivePathAt(t *testing.T) {
at := time.Date(2026, 10, 1, 19, 30, 0, 0, time.UTC)
cases := map[string]string{
"": "",
`{"rotation":"` + rotationNone + `"}`: "",
`{"rotation":"` + rotationMonthly + `"}`: "-2026-10",
dailyConfig: "-2026-10-01",
`{"rotation":"` + rotationHourly + `"}`: "-2026-10-01-19",
"": "",
rotationNone: "",
rotationMonthly: "-2026-10",
rotationDaily: "-2026-10-01",
rotationHourly: "-2026-10-01-19",
}
for config, period := range cases {
for rotation, period := range cases {
target := &database.Target{
BaseModel: database.BaseModel{ID: "tgt-id"},
Name: "Archive",
Config: config,
BaseModel: database.BaseModel{ID: "tgt-id"},
Name: "Archive",
ArchiveRotation: rotation,
}
got, err := archive.PathAt(dbMgr, webhook, target, at)
require.NoError(t, err, config)
assert.Equal(t,
filepath.Join(
dataDir, "archive-orders-archive-tgt-id"+period+".db",
),
got, config,
archive.PathAt(dbMgr, webhook, target, at), rotation,
)
}
_, err := archive.PathAt(dbMgr, webhook, &database.Target{
Config: weeklyConfig,
}, at)
require.Error(t, err)
}
// TestValidateArchiveRotation accepts the four rotations, and empty,