Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 76 additions & 8 deletions lib/poll.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,16 @@ Poll.getInfo = async function (pollId, withVotes = false) {
if (!poll) {
return null;
}
poll.voteCount = parseInt(voteCount, 10) || 0;

// Check for vote counts override (e.g., from remote AP Question)
const override = await Poll.getVoteCountsOverride(pollId);
if (override) {
poll.voteCount = override.total || 0;
poll.uniqueVoters = override.uniqueVoters || null;
} else {
poll.voteCount = parseInt(voteCount, 10) || 0;
}

const end = parseInt(poll.end, 10);
poll.ended = end > 0 && Date.now() > end;
poll.options = await loadOptions(pollId, poll.options, withVotes);
Expand All @@ -222,6 +231,18 @@ Poll.getPollOptionIds = async function (pollId) {
async function loadOptions(pollId, optionsJson, withVotes = false) {
const options = tryParseOptions(optionsJson);

// Check for vote counts override (e.g., from remote AP Question)
const override = await Poll.getVoteCountsOverride(pollId);
if (override) {
options.forEach((option, index) => {
if (option) {
const ov = override.options?.find(o => String(o.id) === String(option.id));
option.voteCount = ov?.voteCount || 0;
}
});
return options;
}

const votes = await db.getSortedSetsMembers(options.map(o => `poll:${pollId}:options:${o.id}:votes`));

options.forEach((option, index) => {
Expand All @@ -246,19 +267,29 @@ function tryParseOptions(optionsJson) {
}

Poll.getOption = async function (pollId, option, withVotes = false) {
const [optionsJson, votes, voteCount] = await Promise.all([
db.getObjectField(`poll:${pollId}`, 'options'),
withVotes ?
db.getSortedSetRange(`poll:${pollId}:options:${option}:votes`, 0, -1) :
null,
Poll.getOptionVoteCount(pollId, option),
]);
const optionsJson = await db.getObjectField(`poll:${pollId}`, 'options');
const options = tryParseOptions(optionsJson);
const optionData = options.find(opt => String(opt.id) === String(option));
if (!optionData) {
return null;
}

// Check for vote counts override (e.g., from remote AP Question)
const override = await Poll.getVoteCountsOverride(pollId);
if (override) {
const ov = override.options?.find(o => String(o.id) === String(option));
optionData.voteCount = ov?.voteCount || 0;
// No voter identity data available for override polls
return optionData;
}

const [votes, voteCount] = await Promise.all([
withVotes ?
db.getSortedSetRange(`poll:${pollId}:options:${option}:votes`, 0, -1) :
null,
Poll.getOptionVoteCount(pollId, option),
]);

if (votes) {
optionData.votes = votes;
}
Expand All @@ -267,20 +298,57 @@ Poll.getOption = async function (pollId, option, withVotes = false) {
};

Poll.getVotersCount = async function (pollId) {
// Check for vote counts override (e.g., from remote AP Question)
const override = await Poll.getVoteCountsOverride(pollId);
if (override) {
return override.uniqueVoters || null;
}
return await db.sortedSetCard(`poll:${pollId}:voters`);
};

Poll.getVoteCount = async function (pollId) {
// Check for vote counts override (e.g., from remote AP Question)
const override = await Poll.getVoteCountsOverride(pollId);
if (override) {
return override.total || 0;
}
const optionIds = await Poll.getPollOptionIds(pollId);
return await db.sortedSetsCardSum(
optionIds.map(option => `poll:${pollId}:options:${option}:votes`)
);
};

Poll.getOptionVoteCount = async function (pollId, option) {
// Check for vote counts override (e.g., from remote AP Question)
const override = await Poll.getVoteCountsOverride(pollId);
if (override) {
const ov = override.options?.find(o => String(o.id) === String(option));
return ov?.voteCount || 0;
}
return await db.sortedSetCard(`poll:${pollId}:options:${option}:votes`);
};

/**
* Returns the vote counts override object if set on the poll, otherwise null.
* The override is stored as JSON in the `voteCounts` field of the poll hash.
* Expected shape: { total: number, uniqueVoters: number, options: [{ id, voteCount }] }
*/
Poll.getVoteCountsOverride = async function (pollId) {
const raw = await db.getObjectField(`poll:${pollId}`, 'voteCounts');
if (!raw) {
return null;
}
try {
const parsed = JSON.parse(raw);
if (typeof parsed.total === 'number' && Array.isArray(parsed.options)) {
return parsed;
}
return null;
} catch {
return null;
}
};

Poll.hasOption = async function (pollId, option) {
const optionIds = new Set(await Poll.getPollOptionIds(pollId));
return optionIds.has(String(option));
Expand Down