Files
pilens/internal/store/store.go
T
Andy Dixon 52da3c13f6 Core packages: config validation, network renderers, root helper, auth, SQLite store, capture engine
Capture engine streams dumpcap pcapng into verbatim, hashed segments with
per-segment tshark summaries; display filters, PDML dissection with verified
byte ranges, export and import. Tests run in a Debian trixie container.
2026-10-09 17:34:32 +01:00

89 lines
2.2 KiB
Go

// Package store opens the SQLite database and applies migrations.
package store
import (
"database/sql"
"encoding/json"
"fmt"
"io/fs"
"sort"
"time"
_ "modernc.org/sqlite"
"pilens.dev/pilens/migrations"
)
type Store struct{ *sql.DB }
func Open(path string) (*Store, error) {
dsn := "file:" + path + "?_pragma=journal_mode(WAL)&_pragma=foreign_keys(1)&_pragma=busy_timeout(5000)&_pragma=synchronous(NORMAL)"
db, err := sql.Open("sqlite", dsn)
if err != nil {
return nil, err
}
db.SetMaxOpenConns(4)
s := &Store{db}
if err := s.migrate(); err != nil {
db.Close()
return nil, err
}
return s, nil
}
func (s *Store) migrate() error {
if _, err := s.Exec(`CREATE TABLE IF NOT EXISTS schema_migrations (name TEXT PRIMARY KEY, applied_at INTEGER NOT NULL)`); err != nil {
return err
}
names, err := fs.Glob(migrations.FS, "*.sql")
if err != nil {
return err
}
sort.Strings(names)
for _, n := range names {
var x int
if s.QueryRow(`SELECT 1 FROM schema_migrations WHERE name=?`, n).Scan(&x) == nil {
continue
}
b, _ := migrations.FS.ReadFile(n)
tx, err := s.Begin()
if err != nil {
return err
}
if _, err := tx.Exec(string(b)); err != nil {
tx.Rollback()
return fmt.Errorf("migration %s: %w", n, err)
}
tx.Exec(`INSERT INTO schema_migrations VALUES (?,?)`, n, time.Now().Unix())
if err := tx.Commit(); err != nil {
return err
}
}
return nil
}
func (s *Store) GetJSON(key string, v any) (bool, error) {
var raw string
err := s.QueryRow(`SELECT value FROM settings WHERE key=?`, key).Scan(&raw)
if err == sql.ErrNoRows {
return false, nil
}
if err != nil {
return false, err
}
return true, json.Unmarshal([]byte(raw), v)
}
func (s *Store) PutJSON(key string, v any) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
_, err = s.Exec(`INSERT INTO settings(key,value,updated_at) VALUES(?,?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=excluded.updated_at`, key, string(b), time.Now().Unix())
return err
}
func (s *Store) Audit(actor, action, detail, remote string) {
s.Exec(`INSERT INTO audit(ts,actor,action,detail,remote) VALUES(?,?,?,?,?)`, time.Now().UnixMilli(), actor, action, detail, remote)
}