aboutsummaryrefslogtreecommitdiff
path: root/internal/storage
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-01-23 20:54:50 -0800
committerFuwn <[email protected]>2026-01-23 20:54:50 -0800
commit4f8955fb5b9e05a5d0e2f4811c13926a6cbbe4b3 (patch)
tree4d1bcd871bb90e55f1aa11b16bc8763ac692af73 /internal/storage
parentfeat: Add database maintenance with backup/reset modes and triggers (diff)
downloadkaze-4f8955fb5b9e05a5d0e2f4811c13926a6cbbe4b3.tar.xz
kaze-4f8955fb5b9e05a5d0e2f4811c13926a6cbbe4b3.zip
feat: Switch to libsql driver for Turso compatibility
Diffstat (limited to 'internal/storage')
-rw-r--r--internal/storage/sqlite.go123
1 files changed, 63 insertions, 60 deletions
diff --git a/internal/storage/sqlite.go b/internal/storage/sqlite.go
index db0ed52..5fcc9b3 100644
--- a/internal/storage/sqlite.go
+++ b/internal/storage/sqlite.go
@@ -8,7 +8,7 @@ import (
"time"
"github.com/Fuwn/kaze/internal/config"
- _ "modernc.org/sqlite"
+ _ "github.com/tursodatabase/go-libsql"
)
// Storage handles all database operations
@@ -107,22 +107,27 @@ func NewWithMaintenance(dbPath string, historyDays int, maintenanceCfg config.Ma
return s, nil
}
-func openDB(dbPath string) (*sql.DB, error) {
- dsn := fmt.Sprintf("%s?_txlock=immediate&_busy_timeout=5000&_journal_mode=WAL&_synchronous=NORMAL", dbPath)
+func openDB(dbURL string) (*sql.DB, error) {
+ isRemote := strings.HasPrefix(dbURL, "libsql://") || strings.HasPrefix(dbURL, "https://")
- db, err := sql.Open("sqlite", dsn)
+ dsn := dbURL
+ if !isRemote && !strings.HasPrefix(dbURL, "file:") {
+ dsn = "file:" + dbURL
+ }
+
+ if !isRemote {
+ dsn = dsn + "?_txlock=immediate&_busy_timeout=5000&_journal_mode=WAL&_synchronous=NORMAL"
+ }
+
+ db, err := sql.Open("libsql", dsn)
if err != nil {
return nil, fmt.Errorf("failed to open database: %w", err)
}
- db.SetMaxOpenConns(1)
- db.SetMaxIdleConns(1)
- db.SetConnMaxLifetime(0)
-
- var journalMode string
- if err := db.QueryRow("PRAGMA journal_mode").Scan(&journalMode); err != nil {
- db.Close()
- return nil, fmt.Errorf("failed to check journal mode: %w", err)
+ if !isRemote {
+ db.SetMaxOpenConns(1)
+ db.SetMaxIdleConns(1)
+ db.SetConnMaxLifetime(0)
}
if _, err := db.Exec("PRAGMA foreign_keys=ON"); err != nil {
@@ -133,55 +138,53 @@ func openDB(dbPath string) (*sql.DB, error) {
return db, nil
}
-// migrate creates the database schema
func (s *Storage) migrate() error {
- schema := `
- CREATE TABLE IF NOT EXISTS check_results (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- monitor_name TEXT NOT NULL,
- timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
- status TEXT NOT NULL CHECK(status IN ('up', 'down', 'degraded')),
- response_time_ms INTEGER NOT NULL DEFAULT 0,
- status_code INTEGER DEFAULT 0,
- error_message TEXT,
- ssl_expiry DATETIME,
- ssl_days_left INTEGER DEFAULT 0
- );
-
- CREATE INDEX IF NOT EXISTS idx_check_results_monitor_time
- ON check_results(monitor_name, timestamp DESC);
-
- CREATE INDEX IF NOT EXISTS idx_check_results_timestamp
- ON check_results(timestamp);
-
- CREATE TABLE IF NOT EXISTS daily_stats (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- monitor_name TEXT NOT NULL,
- date DATE NOT NULL,
- success_count INTEGER NOT NULL DEFAULT 0,
- failure_count INTEGER NOT NULL DEFAULT 0,
- total_checks INTEGER NOT NULL DEFAULT 0,
- avg_response_ms REAL DEFAULT 0,
- uptime_percent REAL DEFAULT 0,
- UNIQUE(monitor_name, date)
- );
-
- CREATE INDEX IF NOT EXISTS idx_daily_stats_monitor_date
- ON daily_stats(monitor_name, date DESC);
-
- CREATE TABLE IF NOT EXISTS monitor_state (
- monitor_name TEXT PRIMARY KEY,
- current_status TEXT NOT NULL DEFAULT 'unknown',
- last_check DATETIME,
- last_response_time_ms INTEGER DEFAULT 0,
- last_error TEXT,
- ssl_expiry DATETIME,
- ssl_days_left INTEGER DEFAULT 0
- );
- `
-
- _, err := s.db.Exec(schema)
- return err
+ statements := []string{
+ `CREATE TABLE IF NOT EXISTS check_results (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ monitor_name TEXT NOT NULL,
+ timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ status TEXT NOT NULL CHECK(status IN ('up', 'down', 'degraded')),
+ response_time_ms INTEGER NOT NULL DEFAULT 0,
+ status_code INTEGER DEFAULT 0,
+ error_message TEXT,
+ ssl_expiry DATETIME,
+ ssl_days_left INTEGER DEFAULT 0
+ )`,
+ `CREATE INDEX IF NOT EXISTS idx_check_results_monitor_time
+ ON check_results(monitor_name, timestamp DESC)`,
+ `CREATE INDEX IF NOT EXISTS idx_check_results_timestamp
+ ON check_results(timestamp)`,
+ `CREATE TABLE IF NOT EXISTS daily_stats (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ monitor_name TEXT NOT NULL,
+ date DATE NOT NULL,
+ success_count INTEGER NOT NULL DEFAULT 0,
+ failure_count INTEGER NOT NULL DEFAULT 0,
+ total_checks INTEGER NOT NULL DEFAULT 0,
+ avg_response_ms REAL DEFAULT 0,
+ uptime_percent REAL DEFAULT 0,
+ UNIQUE(monitor_name, date)
+ )`,
+ `CREATE INDEX IF NOT EXISTS idx_daily_stats_monitor_date
+ ON daily_stats(monitor_name, date DESC)`,
+ `CREATE TABLE IF NOT EXISTS monitor_state (
+ monitor_name TEXT PRIMARY KEY,
+ current_status TEXT NOT NULL DEFAULT 'unknown',
+ last_check DATETIME,
+ last_response_time_ms INTEGER DEFAULT 0,
+ last_error TEXT,
+ ssl_expiry DATETIME,
+ ssl_days_left INTEGER DEFAULT 0
+ )`,
+ }
+
+ for _, stmt := range statements {
+ if _, err := s.db.Exec(stmt); err != nil {
+ return fmt.Errorf("migration failed: %w", err)
+ }
+ }
+ return nil
}
// SaveCheckResult saves a check result and updates monitor state