···29293030 query := `
3131 SELECT s.id, f.feed_url, COALESCE(s.title, f.title), s.category, s.added_at
3232- FROM subscriptions s
3333- JOIN feeds f ON s.feed_url = f.feed_url
3232+ FROM articles.subscriptions s
3333+ JOIN articles.feeds f ON s.feed_url = f.feed_url
3434 WHERE s.user_did = ?`
3535 args := []any{repo}
3636···9595 query := `
9696 SELECT a.uri, a.cid, u.did, a.feed_url, a.article_url,
9797 a.quote, a.note, a.tags, a.rating, a.created_at
9898- FROM annotations a
9898+ FROM articles.annotations a
9999 JOIN users u ON a.author_did = u.did
100100 WHERE 1=1`
101101 args := []any{}
···180180181181 query := `
182182 SELECT l.uri, l.cid, u.did, l.feed_url, l.article_url, l.created_at
183183- FROM likes l
183183+ FROM articles.likes l
184184 JOIN users u ON l.author_did = u.did
185185 WHERE 1=1`
186186 args := []any{}
···250250251251 query := `
252252 SELECT l.feed_url, l.article_url, a.title, COUNT(*) as like_count
253253- FROM likes l
254254- LEFT JOIN articles a ON l.article_url = a.url
253253+ FROM articles.likes l
254254+ LEFT JOIN articles.articles a ON l.article_url = a.url
255255 WHERE 1=1`
256256 args := []any{}
257257···372372 query := `
373373 SELECT u.did, COUNT(s.id) as subscription_count
374374 FROM users u
375375- LEFT JOIN subscriptions s ON u.did = s.user_did
375375+ LEFT JOIN articles.subscriptions s ON u.did = s.user_did
376376 WHERE u.did IN (` + strings.Join(placeholders, ",") + `)`
377377378378 if cursor != "" {
···417417 }
418418 subRows, err := h.db.QueryContext(r.Context(), `
419419 SELECT s.user_did, s.feed_url, COALESCE(s.title, f.title), s.category
420420- FROM subscriptions s
421421- JOIN feeds f ON s.feed_url = f.feed_url
420420+ FROM articles.subscriptions s
421421+ JOIN articles.feeds f ON s.feed_url = f.feed_url
422422 WHERE s.user_did IN (`+strings.Join(ph, ",")+`)
423423 ORDER BY s.user_did, s.added_at DESC
424424 `, args...)
-226
internal/db/db.go
···5757type DB struct {
5858 *sql.DB
5959}
6060-6161-func (d *DB) Close() error {
6262- return d.DB.Close()
6363-}
6464-6565-func Open(path string) (*DB, error) {
6666- db, err := sql.Open("sqlite3_glean", path+"?cache=shared&"+DSN)
6767- if err != nil {
6868- return nil, err
6969- }
7070-7171- db.SetMaxOpenConns(10)
7272- db.SetMaxIdleConns(5)
7373- db.SetConnMaxLifetime(30 * time.Minute)
7474-7575- wrapped := &DB{db}
7676- if err := initSchema(wrapped); err != nil {
7777- db.Close()
7878- return nil, err
7979- }
8080-8181- return wrapped, nil
8282-}
8383-8484-func initSchema(db *DB) error {
8585- for _, s := range schema {
8686- if _, err := db.Exec(s); err != nil {
8787- return err
8888- }
8989- }
9090- return nil
9191-}
9292-9393-var schema = []string{
9494- `CREATE TABLE IF NOT EXISTS users (
9595- did TEXT PRIMARY KEY,
9696- indexed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
9797- updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
9898- )`,
9999- `CREATE TABLE IF NOT EXISTS feeds (
100100- feed_url TEXT PRIMARY KEY,
101101- title TEXT,
102102- site_url TEXT,
103103- description TEXT,
104104- feed_type TEXT CHECK(feed_type IN ('rss', 'atom', 'json')),
105105- last_fetched_at DATETIME,
106106- last_error TEXT,
107107- subscriber_count INTEGER NOT NULL DEFAULT 0,
108108- etag TEXT,
109109- last_modified TEXT,
110110- consecutive_empty_fetches INTEGER NOT NULL DEFAULT 0,
111111- error_count INTEGER NOT NULL DEFAULT 0,
112112- favicon_url TEXT
113113- )`,
114114- `CREATE TABLE IF NOT EXISTS subscriptions (
115115- id INTEGER PRIMARY KEY AUTOINCREMENT,
116116- user_did TEXT NOT NULL,
117117- feed_url TEXT NOT NULL,
118118- title TEXT,
119119- category TEXT,
120120- added_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
121121- uri TEXT,
122122- cid TEXT,
123123- UNIQUE(user_did, feed_url)
124124- )`,
125125- `CREATE TABLE IF NOT EXISTS articles (
126126- id INTEGER PRIMARY KEY AUTOINCREMENT,
127127- feed_url TEXT NOT NULL,
128128- guid TEXT NOT NULL,
129129- title TEXT NOT NULL DEFAULT '',
130130- url TEXT,
131131- author TEXT,
132132- summary TEXT,
133133- content TEXT,
134134- full_content TEXT,
135135- published DATETIME,
136136- updated DATETIME,
137137- fetched_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
138138- UNIQUE(feed_url, guid)
139139- )`,
140140- `CREATE TABLE IF NOT EXISTS read_state (
141141- user_did TEXT NOT NULL,
142142- article_id INTEGER NOT NULL,
143143- is_read BOOLEAN NOT NULL DEFAULT 0,
144144- read_at DATETIME,
145145- PRIMARY KEY (user_did, article_id)
146146- )`,
147147- `CREATE TABLE IF NOT EXISTS annotations (
148148- id INTEGER PRIMARY KEY AUTOINCREMENT,
149149- uri TEXT NOT NULL UNIQUE,
150150- author_did TEXT NOT NULL,
151151- feed_url TEXT NOT NULL,
152152- article_url TEXT NOT NULL,
153153- quote TEXT,
154154- note TEXT,
155155- tags TEXT,
156156- rating INTEGER,
157157- created_at DATETIME NOT NULL,
158158- cid TEXT
159159- )`,
160160- `CREATE TABLE IF NOT EXISTS likes (
161161- id INTEGER PRIMARY KEY AUTOINCREMENT,
162162- uri TEXT NOT NULL UNIQUE,
163163- author_did TEXT NOT NULL,
164164- feed_url TEXT NOT NULL,
165165- article_url TEXT NOT NULL,
166166- created_at DATETIME NOT NULL,
167167- cid TEXT,
168168- UNIQUE(author_did, feed_url, article_url)
169169- )`,
170170- `CREATE TABLE IF NOT EXISTS feed_similarity (
171171- feed_a TEXT NOT NULL,
172172- feed_b TEXT NOT NULL,
173173- jaccard REAL NOT NULL,
174174- computed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
175175- PRIMARY KEY (feed_a, feed_b),
176176- CHECK(feed_a < feed_b)
177177- )`,
178178- `CREATE TABLE IF NOT EXISTS user_similarity (
179179- user_a TEXT NOT NULL,
180180- user_b TEXT NOT NULL,
181181- jaccard REAL NOT NULL,
182182- common_feeds INTEGER NOT NULL,
183183- common_likes INTEGER NOT NULL DEFAULT 0,
184184- common_tags INTEGER NOT NULL DEFAULT 0,
185185- computed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
186186- PRIMARY KEY (user_a, user_b),
187187- CHECK(user_a < user_b)
188188- )`,
189189- `CREATE TABLE IF NOT EXISTS follows (
190190- user_did TEXT NOT NULL,
191191- target_did TEXT NOT NULL,
192192- uri TEXT,
193193- cid TEXT,
194194- followed_at DATETIME,
195195- PRIMARY KEY (user_did, target_did)
196196- )`,
197197- `CREATE TABLE IF NOT EXISTS oauth_auth_requests (
198198- state TEXT PRIMARY KEY,
199199- data TEXT NOT NULL
200200- )`,
201201- `CREATE TABLE IF NOT EXISTS oauth_sessions (
202202- account_did TEXT NOT NULL,
203203- session_id TEXT NOT NULL,
204204- data TEXT NOT NULL,
205205- PRIMARY KEY (account_did, session_id)
206206- )`,
207207- `CREATE TABLE IF NOT EXISTS dismissed_recommendations (
208208- user_did TEXT NOT NULL,
209209- target_type TEXT NOT NULL CHECK(target_type IN ('feed', 'article')),
210210- target_id TEXT NOT NULL,
211211- reason TEXT,
212212- dismissed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
213213- PRIMARY KEY (user_did, target_type, target_id)
214214- )`,
215215- `CREATE TABLE IF NOT EXISTS recommendation_impressions (
216216- user_did TEXT NOT NULL,
217217- target_type TEXT NOT NULL CHECK(target_type IN ('feed', 'article')),
218218- target_id TEXT NOT NULL,
219219- first_shown_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
220220- last_shown_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
221221- shown_count INTEGER NOT NULL DEFAULT 1,
222222- acted BOOLEAN NOT NULL DEFAULT 0,
223223- PRIMARY KEY (user_did, target_type, target_id)
224224- )`,
225225- `CREATE TABLE IF NOT EXISTS follow_distances (
226226- user_a TEXT NOT NULL,
227227- user_b TEXT NOT NULL,
228228- distance INTEGER NOT NULL CHECK(distance IN (1, 2)),
229229- PRIMARY KEY (user_a, user_b)
230230- )`,
231231- `CREATE TABLE IF NOT EXISTS user_signal_weights (
232232- user_did TEXT PRIMARY KEY,
233233- w_sub REAL NOT NULL DEFAULT 1.0,
234234- w_like REAL NOT NULL DEFAULT 0.5,
235235- w_tag REAL NOT NULL DEFAULT 0.3,
236236- w_social REAL NOT NULL DEFAULT 0.7,
237237- w_pop REAL NOT NULL DEFAULT 0.2,
238238- w_category REAL NOT NULL DEFAULT 0.4,
239239- updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
240240- )`,
241241- `CREATE TABLE IF NOT EXISTS user_signal_profiles (
242242- user_did TEXT PRIMARY KEY,
243243- total_likes INTEGER NOT NULL DEFAULT 0,
244244- total_tags INTEGER NOT NULL DEFAULT 0,
245245- top_categories TEXT,
246246- updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
247247- )`,
248248- `CREATE INDEX IF NOT EXISTS idx_subscriptions_feed ON subscriptions(feed_url)`,
249249- `CREATE INDEX IF NOT EXISTS idx_subscriptions_feed_user ON subscriptions(feed_url, user_did)`,
250250- `CREATE INDEX IF NOT EXISTS idx_subscriptions_user ON subscriptions(user_did)`,
251251- `CREATE INDEX IF NOT EXISTS idx_subscriptions_uri ON subscriptions(uri)`,
252252- `CREATE INDEX IF NOT EXISTS idx_likes_author_feed ON likes(author_did, feed_url, created_at)`,
253253- `CREATE INDEX IF NOT EXISTS idx_articles_feed ON articles(feed_url)`,
254254- `CREATE INDEX IF NOT EXISTS idx_articles_published ON articles(published DESC)`,
255255- `CREATE INDEX IF NOT EXISTS idx_articles_url ON articles(url)`,
256256- `CREATE INDEX IF NOT EXISTS idx_read_state_unread ON read_state(user_did, is_read) WHERE is_read = 0`,
257257- `CREATE INDEX IF NOT EXISTS idx_annotations_article ON annotations(article_url)`,
258258- `CREATE INDEX IF NOT EXISTS idx_annotations_author ON annotations(author_did)`,
259259- `CREATE INDEX IF NOT EXISTS idx_annotations_created_at ON annotations(created_at DESC)`,
260260- `CREATE INDEX IF NOT EXISTS idx_likes_article ON likes(feed_url, article_url)`,
261261- `CREATE INDEX IF NOT EXISTS idx_likes_author ON likes(author_did)`,
262262- `CREATE INDEX IF NOT EXISTS idx_likes_created_at ON likes(created_at DESC)`,
263263- `CREATE INDEX IF NOT EXISTS idx_follows_user ON follows(user_did)`,
264264- `CREATE INDEX IF NOT EXISTS idx_follows_target ON follows(target_did)`,
265265- `CREATE INDEX IF NOT EXISTS idx_follows_uri ON follows(uri)`,
266266- `CREATE INDEX IF NOT EXISTS idx_user_similarity_b ON user_similarity(user_b)`,
267267- `CREATE INDEX IF NOT EXISTS idx_user_similarity_a ON user_similarity(user_a)`,
268268- `CREATE INDEX IF NOT EXISTS idx_dismissed_user_type ON dismissed_recommendations(user_did, target_type)`,
269269- `CREATE INDEX IF NOT EXISTS idx_impressions_user_unacted ON recommendation_impressions(user_did, acted, shown_count)`,
270270- `CREATE INDEX IF NOT EXISTS idx_impressions_last_shown ON recommendation_impressions(last_shown_at)`,
271271- `CREATE INDEX IF NOT EXISTS idx_follow_distances_b ON follow_distances(user_b)`,
272272- `CREATE INDEX IF NOT EXISTS idx_follow_distances_a_dist ON follow_distances(user_a, distance)`,
273273- `CREATE INDEX IF NOT EXISTS idx_follows_followed_at ON follows(followed_at)`,
274274- `CREATE VIRTUAL TABLE IF NOT EXISTS articles_fts USING fts5(title, summary, content, author, content=articles, content_rowid=id)`,
275275- `CREATE TRIGGER IF NOT EXISTS articles_ai AFTER INSERT ON articles BEGIN
276276- INSERT INTO articles_fts(rowid, title, summary, content, author) VALUES (new.id, new.title, new.summary, new.content, new.author);
277277- END`,
278278- `CREATE TRIGGER IF NOT EXISTS articles_ad AFTER DELETE ON articles BEGIN
279279- INSERT INTO articles_fts(articles_fts, rowid, title, summary, content, author) VALUES('delete', old.id, old.title, old.summary, old.content, old.author);
280280- END`,
281281- `CREATE TRIGGER IF NOT EXISTS articles_au AFTER UPDATE ON articles BEGIN
282282- INSERT INTO articles_fts(articles_fts, rowid, title, summary, content, author) VALUES('delete', old.id, old.title, old.summary, old.content, old.author);
283283- INSERT INTO articles_fts(rowid, title, summary, content, author) VALUES (new.id, new.title, new.summary, new.content, new.author);
284284- END`,
285285-}