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}