forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
this repo has no description
fork
Configure Feed
Select the types of activity you want to include in your feed.
1package db
2
3import (
4 "context"
5 "database/sql"
6 "log"
7
8 _ "github.com/mattn/go-sqlite3"
9)
10
11type DB struct {
12 *sql.DB
13}
14
15type Execer interface {
16 Query(query string, args ...any) (*sql.Rows, error)
17 QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
18 QueryRow(query string, args ...any) *sql.Row
19 QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
20 Exec(query string, args ...any) (sql.Result, error)
21 ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
22 Prepare(query string) (*sql.Stmt, error)
23 PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
24}
25
26func Make(dbPath string) (*DB, error) {
27 db, err := sql.Open("sqlite3", dbPath)
28 if err != nil {
29 return nil, err
30 }
31 _, err = db.Exec(`
32 pragma journal_mode = WAL;
33 pragma synchronous = normal;
34 pragma foreign_keys = on;
35 pragma temp_store = memory;
36 pragma mmap_size = 30000000000;
37 pragma page_size = 32768;
38 pragma auto_vacuum = incremental;
39 pragma busy_timeout = 5000;
40
41 create table if not exists registrations (
42 id integer primary key autoincrement,
43 domain text not null unique,
44 did text not null,
45 secret text not null,
46 created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
47 registered text
48 );
49 create table if not exists public_keys (
50 id integer primary key autoincrement,
51 did text not null,
52 name text not null,
53 key text not null,
54 created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
55 unique(did, name, key)
56 );
57 create table if not exists repos (
58 id integer primary key autoincrement,
59 did text not null,
60 name text not null,
61 knot text not null,
62 rkey text not null,
63 at_uri text not null unique,
64 created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
65 unique(did, name, knot, rkey)
66 );
67 create table if not exists collaborators (
68 id integer primary key autoincrement,
69 did text not null,
70 repo integer not null,
71 foreign key (repo) references repos(id) on delete cascade
72 );
73 create table if not exists follows (
74 user_did text not null,
75 subject_did text not null,
76 rkey text not null,
77 followed_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
78 primary key (user_did, subject_did),
79 check (user_did <> subject_did)
80 );
81 create table if not exists issues (
82 id integer primary key autoincrement,
83 owner_did text not null,
84 repo_at text not null,
85 issue_id integer not null,
86 title text not null,
87 body text not null,
88 open integer not null default 1,
89 created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
90 issue_at text,
91 unique(repo_at, issue_id),
92 foreign key (repo_at) references repos(at_uri) on delete cascade
93 );
94 create table if not exists comments (
95 id integer primary key autoincrement,
96 owner_did text not null,
97 issue_id integer not null,
98 repo_at text not null,
99 comment_id integer not null,
100 comment_at text not null,
101 body text not null,
102 created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
103 unique(issue_id, comment_id),
104 foreign key (repo_at, issue_id) references issues(repo_at, issue_id) on delete cascade
105 );
106 create table if not exists pulls (
107 -- identifiers
108 id integer primary key autoincrement,
109 pull_id integer not null,
110
111 -- at identifiers
112 repo_at text not null,
113 owner_did text not null,
114 rkey text not null,
115 pull_at text,
116
117 -- content
118 title text not null,
119 body text not null,
120 target_branch text not null,
121 state integer not null default 0 check (state in (0, 1, 2)), -- open, merged, closed
122
123 -- meta
124 created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
125
126 -- constraints
127 unique(repo_at, pull_id),
128 foreign key (repo_at) references repos(at_uri) on delete cascade
129 );
130
131 -- every pull must have atleast 1 submission: the initial submission
132 create table if not exists pull_submissions (
133 -- identifiers
134 id integer primary key autoincrement,
135 pull_id integer not null,
136
137 -- at identifiers
138 repo_at text not null,
139
140 -- content, these are immutable, and require a resubmission to update
141 round_number integer not null default 0,
142 patch text,
143
144 -- meta
145 created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
146
147 -- constraints
148 unique(repo_at, pull_id, round_number),
149 foreign key (repo_at, pull_id) references pulls(repo_at, pull_id) on delete cascade
150 );
151
152 create table if not exists pull_comments (
153 -- identifiers
154 id integer primary key autoincrement,
155 pull_id integer not null,
156 submission_id integer not null,
157
158 -- at identifiers
159 repo_at text not null,
160 owner_did text not null,
161 comment_at text not null,
162
163 -- content
164 body text not null,
165
166 -- meta
167 created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
168
169 -- constraints
170 foreign key (repo_at, pull_id) references pulls(repo_at, pull_id) on delete cascade,
171 foreign key (submission_id) references pull_submissions(id) on delete cascade
172 );
173
174 create table if not exists _jetstream (
175 id integer primary key autoincrement,
176 last_time_us integer not null
177 );
178
179 create table if not exists repo_issue_seqs (
180 repo_at text primary key,
181 next_issue_id integer not null default 1
182 );
183
184 create table if not exists repo_pull_seqs (
185 repo_at text primary key,
186 next_pull_id integer not null default 1
187 );
188
189 create table if not exists stars (
190 id integer primary key autoincrement,
191 starred_by_did text not null,
192 repo_at text not null,
193 rkey text not null,
194 created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
195 foreign key (repo_at) references repos(at_uri) on delete cascade,
196 unique(starred_by_did, repo_at)
197 );
198
199 create table if not exists emails (
200 id integer primary key autoincrement,
201 did text not null,
202 email text not null,
203 verified integer not null default 0,
204 verification_code text not null,
205 last_sent text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
206 is_primary integer not null default 0,
207 created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
208 unique(did, email)
209 );
210
211 create table if not exists migrations (
212 id integer primary key autoincrement,
213 name text unique
214 )
215 `)
216 if err != nil {
217 return nil, err
218 }
219
220 // run migrations
221 runMigration(db, "add-description-to-repos", func(tx *sql.Tx) error {
222 tx.Exec(`
223 alter table repos add column description text check (length(description) <= 200);
224 `)
225 return nil
226 })
227
228 runMigration(db, "add-rkey-to-pubkeys", func(tx *sql.Tx) error {
229 // add unconstrained column
230 _, err := tx.Exec(`
231 alter table public_keys
232 add column rkey text;
233 `)
234 if err != nil {
235 return err
236 }
237
238 // backfill
239 _, err = tx.Exec(`
240 update public_keys
241 set rkey = ''
242 where rkey is null;
243 `)
244 if err != nil {
245 return err
246 }
247
248 return nil
249 })
250
251 return &DB{db}, nil
252}
253
254type migrationFn = func(*sql.Tx) error
255
256func runMigration(d *sql.DB, name string, migrationFn migrationFn) error {
257 tx, err := d.Begin()
258 if err != nil {
259 return err
260 }
261 defer tx.Rollback()
262
263 var exists bool
264 err = tx.QueryRow("select exists (select 1 from migrations where name = ?)", name).Scan(&exists)
265 if err != nil {
266 return err
267 }
268
269 if !exists {
270 // run migration
271 err = migrationFn(tx)
272 if err != nil {
273 log.Printf("Failed to run migration %s: %v", name, err)
274 return err
275 }
276
277 // mark migration as complete
278 _, err = tx.Exec("insert into migrations (name) values (?)", name)
279 if err != nil {
280 log.Printf("Failed to mark migration %s as complete: %v", name, err)
281 return err
282 }
283
284 // commit the transaction
285 if err := tx.Commit(); err != nil {
286 return err
287 }
288
289 log.Printf("migration %s applied successfully", name)
290 } else {
291 log.Printf("skipped migration %s, already applied", name)
292 }
293
294 return nil
295}