arche / internal/archesrv/db.go

commit 154431fd
  1package archesrv
  2
  3import (
  4	"database/sql"
  5	_ "embed"
  6	"fmt"
  7	"time"
  8
  9	_ "github.com/mattn/go-sqlite3"
 10)
 11
 12//go:embed sql/001_initial.sql
 13var sql001 string
 14
 15//go:embed sql/002_ssh_webhooks_invites.sql
 16var sql002 string
 17
 18//go:embed sql/003_per_repo_hooks.sql
 19var sql003 string
 20
 21//go:embed sql/004_mtls_certs.sql
 22var sql004 string
 23
 24//go:embed sql/005_commit_signatures.sql
 25var sql005 string
 26
 27//go:embed sql/006_stack_reviews.sql
 28var sql006 string
 29
 30type DB struct {
 31	db *sql.DB
 32}
 33
 34func openDB(path string) (*DB, error) {
 35	db, err := sql.Open("sqlite3", path+"?_journal_mode=WAL&_busy_timeout=5000")
 36	if err != nil {
 37		return nil, fmt.Errorf("open server.db: %w", err)
 38	}
 39	db.SetMaxOpenConns(1)
 40	d := &DB{db: db}
 41	if err := d.migrate(); err != nil {
 42		db.Close()
 43		return nil, err
 44	}
 45	return d, nil
 46}
 47
 48func (d *DB) Close() error { return d.db.Close() }
 49
 50func (d *DB) migrate() error {
 51	type migration struct {
 52		version int
 53		sql     string
 54	}
 55	all := []migration{{1, sql001}, {2, sql002}, {3, sql003}, {4, sql004}, {5, sql005}, {6, sql006}}
 56
 57	if _, err := d.db.Exec(`CREATE TABLE IF NOT EXISTS schema_migrations (
 58		version    INTEGER PRIMARY KEY,
 59		applied_at INTEGER NOT NULL
 60	)`); err != nil {
 61		return err
 62	}
 63
 64	rows, err := d.db.Query("SELECT version FROM schema_migrations")
 65	if err != nil {
 66		return err
 67	}
 68	applied := map[int]bool{}
 69	for rows.Next() {
 70		var v int
 71		rows.Scan(&v) //nolint:errcheck
 72		applied[v] = true
 73	}
 74	rows.Close()
 75
 76	for _, m := range all {
 77		if applied[m.version] {
 78			continue
 79		}
 80		if _, err := d.db.Exec(m.sql); err != nil {
 81			return fmt.Errorf("migration %d: %w", m.version, err)
 82		}
 83		if _, err := d.db.Exec(
 84			"INSERT INTO schema_migrations(version,applied_at) VALUES(?,?)",
 85			m.version, time.Now().Unix(),
 86		); err != nil {
 87			return fmt.Errorf("record migration %d: %w", m.version, err)
 88		}
 89	}
 90	return nil
 91}
 92
 93type StackReview struct {
 94	ChangeID   string
 95	Status     string
 96	ReviewerID int64
 97	UpdatedAt  int64
 98}
 99
100func (d *DB) GetStackReview(repoID int64, changeID string) string {
101	var status string
102	err := d.db.QueryRow(
103		"SELECT status FROM stack_reviews WHERE repo_id = ? AND change_id = ?",
104		repoID, changeID,
105	).Scan(&status)
106	if err != nil {
107		return "open"
108	}
109	return status
110}
111
112func (d *DB) SetStackReview(repoID int64, changeID, status string, reviewerID int64) error {
113	_, err := d.db.Exec(
114		`INSERT INTO stack_reviews (repo_id, change_id, status, reviewer_id, updated_at)
115		 VALUES (?, ?, ?, ?, ?)
116		 ON CONFLICT(repo_id, change_id) DO UPDATE SET
117		   status = excluded.status,
118		   reviewer_id = excluded.reviewer_id,
119		   updated_at = excluded.updated_at`,
120		repoID, changeID, status, reviewerID, time.Now().Unix(),
121	)
122	return err
123}
124
125func (d *DB) ListStackReviews(repoID int64) (map[string]string, error) {
126	rows, err := d.db.Query(
127		"SELECT change_id, status FROM stack_reviews WHERE repo_id = ?", repoID,
128	)
129	if err != nil {
130		return nil, err
131	}
132	defer rows.Close()
133	m := make(map[string]string)
134	for rows.Next() {
135		var changeID, status string
136		if err := rows.Scan(&changeID, &status); err != nil {
137			return nil, err
138		}
139		m[changeID] = status
140	}
141	return m, rows.Err()
142}