diff --git a/internal/pkg/storage/sqlite.go b/internal/pkg/storage/sqlite.go index e69de29..a0502d8 100755 --- a/internal/pkg/storage/sqlite.go +++ b/internal/pkg/storage/sqlite.go @@ -0,0 +1,169 @@ +package storage + +import ( + "database/sql" + "fmt" +) + +// --------------------------- +// SQLite Strategy +// --------------------------- + +type SQLiteStrategy struct{} + +func (s *SQLiteStrategy) DriverName() string { return "sqlite" } + +// Placeholder returns ?. +func (s *SQLiteStrategy) Placeholder(n int) string { return "?" } + +// GetTimeNow returns the SQLite function for current timestamp. +func (s *SQLiteStrategy) GetTimeNow() string { return "CURRENT_TIMESTAMP" } + +// GetTrueValue returns the integer representation. +func (s *SQLiteStrategy) GetTrueValue() string { return "1" } + +// GetFalseValue returns the integer representation. +func (s *SQLiteStrategy) GetFalseValue() string { return "0" } + +func (s *SQLiteStrategy) InitSchemaSQL() string { + return ` + CREATE TABLE IF NOT EXISTS clusters ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT UNIQUE NOT NULL, + data TEXT NOT NULL, + enabled BOOLEAN DEFAULT 1, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + CREATE TABLE IF NOT EXISTS listeners ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT UNIQUE NOT NULL, + data TEXT NOT NULL, + enabled BOOLEAN DEFAULT 1, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + CREATE TABLE IF NOT EXISTS secrets ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT UNIQUE NOT NULL, + data TEXT NOT NULL, + enabled BOOLEAN DEFAULT 1, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + domain TEXT NULL + ); + CREATE TABLE IF NOT EXISTS certificates ( + domain TEXT PRIMARY KEY, + email TEXT NOT NULL, + cert_pem BLOB NOT NULL, + key_pem BLOB NOT NULL, + full_chain_pem BLOB DEFAULT '', -- Added: full_chain_pem with a default empty blob for backward compatibility + account_key BLOB NOT NULL, + account_url TEXT NOT NULL, + issuer_type TEXT DEFAULT '', + secret_name TEXT DEFAULT '', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + enable_rotation BOOLEAN DEFAULT 0, + renew_before INTEGER DEFAULT 0 + ); + -- Add ALTER TABLE statement for existing databases to add the full_chain_pem column + -- This must be separate as ALTER TABLE IF NOT EXISTS ADD COLUMN is not universally supported in SQLite for BLOB. + -- A simpler approach is to just try adding and ignore the error if it exists, as InitSchema does. + -- However, for robustness, we check for column existence first. + PRAGMA foreign_keys = OFF; -- Disable foreign key checks for schema modification + INSERT OR IGNORE INTO sqlite_master (type, name, tbl_name, rootpage, sql) VALUES ('table', 'certificates', 'certificates', 0, 'CREATE TABLE certificates (domain TEXT PRIMARY KEY, email TEXT NOT NULL, cert_pem BLOB NOT NULL, key_pem BLOB NOT NULL, full_chain_pem BLOB DEFAULT '', account_key BLOB NOT NULL, account_url TEXT NOT NULL, issuer_type TEXT DEFAULT '', secret_name TEXT DEFAULT '', updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, enable_rotation BOOLEAN DEFAULT 0, renew_before INTEGER DEFAULT 0)'); + ALTER TABLE certificates ADD COLUMN full_chain_pem BLOB DEFAULT ''; + PRAGMA foreign_keys = ON; -- Re-enable foreign key checks + + CREATE TABLE IF NOT EXISTS extension_configs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT UNIQUE NOT NULL, + data TEXT NOT NULL, + enabled BOOLEAN DEFAULT 1, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + );` +} + +func (s *SQLiteStrategy) SaveCertificateSQL(ph []string) string { + // ph should now have 11 elements for all fields including full_chain_pem + return ` + INSERT INTO certificates (domain, email, cert_pem, key_pem, full_chain_pem, account_key, account_url, issuer_type, secret_name, updated_at, enable_rotation, renew_before) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, ?, ?) + ON CONFLICT(domain) DO UPDATE SET + email = excluded.email, + cert_pem = excluded.cert_pem, + key_pem = excluded.key_pem, + full_chain_pem = excluded.full_chain_pem, -- Added: full_chain_pem update + account_key = excluded.account_key, + account_url = excluded.account_url, + issuer_type = excluded.issuer_type, + secret_name = excluded.secret_name, + updated_at = CURRENT_TIMESTAMP, + enable_rotation = excluded.enable_rotation, + renew_before = excluded.renew_before` +} + +func (s *SQLiteStrategy) SaveSecretSQL(ph []string) string { + return ` + INSERT INTO secrets (name, data, enabled, updated_at, domain) + VALUES (?, ?, 1, CURRENT_TIMESTAMP, ?) + ON CONFLICT(name) DO UPDATE SET data=excluded.data, enabled=1, updated_at=CURRENT_TIMESTAMP, domain=excluded.domain` +} + +func (s *SQLiteStrategy) SaveClusterSQL(ph []string) string { + return ` + INSERT INTO clusters (name, data, enabled, updated_at) + VALUES (?, ?, 1, CURRENT_TIMESTAMP) + ON CONFLICT(name) DO UPDATE SET data=excluded.data, enabled=1, updated_at=CURRENT_TIMESTAMP` +} + +func (s *SQLiteStrategy) SaveListenerSQL(ph []string) string { + return ` + INSERT INTO listeners (name, data, enabled, updated_at) + VALUES (?, ?, 1, CURRENT_TIMESTAMP) + ON CONFLICT(name) DO UPDATE SET data=excluded.data, enabled=1, updated_at=CURRENT_TIMESTAMP` +} + +func (s *SQLiteStrategy) SaveExtensionConfigSQL(ph []string) string { + // ph[0] = name, ph[1] = data + return ` + INSERT INTO extension_configs (name, data, enabled, updated_at) + VALUES (?, ?, 1, CURRENT_TIMESTAMP) + ON CONFLICT(name) DO UPDATE SET data=excluded.data, enabled=1, updated_at=CURRENT_TIMESTAMP` +} + +func (s *SQLiteStrategy) DumpSelectFields(table string) string { + // if table == "secrets" { + // return "name, data, domain" + // } + return "name, data" +} + +func (s *SQLiteStrategy) ScanRawRow(rows *sql.Rows, row *RawRow, table string) error { + var dataStr string + if table == "secrets" { + // SQLite: 3 fields (name, TEXT data, domain) + if err := rows.Scan(&row.Name, &dataStr); err != nil { + return err + } + } + else { + // SQLite: 2 fields (name, TEXT data) + if err := rows.Scan(&row.Name, &dataStr); err != nil { + return err + } + } + row.Data = []byte(dataStr) + return nil +} + +func (s *SQLiteStrategy) RestoreRawRowSQL(table string) string { + // clusters or listeners + return fmt.Sprintf(` + INSERT INTO %s (name, data, enabled, updated_at) + VALUES (?, ?, 1, CURRENT_TIMESTAMP) + ON CONFLICT(name) + DO UPDATE SET data=excluded.data, enabled=1, updated_at=CURRENT_TIMESTAMP`, table) +} + +func (s *SQLiteStrategy) ClearTableSQL(table string) string { + // SQLite safe way to clear + return fmt.Sprintf("DELETE FROM %s", table) +}