Artifact 11d69adb9d0e78edad9af22bcf02150eb69b3ddf712e5c096a26b37770fa94ab:
- File
internal/oltp/subs.go
— part of check-in
[893885b3d5]
at
2026-08-21 19:49:17
on branch quarry-forum
— forum subscriptions: auto-sub on authoring, reply/mention fanout, unsub sticks
Fanout rides the write path only (a reindex can never re-notify); delivery/dedup stays in the notify companion via TxnID. (user: dmestas, size: 2210) [annotate] [blame] [check-ins using] [more...]
// subs.go stores forum subscriptions. The rule that matters: an // EXPLICIT unsubscribe outranks every auto-subscribe forever — "leave // me alone" must survive the subscriber's own later replies. package oltp const subsSchema = ` CREATE TABLE IF NOT EXISTS forum_sub ( repo TEXT NOT NULL, account TEXT NOT NULL, thread TEXT NOT NULL, -- thread root hash state TEXT NOT NULL, -- 'sub' | 'unsub' at TEXT NOT NULL, PRIMARY KEY (repo, account, thread) ); CREATE INDEX IF NOT EXISTS forum_sub_by_thread ON forum_sub (repo, thread, state);` // SetForumSub records an EXPLICIT choice, replacing whatever stood. func (d *DB) SetForumSub(repo, account, thread, state string) error { _, err := d.db.Exec(`INSERT INTO forum_sub (repo, account, thread, state, at) VALUES (?, ?, ?, ?, ?) ON CONFLICT (repo, account, thread) DO UPDATE SET state = excluded.state, at = excluded.at`, repo, account, thread, state, now()) return err } // AutoSubscribe subscribes an author to a thread they touched — unless // ANY explicit row exists. INSERT OR IGNORE is the whole mechanism: an // earlier unsubscribe is a row, so it is never overridden. func (d *DB) AutoSubscribe(repo, account, thread string) error { _, err := d.db.Exec(`INSERT OR IGNORE INTO forum_sub (repo, account, thread, state, at) VALUES (?, ?, ?, 'sub', ?)`, repo, account, thread, now()) return err } // ForumSubscribers lists the accounts subscribed to a thread. func (d *DB) ForumSubscribers(repo, thread string) ([]string, error) { rows, err := d.db.Query(`SELECT account FROM forum_sub WHERE repo = ? AND thread = ? AND state = 'sub' ORDER BY account`, repo, thread) if err != nil { return nil, err } defer rows.Close() var out []string for rows.Next() { var a string if err := rows.Scan(&a); err != nil { return nil, err } out = append(out, a) } return out, rows.Err() } // ForumSubState answers one account's standing: 'sub', 'unsub', or ''. func (d *DB) ForumSubState(repo, account, thread string) string { var s string if account == "" { return "" } d.db.QueryRow(`SELECT state FROM forum_sub WHERE repo = ? AND account = ? AND thread = ?`, repo, account, thread).Scan(&s) return s }