package db import ( "context" "database/sql" "fmt" _ "modernc.org/sqlite" ) func Open(path string) (*sql.DB, error) { db, err := sql.Open("sqlite", path+"?_journal=WAL&_timeout=5000&_fk=true") if err != nil { return nil, err } db.SetMaxOpenConns(1) // SQLite is single-writer if err := migrate(db); err != nil { db.Close() return nil, fmt.Errorf("migrate: %w", err) } return db, nil } func migrate(db *sql.DB) error { ctx := context.Background() if _, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL)`); err != nil { return err } var version int db.QueryRowContext(ctx, `SELECT COALESCE(MAX(version),0) FROM schema_version`).Scan(&version) //nolint migrations := []string{ // v1: core schema `CREATE TABLE IF NOT EXISTS tickets ( id INTEGER PRIMARY KEY AUTOINCREMENT, ticket_number INTEGER NOT NULL, user_id TEXT NOT NULL, panel TEXT NOT NULL, type TEXT NOT NULL, channel_id TEXT NOT NULL UNIQUE, opened_at DATETIME NOT NULL, claimed_by TEXT, claimed_at DATETIME, closed_at DATETIME, closed_by TEXT, reason TEXT, transcript_path TEXT, status TEXT NOT NULL DEFAULT 'open' ); CREATE INDEX IF NOT EXISTS idx_tickets_user ON tickets(user_id); CREATE INDEX IF NOT EXISTS idx_tickets_status ON tickets(status); CREATE INDEX IF NOT EXISTS idx_tickets_type ON tickets(type); CREATE TABLE IF NOT EXISTS claim_messages ( ticket_id INTEGER PRIMARY KEY, message_id TEXT NOT NULL, last_reup_at DATETIME NOT NULL ); CREATE TABLE IF NOT EXISTS convocation_counter ( id INTEGER PRIMARY KEY CHECK(id=1), count INTEGER NOT NULL DEFAULT 0 ); INSERT OR IGNORE INTO convocation_counter(id,count) VALUES(1,0);`, } for i, m := range migrations { v := i + 1 if v <= version { continue } if _, err := db.ExecContext(ctx, m); err != nil { return fmt.Errorf("migration v%d: %w", v, err) } if _, err := db.ExecContext(ctx, `INSERT INTO schema_version(version) VALUES(?)`, v); err != nil { return fmt.Errorf("record migration v%d: %w", v, err) } } return nil }