refactor: architecture foundations (status type, schema versioning, shared mock) #107
@@ -5,10 +5,16 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Migration struct {
|
||||||
|
Version int
|
||||||
|
SQL string
|
||||||
|
}
|
||||||
|
|
||||||
type Dialect interface {
|
type Dialect interface {
|
||||||
DriverName() string
|
DriverName() string
|
||||||
CreateTablesSQL() []string
|
CreateTablesSQL() []string
|
||||||
MigrationsSQL() []string
|
Migrations() []Migration
|
||||||
|
BaselineVersion() int
|
||||||
BoolFalse() string
|
BoolFalse() string
|
||||||
ResetSequenceOnEmpty(db *sql.DB, table string)
|
ResetSequenceOnEmpty(db *sql.DB, table string)
|
||||||
ImportWipe(tx *sql.Tx)
|
ImportWipe(tx *sql.Tx)
|
||||||
|
|||||||
+30
-27
@@ -13,8 +13,9 @@ func NewPostgresStore(connStr string) (*SQLStore, error) {
|
|||||||
return NewSQLStore("postgres", connStr, &PostgresDialect{})
|
return NewSQLStore("postgres", connStr, &PostgresDialect{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *PostgresDialect) DriverName() string { return "postgres" }
|
func (d *PostgresDialect) DriverName() string { return "postgres" }
|
||||||
func (d *PostgresDialect) BoolFalse() string { return "FALSE" }
|
func (d *PostgresDialect) BoolFalse() string { return "FALSE" }
|
||||||
|
func (d *PostgresDialect) BaselineVersion() int { return 21 }
|
||||||
|
|
||||||
func (d *PostgresDialect) CreateTablesSQL() []string {
|
func (d *PostgresDialect) CreateTablesSQL() []string {
|
||||||
return []string{
|
return []string{
|
||||||
@@ -32,7 +33,8 @@ func (d *PostgresDialect) CreateTablesSQL() []string {
|
|||||||
method TEXT DEFAULT 'GET', description TEXT DEFAULT '',
|
method TEXT DEFAULT 'GET', description TEXT DEFAULT '',
|
||||||
parent_id INTEGER DEFAULT 0, accepted_codes TEXT DEFAULT '200-299',
|
parent_id INTEGER DEFAULT 0, accepted_codes TEXT DEFAULT '200-299',
|
||||||
dns_resolve_type TEXT DEFAULT '', dns_server TEXT DEFAULT '',
|
dns_resolve_type TEXT DEFAULT '', dns_server TEXT DEFAULT '',
|
||||||
ignore_tls BOOLEAN DEFAULT FALSE, paused BOOLEAN DEFAULT FALSE
|
ignore_tls BOOLEAN DEFAULT FALSE, paused BOOLEAN DEFAULT FALSE,
|
||||||
|
regions TEXT DEFAULT ''
|
||||||
)`,
|
)`,
|
||||||
`CREATE TABLE IF NOT EXISTS users (
|
`CREATE TABLE IF NOT EXISTS users (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
@@ -42,7 +44,8 @@ func (d *PostgresDialect) CreateTablesSQL() []string {
|
|||||||
`CREATE TABLE IF NOT EXISTS check_history (
|
`CREATE TABLE IF NOT EXISTS check_history (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
site_id INTEGER NOT NULL, latency_ns BIGINT,
|
site_id INTEGER NOT NULL, latency_ns BIGINT,
|
||||||
is_up BOOLEAN, checked_at TIMESTAMPTZ DEFAULT NOW()
|
is_up BOOLEAN, checked_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
node_id TEXT DEFAULT ''
|
||||||
)`,
|
)`,
|
||||||
`CREATE INDEX IF NOT EXISTS idx_check_history_site ON check_history(site_id, checked_at DESC)`,
|
`CREATE INDEX IF NOT EXISTS idx_check_history_site ON check_history(site_id, checked_at DESC)`,
|
||||||
`CREATE TABLE IF NOT EXISTS nodes (
|
`CREATE TABLE IF NOT EXISTS nodes (
|
||||||
@@ -92,29 +95,29 @@ func (d *PostgresDialect) CreateTablesSQL() []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *PostgresDialect) MigrationsSQL() []string {
|
func (d *PostgresDialect) Migrations() []Migration {
|
||||||
return []string{
|
return []Migration{
|
||||||
"ALTER TABLE sites ADD COLUMN IF NOT EXISTS hostname TEXT DEFAULT ''",
|
{1, "ALTER TABLE sites ADD COLUMN IF NOT EXISTS hostname TEXT DEFAULT ''"},
|
||||||
"ALTER TABLE sites ADD COLUMN IF NOT EXISTS port INTEGER DEFAULT 0",
|
{2, "ALTER TABLE sites ADD COLUMN IF NOT EXISTS port INTEGER DEFAULT 0"},
|
||||||
"ALTER TABLE sites ADD COLUMN IF NOT EXISTS timeout INTEGER DEFAULT 0",
|
{3, "ALTER TABLE sites ADD COLUMN IF NOT EXISTS timeout INTEGER DEFAULT 0"},
|
||||||
"ALTER TABLE sites ADD COLUMN IF NOT EXISTS method TEXT DEFAULT 'GET'",
|
{4, "ALTER TABLE sites ADD COLUMN IF NOT EXISTS method TEXT DEFAULT 'GET'"},
|
||||||
"ALTER TABLE sites ADD COLUMN IF NOT EXISTS description TEXT DEFAULT ''",
|
{5, "ALTER TABLE sites ADD COLUMN IF NOT EXISTS description TEXT DEFAULT ''"},
|
||||||
"ALTER TABLE sites ADD COLUMN IF NOT EXISTS parent_id INTEGER DEFAULT 0",
|
{6, "ALTER TABLE sites ADD COLUMN IF NOT EXISTS parent_id INTEGER DEFAULT 0"},
|
||||||
"ALTER TABLE sites ADD COLUMN IF NOT EXISTS accepted_codes TEXT DEFAULT '200-299'",
|
{7, "ALTER TABLE sites ADD COLUMN IF NOT EXISTS accepted_codes TEXT DEFAULT '200-299'"},
|
||||||
"ALTER TABLE sites ADD COLUMN IF NOT EXISTS dns_resolve_type TEXT DEFAULT ''",
|
{8, "ALTER TABLE sites ADD COLUMN IF NOT EXISTS dns_resolve_type TEXT DEFAULT ''"},
|
||||||
"ALTER TABLE sites ADD COLUMN IF NOT EXISTS dns_server TEXT DEFAULT ''",
|
{9, "ALTER TABLE sites ADD COLUMN IF NOT EXISTS dns_server TEXT DEFAULT ''"},
|
||||||
"ALTER TABLE sites ADD COLUMN IF NOT EXISTS ignore_tls BOOLEAN DEFAULT FALSE",
|
{10, "ALTER TABLE sites ADD COLUMN IF NOT EXISTS ignore_tls BOOLEAN DEFAULT FALSE"},
|
||||||
"ALTER TABLE sites ADD COLUMN IF NOT EXISTS paused BOOLEAN DEFAULT FALSE",
|
{11, "ALTER TABLE sites ADD COLUMN IF NOT EXISTS paused BOOLEAN DEFAULT FALSE"},
|
||||||
"ALTER TABLE check_history ADD COLUMN IF NOT EXISTS node_id TEXT DEFAULT ''",
|
{12, "ALTER TABLE check_history ADD COLUMN IF NOT EXISTS node_id TEXT DEFAULT ''"},
|
||||||
"ALTER TABLE sites ADD COLUMN IF NOT EXISTS regions TEXT DEFAULT ''",
|
{13, "ALTER TABLE sites ADD COLUMN IF NOT EXISTS regions TEXT DEFAULT ''"},
|
||||||
"ALTER TABLE check_history ALTER COLUMN checked_at TYPE TIMESTAMPTZ USING checked_at AT TIME ZONE 'UTC'",
|
{14, "ALTER TABLE check_history ALTER COLUMN checked_at TYPE TIMESTAMPTZ USING checked_at AT TIME ZONE 'UTC'"},
|
||||||
"ALTER TABLE nodes ALTER COLUMN last_seen TYPE TIMESTAMPTZ USING last_seen AT TIME ZONE 'UTC'",
|
{15, "ALTER TABLE nodes ALTER COLUMN last_seen TYPE TIMESTAMPTZ USING last_seen AT TIME ZONE 'UTC'"},
|
||||||
"ALTER TABLE logs ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at AT TIME ZONE 'UTC'",
|
{16, "ALTER TABLE logs ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at AT TIME ZONE 'UTC'"},
|
||||||
"ALTER TABLE maintenance_windows ALTER COLUMN start_time TYPE TIMESTAMPTZ USING start_time AT TIME ZONE 'UTC'",
|
{17, "ALTER TABLE maintenance_windows ALTER COLUMN start_time TYPE TIMESTAMPTZ USING start_time AT TIME ZONE 'UTC'"},
|
||||||
"ALTER TABLE maintenance_windows ALTER COLUMN end_time TYPE TIMESTAMPTZ USING end_time AT TIME ZONE 'UTC'",
|
{18, "ALTER TABLE maintenance_windows ALTER COLUMN end_time TYPE TIMESTAMPTZ USING end_time AT TIME ZONE 'UTC'"},
|
||||||
"ALTER TABLE maintenance_windows ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at AT TIME ZONE 'UTC'",
|
{19, "ALTER TABLE maintenance_windows ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at AT TIME ZONE 'UTC'"},
|
||||||
"ALTER TABLE state_changes ALTER COLUMN changed_at TYPE TIMESTAMPTZ USING changed_at AT TIME ZONE 'UTC'",
|
{20, "ALTER TABLE state_changes ALTER COLUMN changed_at TYPE TIMESTAMPTZ USING changed_at AT TIME ZONE 'UTC'"},
|
||||||
"ALTER TABLE alert_health ALTER COLUMN last_send_at TYPE TIMESTAMPTZ USING last_send_at AT TIME ZONE 'UTC'",
|
{21, "ALTER TABLE alert_health ALTER COLUMN last_send_at TYPE TIMESTAMPTZ USING last_send_at AT TIME ZONE 'UTC'"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+22
-19
@@ -28,8 +28,9 @@ func NewSQLiteStore(path string) (*SQLStore, error) {
|
|||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SQLiteDialect) DriverName() string { return "sqlite" }
|
func (d *SQLiteDialect) DriverName() string { return "sqlite" }
|
||||||
func (d *SQLiteDialect) BoolFalse() string { return "0" }
|
func (d *SQLiteDialect) BoolFalse() string { return "0" }
|
||||||
|
func (d *SQLiteDialect) BaselineVersion() int { return 13 }
|
||||||
|
|
||||||
func (d *SQLiteDialect) CreateTablesSQL() []string {
|
func (d *SQLiteDialect) CreateTablesSQL() []string {
|
||||||
return []string{
|
return []string{
|
||||||
@@ -47,7 +48,8 @@ func (d *SQLiteDialect) CreateTablesSQL() []string {
|
|||||||
method TEXT DEFAULT 'GET', description TEXT DEFAULT '',
|
method TEXT DEFAULT 'GET', description TEXT DEFAULT '',
|
||||||
parent_id INTEGER DEFAULT 0, accepted_codes TEXT DEFAULT '200-299',
|
parent_id INTEGER DEFAULT 0, accepted_codes TEXT DEFAULT '200-299',
|
||||||
dns_resolve_type TEXT DEFAULT '', dns_server TEXT DEFAULT '',
|
dns_resolve_type TEXT DEFAULT '', dns_server TEXT DEFAULT '',
|
||||||
ignore_tls BOOLEAN DEFAULT 0, paused BOOLEAN DEFAULT 0
|
ignore_tls BOOLEAN DEFAULT 0, paused BOOLEAN DEFAULT 0,
|
||||||
|
regions TEXT DEFAULT ''
|
||||||
)`,
|
)`,
|
||||||
`CREATE TABLE IF NOT EXISTS users (
|
`CREATE TABLE IF NOT EXISTS users (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@@ -57,7 +59,8 @@ func (d *SQLiteDialect) CreateTablesSQL() []string {
|
|||||||
`CREATE TABLE IF NOT EXISTS check_history (
|
`CREATE TABLE IF NOT EXISTS check_history (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
site_id INTEGER NOT NULL, latency_ns INTEGER,
|
site_id INTEGER NOT NULL, latency_ns INTEGER,
|
||||||
is_up BOOLEAN, checked_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
is_up BOOLEAN, checked_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
node_id TEXT DEFAULT ''
|
||||||
)`,
|
)`,
|
||||||
`CREATE INDEX IF NOT EXISTS idx_check_history_site ON check_history(site_id, checked_at DESC)`,
|
`CREATE INDEX IF NOT EXISTS idx_check_history_site ON check_history(site_id, checked_at DESC)`,
|
||||||
`CREATE TABLE IF NOT EXISTS nodes (
|
`CREATE TABLE IF NOT EXISTS nodes (
|
||||||
@@ -107,21 +110,21 @@ func (d *SQLiteDialect) CreateTablesSQL() []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SQLiteDialect) MigrationsSQL() []string {
|
func (d *SQLiteDialect) Migrations() []Migration {
|
||||||
return []string{
|
return []Migration{
|
||||||
"ALTER TABLE sites ADD COLUMN hostname TEXT DEFAULT ''",
|
{1, "ALTER TABLE sites ADD COLUMN hostname TEXT DEFAULT ''"},
|
||||||
"ALTER TABLE sites ADD COLUMN port INTEGER DEFAULT 0",
|
{2, "ALTER TABLE sites ADD COLUMN port INTEGER DEFAULT 0"},
|
||||||
"ALTER TABLE sites ADD COLUMN timeout INTEGER DEFAULT 0",
|
{3, "ALTER TABLE sites ADD COLUMN timeout INTEGER DEFAULT 0"},
|
||||||
"ALTER TABLE sites ADD COLUMN method TEXT DEFAULT 'GET'",
|
{4, "ALTER TABLE sites ADD COLUMN method TEXT DEFAULT 'GET'"},
|
||||||
"ALTER TABLE sites ADD COLUMN description TEXT DEFAULT ''",
|
{5, "ALTER TABLE sites ADD COLUMN description TEXT DEFAULT ''"},
|
||||||
"ALTER TABLE sites ADD COLUMN parent_id INTEGER DEFAULT 0",
|
{6, "ALTER TABLE sites ADD COLUMN parent_id INTEGER DEFAULT 0"},
|
||||||
"ALTER TABLE sites ADD COLUMN accepted_codes TEXT DEFAULT '200-299'",
|
{7, "ALTER TABLE sites ADD COLUMN accepted_codes TEXT DEFAULT '200-299'"},
|
||||||
"ALTER TABLE sites ADD COLUMN dns_resolve_type TEXT DEFAULT ''",
|
{8, "ALTER TABLE sites ADD COLUMN dns_resolve_type TEXT DEFAULT ''"},
|
||||||
"ALTER TABLE sites ADD COLUMN dns_server TEXT DEFAULT ''",
|
{9, "ALTER TABLE sites ADD COLUMN dns_server TEXT DEFAULT ''"},
|
||||||
"ALTER TABLE sites ADD COLUMN ignore_tls BOOLEAN DEFAULT 0",
|
{10, "ALTER TABLE sites ADD COLUMN ignore_tls BOOLEAN DEFAULT 0"},
|
||||||
"ALTER TABLE sites ADD COLUMN paused BOOLEAN DEFAULT 0",
|
{11, "ALTER TABLE sites ADD COLUMN paused BOOLEAN DEFAULT 0"},
|
||||||
"ALTER TABLE check_history ADD COLUMN node_id TEXT DEFAULT ''",
|
{12, "ALTER TABLE check_history ADD COLUMN node_id TEXT DEFAULT ''"},
|
||||||
"ALTER TABLE sites ADD COLUMN regions TEXT DEFAULT ''",
|
{13, "ALTER TABLE sites ADD COLUMN regions TEXT DEFAULT ''"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+32
-10
@@ -7,7 +7,6 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.lerkolabs.com/lerkolabs/uptop/internal/models"
|
"gitea.lerkolabs.com/lerkolabs/uptop/internal/models"
|
||||||
@@ -80,13 +79,34 @@ func (s *SQLStore) Init(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, m := range s.dialect.MigrationsSQL() {
|
|
||||||
if _, err := s.db.ExecContext(ctx, m); err != nil {
|
if _, err := s.db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS schema_version (
|
||||||
errMsg := err.Error()
|
version INTEGER PRIMARY KEY,
|
||||||
if strings.Contains(errMsg, "already exists") || strings.Contains(errMsg, "duplicate column") {
|
applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
continue
|
)`); err != nil {
|
||||||
}
|
return fmt.Errorf("create schema_version: %w", err)
|
||||||
return fmt.Errorf("migration failed: %w", err)
|
}
|
||||||
|
|
||||||
|
var current int
|
||||||
|
_ = s.db.QueryRowContext(ctx, "SELECT COALESCE(MAX(version), 0) FROM schema_version").Scan(¤t) //nolint:errcheck
|
||||||
|
|
||||||
|
if current == 0 {
|
||||||
|
baseline := s.dialect.BaselineVersion()
|
||||||
|
if _, err := s.db.ExecContext(ctx, s.q("INSERT INTO schema_version (version) VALUES (?)"), baseline); err != nil {
|
||||||
|
return fmt.Errorf("seed baseline version: %w", err)
|
||||||
|
}
|
||||||
|
current = baseline
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range s.dialect.Migrations() {
|
||||||
|
if m.Version <= current {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := s.db.ExecContext(ctx, m.SQL); err != nil {
|
||||||
|
return fmt.Errorf("migration %d failed: %w", m.Version, err)
|
||||||
|
}
|
||||||
|
if _, err := s.db.ExecContext(ctx, s.q("INSERT INTO schema_version (version) VALUES (?)"), m.Version); err != nil {
|
||||||
|
return fmt.Errorf("record migration %d: %w", m.Version, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -325,8 +345,10 @@ func (s *SQLStore) UpdateAlert(ctx context.Context, id int, name, aType string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *SQLStore) DeleteAlert(ctx context.Context, id int) error {
|
func (s *SQLStore) DeleteAlert(ctx context.Context, id int) error {
|
||||||
_, err := s.db.ExecContext(ctx, s.q("DELETE FROM alerts WHERE id=?"), id)
|
if _, err := s.db.ExecContext(ctx, s.q("UPDATE sites SET alert_id = 0 WHERE alert_id = ?"), id); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
|
}
|
||||||
|
if _, err := s.db.ExecContext(ctx, s.q("DELETE FROM alerts WHERE id=?"), id); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.dialect.ResetSequenceOnEmpty(s.db, "alerts")
|
s.dialect.ResetSequenceOnEmpty(s.db, "alerts")
|
||||||
|
|||||||
Reference in New Issue
Block a user