package store import ( "database/sql" "log" _ "github.com/lib/pq" ) type PostgresDialect struct{} func NewPostgresStore(connStr string) (*SQLStore, error) { return NewSQLStore("postgres", connStr, &PostgresDialect{}) } func (d *PostgresDialect) DriverName() string { return "postgres" } func (d *PostgresDialect) BoolFalse() string { return "FALSE" } func (d *PostgresDialect) CreateTablesSQL() []string { return []string{ `CREATE TABLE IF NOT EXISTS alerts ( id SERIAL PRIMARY KEY, name TEXT, type TEXT, settings TEXT )`, `CREATE TABLE IF NOT EXISTS sites ( id SERIAL PRIMARY KEY, name TEXT DEFAULT 'New Monitor', url TEXT, type TEXT DEFAULT 'http', token TEXT, interval INTEGER, alert_id INTEGER, check_ssl BOOLEAN DEFAULT FALSE, threshold INTEGER DEFAULT 7, max_retries INTEGER DEFAULT 0, hostname TEXT DEFAULT '', port INTEGER DEFAULT 0, timeout INTEGER DEFAULT 0, method TEXT DEFAULT 'GET', description TEXT DEFAULT '', parent_id INTEGER DEFAULT 0, accepted_codes TEXT DEFAULT '200-299', dns_resolve_type TEXT DEFAULT '', dns_server TEXT DEFAULT '', ignore_tls BOOLEAN DEFAULT FALSE, paused BOOLEAN DEFAULT FALSE )`, `CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, username TEXT NOT NULL, public_key TEXT NOT NULL, role TEXT DEFAULT 'user' )`, `CREATE TABLE IF NOT EXISTS check_history ( id SERIAL PRIMARY KEY, site_id INTEGER NOT NULL, latency_ns BIGINT, is_up BOOLEAN, checked_at TIMESTAMP DEFAULT NOW() )`, `CREATE INDEX IF NOT EXISTS idx_check_history_site ON check_history(site_id, checked_at DESC)`, `CREATE TABLE IF NOT EXISTS nodes ( id TEXT PRIMARY KEY, name TEXT NOT NULL, region TEXT DEFAULT '', last_seen TIMESTAMP DEFAULT NOW(), version TEXT DEFAULT '' )`, `CREATE TABLE IF NOT EXISTS logs ( id SERIAL PRIMARY KEY, message TEXT NOT NULL, created_at TIMESTAMP DEFAULT NOW() )`, `CREATE TABLE IF NOT EXISTS maintenance_windows ( id SERIAL PRIMARY KEY, monitor_id INTEGER DEFAULT 0, title TEXT NOT NULL, description TEXT DEFAULT '', type TEXT DEFAULT 'maintenance', start_time TIMESTAMP NOT NULL, end_time TIMESTAMP, created_by TEXT DEFAULT '', created_at TIMESTAMP DEFAULT NOW() )`, `CREATE TABLE IF NOT EXISTS preferences ( key TEXT PRIMARY KEY, value TEXT NOT NULL )`, `CREATE TABLE IF NOT EXISTS state_changes ( id SERIAL PRIMARY KEY, site_id INTEGER NOT NULL, from_status TEXT NOT NULL, to_status TEXT NOT NULL, error_reason TEXT DEFAULT '', changed_at TIMESTAMP DEFAULT NOW() )`, `CREATE INDEX IF NOT EXISTS idx_state_changes_site ON state_changes(site_id, changed_at DESC)`, `CREATE TABLE IF NOT EXISTS alert_health ( alert_id INTEGER PRIMARY KEY, last_send_at TIMESTAMP, last_send_ok BOOLEAN DEFAULT FALSE, last_error TEXT DEFAULT '', send_count INTEGER DEFAULT 0, fail_count INTEGER DEFAULT 0 )`, } } func (d *PostgresDialect) MigrationsSQL() []string { return []string{ "ALTER TABLE sites ADD COLUMN IF NOT EXISTS hostname TEXT DEFAULT ''", "ALTER TABLE sites ADD COLUMN IF NOT EXISTS port INTEGER DEFAULT 0", "ALTER TABLE sites ADD COLUMN IF NOT EXISTS timeout INTEGER DEFAULT 0", "ALTER TABLE sites ADD COLUMN IF NOT EXISTS method TEXT DEFAULT 'GET'", "ALTER TABLE sites ADD COLUMN IF NOT EXISTS description TEXT DEFAULT ''", "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'", "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 ''", "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", "ALTER TABLE check_history ADD COLUMN IF NOT EXISTS node_id TEXT DEFAULT ''", "ALTER TABLE sites ADD COLUMN IF NOT EXISTS regions TEXT DEFAULT ''", } } func (d *PostgresDialect) UpsertNodeSQL() string { return "INSERT INTO nodes (id, name, region, last_seen, version) VALUES ($1, $2, $3, NOW(), $4) ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, region = EXCLUDED.region, last_seen = NOW(), version = EXCLUDED.version" } func (d *PostgresDialect) UpsertAlertHealthSQL() string { return "INSERT INTO alert_health (alert_id, last_send_at, last_send_ok, last_error, send_count, fail_count) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (alert_id) DO UPDATE SET last_send_at = EXCLUDED.last_send_at, last_send_ok = EXCLUDED.last_send_ok, last_error = EXCLUDED.last_error, send_count = EXCLUDED.send_count, fail_count = EXCLUDED.fail_count" } func (d *PostgresDialect) ResetSequenceOnEmpty(db *sql.DB, table string) {} func (d *PostgresDialect) ImportWipe(tx *sql.Tx) { if _, err := tx.Exec("TRUNCATE TABLE sites RESTART IDENTITY CASCADE"); err != nil { log.Printf("import wipe error: %v", err) } if _, err := tx.Exec("TRUNCATE TABLE alerts RESTART IDENTITY CASCADE"); err != nil { log.Printf("import wipe error: %v", err) } if _, err := tx.Exec("TRUNCATE TABLE users RESTART IDENTITY CASCADE"); err != nil { log.Printf("import wipe error: %v", err) } if _, err := tx.Exec("TRUNCATE TABLE maintenance_windows RESTART IDENTITY CASCADE"); err != nil { log.Printf("import wipe error: %v", err) } } func (d *PostgresDialect) ImportResetSequences(tx *sql.Tx) { if _, err := tx.Exec("SELECT setval('sites_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sites))"); err != nil { log.Printf("sequence reset error: %v", err) } if _, err := tx.Exec("SELECT setval('alerts_id_seq', (SELECT COALESCE(MAX(id), 1) FROM alerts))"); err != nil { log.Printf("sequence reset error: %v", err) } if _, err := tx.Exec("SELECT setval('users_id_seq', (SELECT COALESCE(MAX(id), 1) FROM users))"); err != nil { log.Printf("sequence reset error: %v", err) } if _, err := tx.Exec("SELECT setval('maintenance_windows_id_seq', (SELECT COALESCE(MAX(id), 1) FROM maintenance_windows))"); err != nil { log.Printf("sequence reset error: %v", err) } }