refactor(polls): centralize question semantics
This commit is contained in:
1 parent
66b606961b
commit
abb04f8362
7 files changed
+228
-47
No files matched your search
+42
-22
@@ -10,11 +10,17 @@ import {
|
||||
WebsitePollVote,
|
||||
} from "@/lib/db";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import {
|
||||
type PollQuestionType,
|
||||
parsePollAnswerSelections,
|
||||
parsePollOptions,
|
||||
} from "@/lib/polls/poll-semantics";
|
||||
import { adminAction, authAction } from "@/lib/safe-action";
|
||||
import { ActionError, actionError, actionOk } from "@/lib/safe-action-shared";
|
||||
import { logAudit } from "@/lib/services/audit";
|
||||
import {
|
||||
createPollSchema,
|
||||
pollQuestionPatchSchema,
|
||||
pollQuestionSchema,
|
||||
updatePollSchema,
|
||||
voteOnPollSchema,
|
||||
@@ -54,12 +60,19 @@ export const updatePoll = adminAction(
|
||||
.select({
|
||||
id: WebsitePoll.id,
|
||||
title: WebsitePoll.title,
|
||||
description: WebsitePoll.description,
|
||||
status: WebsitePoll.status,
|
||||
showResults: WebsitePoll.showResults,
|
||||
multipleChoice: WebsitePoll.multipleChoice,
|
||||
startsAt: WebsitePoll.startsAt,
|
||||
endsAt: WebsitePoll.endsAt,
|
||||
})
|
||||
.from(WebsitePoll)
|
||||
.where(eq(WebsitePoll.id, id))
|
||||
.limit(1);
|
||||
if (!existing) throw new ActionError("Poll not found");
|
||||
const merged = createPollSchema.safeParse({ ...existing, ...data });
|
||||
if (!merged.success) throw new ActionError("Invalid poll schedule");
|
||||
|
||||
await db
|
||||
.update(WebsitePoll)
|
||||
@@ -113,7 +126,7 @@ export const addPollQuestion = adminAction(
|
||||
},
|
||||
);
|
||||
|
||||
const updateQuestionInput = pollQuestionSchema.partial().extend({
|
||||
const updateQuestionInput = pollQuestionPatchSchema.extend({
|
||||
id: z.coerce.number().int().positive(),
|
||||
});
|
||||
|
||||
@@ -121,6 +134,21 @@ export const updatePollQuestion = adminAction(
|
||||
{ permission: PERMS.POLLS_EDIT, schema: updateQuestionInput },
|
||||
async (ctx) => {
|
||||
const { id, ...data } = ctx.data;
|
||||
const [existing] = await db
|
||||
.select({
|
||||
pollId: WebsitePollQuestion.pollId,
|
||||
question: WebsitePollQuestion.question,
|
||||
type: WebsitePollQuestion.type,
|
||||
sortOrder: WebsitePollQuestion.sortOrder,
|
||||
options: WebsitePollQuestion.options,
|
||||
})
|
||||
.from(WebsitePollQuestion)
|
||||
.where(eq(WebsitePollQuestion.id, id))
|
||||
.limit(1);
|
||||
if (!existing) throw new ActionError("Poll question not found");
|
||||
const merged = pollQuestionSchema.safeParse({ ...existing, ...data });
|
||||
if (!merged.success) throw new ActionError("Invalid poll question");
|
||||
|
||||
await db
|
||||
.update(WebsitePollQuestion)
|
||||
.set(data)
|
||||
@@ -145,13 +173,6 @@ export const deletePollQuestion = adminAction(
|
||||
|
||||
// ── Public site: vote ───────────────────────────────────────────────
|
||||
|
||||
function parsePollOptions(options: string): string[] {
|
||||
return options
|
||||
.split("\n")
|
||||
.map((o) => o.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export const voteOnPoll = authAction(
|
||||
{
|
||||
schema: voteOnPollSchema,
|
||||
@@ -215,26 +236,25 @@ export const voteOnPoll = authAction(
|
||||
const answer = vote.answer.trim();
|
||||
if (!answer) return actionError("Answer is required");
|
||||
|
||||
const options = parsePollOptions(question.options);
|
||||
const selected = parsePollAnswerSelections(
|
||||
question.type as PollQuestionType,
|
||||
answer,
|
||||
);
|
||||
|
||||
if (question.type === "text") {
|
||||
if (answer.length > 500) {
|
||||
return actionError("Answer is too long");
|
||||
}
|
||||
} else {
|
||||
const options = parsePollOptions(question.options);
|
||||
if (question.type === "multiple") {
|
||||
const selected = answer
|
||||
.split("\n")
|
||||
.map((a) => a.trim())
|
||||
.filter(Boolean);
|
||||
if (selected.length === 0) {
|
||||
return actionError("Select at least one option");
|
||||
}
|
||||
if (selected.some((a) => !options.includes(a))) {
|
||||
return actionError("Invalid option selected");
|
||||
}
|
||||
} else if (!options.includes(answer)) {
|
||||
} else if (question.type === "multiple") {
|
||||
if (selected.length === 0) {
|
||||
return actionError("Select at least one option");
|
||||
}
|
||||
if (selected.some((selection) => !options.includes(selection))) {
|
||||
return actionError("Invalid option selected");
|
||||
}
|
||||
} else if (!options.includes(selected[0] ?? "")) {
|
||||
return actionError("Invalid option selected");
|
||||
}
|
||||
|
||||
const [existing] = await db
|
||||
|
||||
Reference in new issue
Block a user