Skip to content
quarry Artifact [11d69adb9d]
Login

Artifact [11d69adb9d]

Artifact 11d69adb9d0e78edad9af22bcf02150eb69b3ddf712e5c096a26b37770fa94ab:


// 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
}