Skip to content
quarry Artifact [eedcaa27a2]
Login

Artifact [eedcaa27a2]

Artifact eedcaa27a2b14388dcfa8c495d3b94923be82f8842efc83cce412155655ab14f:


// category.go stores forum thread categorization — forge POLICY, not
// content (0001 §8): the forum artifact is untouched, so a fossil peer
// sees a plain thread, and category semantics live entirely here. v1
// has one special category, "announcements"; a policy table for custom
// categories arrives when custom categories do.
package oltp

const categorySchema = `
CREATE TABLE IF NOT EXISTS forum_thread_category (
	repo     TEXT NOT NULL,
	thread   TEXT NOT NULL, -- thread root hash
	category TEXT NOT NULL,
	PRIMARY KEY (repo, thread)
);`

// Announcements is the built-in category: maintainer-start, anyone-
// reply, pinned at the top of the forum and surfaced on the repo home.
const Announcements = "announcements"

// SetThreadCategory records (replacing) one thread's category; an empty
// category uncategorizes.
func (d *DB) SetThreadCategory(repo, thread, category string) error {
	if category == "" {
		_, err := d.w.Exec(`DELETE FROM forum_thread_category WHERE repo = ? AND thread = ?`,
			repo, thread)
		return err
	}
	_, err := d.w.Exec(`INSERT INTO forum_thread_category (repo, thread, category)
		VALUES (?, ?, ?)
		ON CONFLICT (repo, thread) DO UPDATE SET category = excluded.category`,
		repo, thread, category)
	return err
}

// ThreadCategories returns every categorized thread of a repo. One map
// per page render: category assignment is small by construction (only
// special threads carry one).
func (d *DB) ThreadCategories(repo string) (map[string]string, error) {
	rows, err := d.db.Query(`SELECT thread, category FROM forum_thread_category WHERE repo = ?`, repo)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	out := map[string]string{}
	for rows.Next() {
		var t, c string
		if err := rows.Scan(&t, &c); err != nil {
			return nil, err
		}
		out[t] = c
	}
	return out, rows.Err()
}