feat: settings service — key-value store with upsert

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell 2026-03-17 06:15:55 -04:00
parent 62133d8966
commit 4de47352cd
2 changed files with 111 additions and 0 deletions

View File

@ -0,0 +1,41 @@
package settings
import "database/sql"
type SettingsService struct {
db *sql.DB
}
func NewSettingsService(db *sql.DB) *SettingsService {
return &SettingsService{db: db}
}
func (s *SettingsService) Get(key string) (string, error) {
var value string
err := s.db.QueryRow("SELECT value FROM settings WHERE key = ?", key).Scan(&value)
if err == sql.ErrNoRows {
return "", nil
}
return value, err
}
func (s *SettingsService) GetDefault(key, defaultValue string) string {
val, err := s.Get(key)
if err != nil || val == "" {
return defaultValue
}
return val
}
func (s *SettingsService) Set(key, value string) error {
_, err := s.db.Exec(
"INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = ?",
key, value, value,
)
return err
}
func (s *SettingsService) Delete(key string) error {
_, err := s.db.Exec("DELETE FROM settings WHERE key = ?", key)
return err
}

View File

@ -0,0 +1,70 @@
package settings
import (
"path/filepath"
"testing"
"github.com/vstockwell/wraith/internal/db"
)
func setupTestDB(t *testing.T) *SettingsService {
t.Helper()
d, err := db.Open(filepath.Join(t.TempDir(), "test.db"))
if err != nil {
t.Fatal(err)
}
if err := db.Migrate(d); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { d.Close() })
return NewSettingsService(d)
}
func TestSetAndGet(t *testing.T) {
s := setupTestDB(t)
if err := s.Set("theme", "dracula"); err != nil {
t.Fatalf("Set() error: %v", err)
}
val, err := s.Get("theme")
if err != nil {
t.Fatalf("Get() error: %v", err)
}
if val != "dracula" {
t.Errorf("Get() = %q, want %q", val, "dracula")
}
}
func TestGetMissing(t *testing.T) {
s := setupTestDB(t)
val, err := s.Get("nonexistent")
if err != nil {
t.Fatalf("Get() error: %v", err)
}
if val != "" {
t.Errorf("Get() = %q, want empty string", val)
}
}
func TestSetOverwrites(t *testing.T) {
s := setupTestDB(t)
s.Set("key", "value1")
s.Set("key", "value2")
val, _ := s.Get("key")
if val != "value2" {
t.Errorf("Get() = %q, want %q", val, "value2")
}
}
func TestGetWithDefault(t *testing.T) {
s := setupTestDB(t)
val := s.GetDefault("missing", "fallback")
if val != "fallback" {
t.Errorf("GetDefault() = %q, want %q", val, "fallback")
}
}