Added database.change_batch_retention, deleting old change batches

Each save that records a batch also deletes, in the same
transaction, the batches older than database.change_batch_retention
(30 days by default; 0 or less keeps them for ever), with their
records of opportunities. A client still holding a deleted batch
gets 410 Gone from ?changed_after= and reloads the whole list, as
for any unknown batch.
This commit is contained in:
bwilliams committed 2026-10-16 16:10:32 +01:00
1 parent 1988d07a29
commit 65e5bc6b26
10 files changed
+153 -19

No files matched your search

+2
View File
@@ -58,6 +58,7 @@ priority over `--quiet`.
| Key | Type | Default | Notes |
|---|---|---|---|
| `database.file` | string | | Path to the SQLite database file. Empty: commands that need the database fail |
| `database.change_batch_retention` | duration | `720h` | How long a batch of changed opportunities is kept for clients to catch up from (`?changed_after=`); older ones are deleted as new ones are recorded. A client whose batch has gone reloads the whole list. 0 or less: kept for ever |
Database is currently restricted to SQLite v3. At this point, no other engine is
supported.
@@ -219,6 +220,7 @@ logging:
file: ""
database:
file: ./jobsearch.db
change_batch_retention: 720h
sources:
find_a_tender:
enabled: true
+9 -3
View File
@@ -1,15 +1,21 @@
package config
import "reflect"
import (
"reflect"
"time"
)
// JSCKDatabase is the database section's configuration key.
const JSCKDatabase string = "database"
// JSCDatabase configures the SQLite database. File is the path to
// the database file; empty means none, and commands that need the database
// fail with a message saying so.
// fail with a message saying so. ChangeBatchRetention is how long a batch of
// changed opportunities is kept for clients to catch up from (see
// store.WithChangeBatchRetention); 0 or less means they are kept for ever.
type JSCDatabase struct {
File string `cfg:"file"`
File string `cfg:"file"`
ChangeBatchRetention time.Duration `cfg:"change_batch_retention" default:"720h"`
}
// ---------- Section methods (see Section) ----------
+24 -5
View File
@@ -595,18 +595,24 @@ func TestLoadFromViperInvalidValues(t *testing.T) {
func TestDatabaseSection(t *testing.T) {
c := JSCDatabase{
File: "./jobsearch.db",
File: "./jobsearch.db",
ChangeBatchRetention: 48 * time.Hour,
}
if got, want := c.Keys(), []string{"file"}; !reflect.DeepEqual(got, want) {
t.Errorf("Keys = %v; want %v", got, want)
keys := []string{"file", "change_batch_retention"}
if got := c.Keys(); !reflect.DeepEqual(got, keys) {
t.Errorf("Keys = %v; want %v", got, keys)
}
if got, want := c.Map(), map[string]any{
"file": "./jobsearch.db",
"file": "./jobsearch.db",
"change_batch_retention": "48h0m0s",
}; !reflect.DeepEqual(got, want) {
t.Errorf("Map = %v; want %v", got, want)
}
want := map[string]any{"database.file": "./jobsearch.db"}
want := map[string]any{
"database.file": "./jobsearch.db",
"database.change_batch_retention": "48h0m0s",
}
if got := c.Flatten(); !reflect.DeepEqual(got, want) {
t.Errorf("Flatten = %v; want %v", got, want)
}
@@ -636,6 +642,19 @@ func TestDatabaseSection(t *testing.T) {
conf.Database.File != "./other.db" {
t.Errorf("SetValue(database.file) = %v, File = %q", err, conf.Database.File)
}
// 30 days by default.
retention := defaultConfig(t).Database.ChangeBatchRetention
if retention != 720*time.Hour {
t.Errorf("default change_batch_retention = %v; want 720h", retention)
}
err := conf.SetValue("database.change_batch_retention", "0s")
if err != nil || conf.Database.ChangeBatchRetention != 0 {
t.Errorf(
"SetValue(change_batch_retention) = %v, retention = %v",
err, conf.Database.ChangeBatchRetention,
)
}
}
func TestViperValue(t *testing.T) {