Artifact 503c0b2afc2057d3e401e4f1ee960a3aa266baadf27317a329199a2ca2d210b4:
- File
internal/oltp/poll.go
— part of check-in
[e674e718a8]
at
2026-08-21 19:34:28
on branch quarry-forum
— forum polls: fenced-block definition, OLTP ballots, no-script widget
The block syncs as markdown (fossil peers render it readably); option range and close date re-read from the body at cast time, never the form. (user: dmestas, size: 2109) [annotate] [blame] [check-ins using] [more...]
// poll.go stores poll ballots. The poll DEFINITION lives in the forum // post's body (a ```poll fenced block — a fossil peer renders readable // markdown, which is the whole compatibility story); only the ballots // are forge state, with the same idempotent shape as forum votes: the // (repo, account, post) key IS the one-ballot rule. package oltp import "time" const pollSchema = ` CREATE TABLE IF NOT EXISTS poll_vote ( repo TEXT NOT NULL, account TEXT NOT NULL, post TEXT NOT NULL, -- the forum post carrying the poll block choice INTEGER NOT NULL, -- option index in the block, 0-based at TEXT NOT NULL, PRIMARY KEY (repo, account, post) ); CREATE INDEX IF NOT EXISTS poll_vote_by_post ON poll_vote (repo, post);` // SetPollVote records (replacing) one account's ballot; changeable // until the poll closes, which the API enforces from the parsed block. func (d *DB) SetPollVote(repo, account, post string, choice int) error { _, err := d.db.Exec(`INSERT INTO poll_vote (repo, account, post, choice, at) VALUES (?, ?, ?, ?, ?) ON CONFLICT (repo, account, post) DO UPDATE SET choice = excluded.choice, at = excluded.at`, repo, account, post, choice, time.Now().UTC().Format(time.RFC3339)) return err } // PollCounts returns ballots per option index for one poll post. func (d *DB) PollCounts(repo, post string) (map[int]int, error) { rows, err := d.db.Query(`SELECT choice, count(*) FROM poll_vote WHERE repo = ? AND post = ? GROUP BY choice`, repo, post) if err != nil { return nil, err } defer rows.Close() out := map[int]int{} for rows.Next() { var c, n int if err := rows.Scan(&c, &n); err != nil { return nil, err } out[c] = n } return out, rows.Err() } // PollChoice returns one account's ballot; ok is false when they have // not voted (or are anonymous). func (d *DB) PollChoice(repo, account, post string) (choice int, ok bool) { if account == "" { return 0, false } err := d.db.QueryRow(`SELECT choice FROM poll_vote WHERE repo = ? AND account = ? AND post = ?`, repo, account, post).Scan(&choice) return choice, err == nil }