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