package db import ( "database/sql" "encoding/json" "fmt" "os" "path/filepath" _ "modernc.org/sqlite" ) type DB struct { sql *sql.DB DbPath string } const schema = ` CREATE TABLE IF NOT EXISTS webui_meta ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS webui_config ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); ` func Init(dataDir string) (*DB, error) { if err := os.MkdirAll(dataDir, 0o755); err != nil { return nil, fmt.Errorf("create data dir: %w", err) } dbPath := filepath.Join(dataDir, "webui.db") sqlDB, err := sql.Open("sqlite", dbPath) if err != nil { return nil, fmt.Errorf("open sqlite: %w", err) } if _, err := sqlDB.Exec(schema); err != nil { return nil, fmt.Errorf("init schema: %w", err) } // ensure schema version _, _ = sqlDB.Exec(`INSERT OR IGNORE INTO webui_meta (key,value) VALUES ('schema_version','1')`) return &DB{sql: sqlDB, DbPath: dbPath}, nil } func (d *DB) Close() { _ = d.sql.Close() } // ── Generic config key-value ────────────────────────────────────────────────── func (d *DB) GetConfig(key string) (string, bool) { var val string err := d.sql.QueryRow(`SELECT value FROM webui_config WHERE key=?`, key).Scan(&val) if err != nil { return "", false } return val, true } func (d *DB) SetConfig(key, value string) error { _, err := d.sql.Exec(`INSERT OR REPLACE INTO webui_config (key,value) VALUES (?,?)`, key, value) return err } func (d *DB) DeleteConfig(key string) error { _, err := d.sql.Exec(`DELETE FROM webui_config WHERE key=?`, key) return err } func (d *DB) AllConfig() (map[string]string, error) { rows, err := d.sql.Query(`SELECT key,value FROM webui_config`) if err != nil { return nil, err } defer rows.Close() m := map[string]string{} for rows.Next() { var k, v string if err := rows.Scan(&k, &v); err != nil { return nil, err } m[k] = v } return m, nil } // ── Pinned sessions (stored as JSON array in webui_config) ─────────────────── const pinnedKey = "__pinned_sessions__" func (d *DB) GetPinnedSessions() ([]string, error) { val, ok := d.GetConfig(pinnedKey) if !ok { return nil, nil } var paths []string if err := json.Unmarshal([]byte(val), &paths); err != nil { return nil, err } return paths, nil } func (d *DB) SetSessionPinned(path string, pinned bool) error { paths, _ := d.GetPinnedSessions() set := map[string]struct{}{} for _, p := range paths { set[p] = struct{}{} } if pinned { set[path] = struct{}{} } else { delete(set, path) } newPaths := make([]string, 0, len(set)) for p := range set { newPaths = append(newPaths, p) } b, err := json.Marshal(newPaths) if err != nil { return err } return d.SetConfig(pinnedKey, string(b)) }