fix(polls): canonicalize transaction lock order

This commit is contained in:
Simo committed 2026-09-02 19:09:50 +02:00
1 parent e9680775f9
commit de7cc42ddf
4 files changed
+275 -38

No files matched your search

+41 -9
View File
@@ -1,6 +1,6 @@
"use server";
import { and, eq } from "drizzle-orm";
import { and, eq, inArray } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { z } from "zod";
import {
@@ -97,14 +97,43 @@ const deletePollInput = z.object({
export const deletePoll = adminAction(
{ permission: PERMS.POLLS_EDIT, schema: deletePollInput },
async (ctx) => {
const [existing] = await db
.select({ id: WebsitePoll.id, title: WebsitePoll.title })
.from(WebsitePoll)
.where(eq(WebsitePoll.id, ctx.data.id))
.limit(1);
if (!existing) throw new ActionError("Poll not found");
const existing = await db.transaction(
async (tx) => {
const [poll] = await tx
.select({ id: WebsitePoll.id, title: WebsitePoll.title })
.from(WebsitePoll)
.where(eq(WebsitePoll.id, ctx.data.id))
.for("update");
if (!poll) throw new ActionError("Poll not found");
const questionRows = await tx
.select({ id: WebsitePollQuestion.id })
.from(WebsitePollQuestion)
.where(eq(WebsitePollQuestion.pollId, poll.id))
.orderBy(WebsitePollQuestion.id);
for (const question of questionRows) {
await tx
.select({ id: WebsitePollQuestion.id })
.from(WebsitePollQuestion)
.where(eq(WebsitePollQuestion.id, question.id))
.for("update");
}
const questionIds = questionRows.map(({ id }) => id);
if (questionIds.length > 0) {
await tx
.delete(WebsitePollVote)
.where(inArray(WebsitePollVote.questionId, questionIds));
}
await tx
.delete(WebsitePollQuestion)
.where(eq(WebsitePollQuestion.pollId, poll.id));
await tx.delete(WebsitePoll).where(eq(WebsitePoll.id, poll.id));
return poll;
},
{ isolationLevel: "read committed" },
);
await db.delete(WebsitePoll).where(eq(WebsitePoll.id, ctx.data.id));
logAudit({
userId: ctx.session.user.id,
action: "poll_delete",
@@ -272,8 +301,11 @@ export const voteOnPoll = authAction(
}
}
const votesToInsert = [...ctx.data.votes].sort(
(left, right) => left.questionId - right.questionId,
);
await db.transaction(async (tx) => {
for (const vote of ctx.data.votes) {
for (const vote of votesToInsert) {
await tx.insert(WebsitePollVote).values({
questionId: vote.questionId,
userId,