Skip to content
quarry Artifact [8874ec6a5b]
Login

Artifact [8874ec6a5b]

Artifact 8874ec6a5ba777d70a8cb89eb8223b1ad0df64de06505e13eed6c6f3762116e5:


// 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 (
	"strings"
	"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.w.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
}

// PollBallot is one account's ballot on one version of a poll post.
type PollBallot struct {
	Account string
	Post    string
	Choice  int
	At      string
}

// PollBallots returns every ballot cast on any of posts: the versions of
// one post, which the API tallies as one poll.
func (d *DB) PollBallots(repo string, posts []string) ([]PollBallot, error) {
	if len(posts) == 0 {
		return nil, nil
	}
	args := []any{repo}
	for _, p := range posts {
		args = append(args, p)
	}
	rows, err := d.db.Query(`SELECT account, post, choice, at FROM poll_vote
		WHERE repo = ? AND post IN (?`+strings.Repeat(", ?", len(posts)-1)+`)`, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var out []PollBallot
	for rows.Next() {
		var b PollBallot
		if err := rows.Scan(&b.Account, &b.Post, &b.Choice, &b.At); err != nil {
			return nil, err
		}
		out = append(out, b)
	}
	return out, rows.Err()
}