apps/feeds/db.go 2.6 K raw
1
package main
2
3
import (
4
	"database/sql"
5
	"errors"
6
	"fmt"
7
	"strings"
8
)
9
10
const feedsSchema = `
11
CREATE TABLE IF NOT EXISTS categories (
12
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
13
    name       TEXT NOT NULL UNIQUE,
14
    created_at TEXT NOT NULL DEFAULT (datetime('now'))
15
);
16
17
CREATE TABLE IF NOT EXISTS subscriptions (
18
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
19
    feed_url        TEXT NOT NULL UNIQUE,
20
    title           TEXT NOT NULL,
21
    site_url        TEXT,
22
    favicon_url     TEXT,
23
    category_id     INTEGER REFERENCES categories(id) ON DELETE SET NULL,
24
    etag            TEXT,
25
    last_modified   TEXT,
26
    last_fetched_at TEXT,
27
    last_error      TEXT,
28
    added_at        TEXT NOT NULL DEFAULT (datetime('now'))
29
);
30
CREATE INDEX IF NOT EXISTS idx_subs_category ON subscriptions(category_id);
31
32
CREATE TABLE IF NOT EXISTS items (
33
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
34
    subscription_id INTEGER NOT NULL REFERENCES subscriptions(id) ON DELETE CASCADE,
35
    guid            TEXT NOT NULL,
36
    title           TEXT NOT NULL,
37
    link            TEXT NOT NULL,
38
    author          TEXT,
39
    published_at    INTEGER NOT NULL,
40
    is_read         INTEGER NOT NULL DEFAULT 0,
41
    fetched_at      TEXT NOT NULL DEFAULT (datetime('now')),
42
    UNIQUE(subscription_id, guid)
43
);
44
CREATE INDEX IF NOT EXISTS idx_items_sub_pub ON items(subscription_id, published_at DESC);
45
CREATE INDEX IF NOT EXISTS idx_items_pub ON items(published_at DESC);
46
CREATE INDEX IF NOT EXISTS idx_items_unread ON items(is_read, published_at DESC);
47
48
CREATE TABLE IF NOT EXISTS settings (
49
    key   TEXT PRIMARY KEY,
50
    value TEXT NOT NULL
51
);
52
`
53
54
func seedSettings(db *sql.DB, defaultPoll int) error {
55
	_, err := db.Exec(`INSERT INTO settings (key, value) VALUES ('poll_interval_minutes', ?)
56
		ON CONFLICT(key) DO NOTHING`, fmt.Sprintf("%d", defaultPoll))
57
	return err
58
}
59
60
func getSetting(db *sql.DB, key string) (string, bool, error) {
61
	var value string
62
	err := db.QueryRow(`SELECT value FROM settings WHERE key = ?`, key).Scan(&value)
63
	if errors.Is(err, sql.ErrNoRows) {
64
		return "", false, nil
65
	}
66
	if err != nil {
67
		return "", false, err
68
	}
69
	return value, true, nil
70
}
71
72
func setSetting(db *sql.DB, key, value string) error {
73
	_, err := db.Exec(`INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value`, key, value)
74
	return err
75
}
76
77
func nullableString(s *string) any {
78
	if s == nil || strings.TrimSpace(*s) == "" {
79
		return nil
80
	}
81
	return *s
82
}
83
84
func nullableInt64(v *int64) any {
85
	if v == nil {
86
		return nil
87
	}
88
	return *v
89
}
90
91
func stringPtr(s string) *string {
92
	if strings.TrimSpace(s) == "" {
93
		return nil
94
	}
95
	return &s
96
}