Skip to content
quarry Artifact [af0873ad2d]
Login

Artifact [af0873ad2d]

Artifact af0873ad2d7021ec1383d1985d62442dd3293ebc9ec04c2da8f567aa74cb266a:


// 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.w.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.w.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 empty.
// Double quotes deliberately: gofmt rewrites a pair of apostrophes in a
// comment into a typographic closing quote, which reads as a typo.
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
}