storage: add speedtests table + Save/List/Delete + retention
Adds a Store-method API for the RecordSpeedTest RPC (Task 8) and the
retention sweep (Task 14):
- SaveSpeedTest / ListRecentSpeedTests / DeleteSpeedTestsBefore
- SpeedTest struct (no json tags; that decision deferred to Task 10)
Schema lives in migrations/002_speedtests.sql to match the existing
embed.FS pattern (rather than inline in Go). The migrate() loop now
iterates every *.sql in lexical order, so future migrations drop in
without code changes. Both files use CREATE ... IF NOT EXISTS so the
existing 001 migration remains idempotent on already-initialised DBs.
CHECK constraints enforce node IN ('fl','pl1') and non-negative metric
values at the DB level. country has no DB CHECK — validation belongs
to the gRPC handler in Task 8.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"database/sql"
|
||||
"embed"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/bergabruh/stats-gateway/internal/probes"
|
||||
@@ -42,13 +43,32 @@ func Open(path string) (*Store, error) {
|
||||
// Close closes the underlying database.
|
||||
func (s *Store) Close() error { return s.db.Close() }
|
||||
|
||||
// migrate applies every SQL file under migrations/ in lexical filename order.
|
||||
// Each file is executed wholesale (statements are CREATE ... IF NOT EXISTS,
|
||||
// so re-applying on an existing DB is a no-op).
|
||||
func (s *Store) migrate() error {
|
||||
body, err := migrations.ReadFile("migrations/001_init.sql")
|
||||
entries, err := migrations.ReadDir("migrations")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.db.Exec(string(body))
|
||||
return err
|
||||
names := make([]string, 0, len(entries))
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
continue
|
||||
}
|
||||
names = append(names, e.Name())
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
body, err := migrations.ReadFile("migrations/" + name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read migration %s: %w", name, err)
|
||||
}
|
||||
if _, err := s.db.Exec(string(body)); err != nil {
|
||||
return fmt.Errorf("apply migration %s: %w", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertSample stores a probe result for the given node.
|
||||
|
||||
Reference in New Issue
Block a user