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 _jetstream (
107 id integer primary key autoincrement,
108 last_time_us integer not null
109 );
110
111 create table if not exists repo_issue_seqs (
112 repo_at text primary key,
113 next_issue_id integer not null default 1
114 );
115
116 create table if not exists stars (
117 id integer primary key autoincrement,
118 starred_by_did text not null,
119 repo_at text not null,
120 rkey text not null,
121 created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
122 foreign key (repo_at) references repos(at_uri) on delete cascade,
123 unique(starred_by_did, repo_at)
124 );
125
126 create table if not exists migrations (
127 id integer primary key autoincrement,
128 name text unique
129 )
130 `)
131 if err != nil {
132 return nil, err
133 }
134
135 // run migrations
136 runMigration(db, "add-description-to-repos", func(tx *sql.Tx) error {
137 tx.Exec(`
138 alter table repos add column description text check (length(description) <= 200);
139 `)
140 return nil
141 })
142
143 return &DB{db}, nil
144}
145
146type migrationFn = func(*sql.Tx) error
147
148func runMigration(d *sql.DB, name string, migrationFn migrationFn) error {
149 tx, err := d.Begin()
150 if err != nil {
151 return err
152 }
153 defer tx.Rollback()
154
155 var exists bool
156 err = tx.QueryRow("select exists (select 1 from migrations where name = ?)", name).Scan(&exists)
157 if err != nil {
158 return err
159 }
160
161 if !exists {
162 // run migration
163 err = migrationFn(tx)
164 if err != nil {
165 log.Printf("Failed to run migration %s: %v", name, err)
166 return err
167 }
168
169 // mark migration as complete
170 _, err = tx.Exec("insert into migrations (name) values (?)", name)
171 if err != nil {
172 log.Printf("Failed to mark migration %s as complete: %v", name, err)
173 return err
174 }
175
176 // commit the transaction
177 if err := tx.Commit(); err != nil {
178 return err
179 }
180
181 log.Printf("migration %s applied successfully", name)
182 } else {
183 log.Printf("skipped migration %s, already applied", name)
184 }
185
186 return nil
187}