fix(polls): canonicalize transaction lock order
This commit is contained in:
1 parent
e9680775f9
commit
de7cc42ddf
4 files changed
+275
-38
No files matched your search
+41
-9
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user