Files
erika/app/actions/submissions.ts
T

183 lines
5.3 KiB
TypeScript

"use server";
import { db } from "@/db";
import { submissions, answers } from "@/db/schema";
import { eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { getServerSession } from "next-auth";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
export async function submitForm(
formId: string,
answersList: { questionId: string; value: string }[]
) {
const session = await getServerSession(authOptions);
if (!session?.user) throw new Error("Not authenticated");
const discordId = (session.user as { discordId?: string }).discordId;
if (!discordId) throw new Error("No Discord ID found");
const form = await db.query.forms.findFirst({
where: (f, { eq }) => eq(f.id, formId),
});
if (!form) throw new Error("Form not found");
if (!form.isOpen) throw new Error("This form is disabled for now");
const existingSubmission = await db.query.submissions.findFirst({
where: (s, { eq, and }) =>
and(eq(s.formId, formId), eq(s.userDiscordId, discordId)),
});
let submissionId = "";
const isUpdate = !!existingSubmission;
if (existingSubmission) {
submissionId = existingSubmission.id;
// Fetch old answers
const oldAnswersList = await db.query.answers.findMany({
where: (a, { eq }) => eq(a.submissionId, submissionId),
});
const oldAnswersDict = oldAnswersList.reduce((acc, curr) => {
acc[curr.questionId] = curr.value;
return acc;
}, {} as Record<string, string>);
// Ensure editHistory is an array
let history = existingSubmission.editHistory;
if (!Array.isArray(history)) {
history = [];
}
// Push the old answers to history
history.push({
editedAt: new Date().toISOString(),
oldAnswers: oldAnswersDict,
});
// Update submission record
await db
.update(submissions)
.set({
editHistory: history,
})
.where(eq(submissions.id, submissionId));
// Delete old answers
await db.delete(answers).where(eq(answers.submissionId, submissionId));
} else {
// Insert new submission
const [inserted] = await db
.insert(submissions)
.values({
formId,
userDiscordId: discordId,
userName: session.user.name ?? null,
})
.returning();
submissionId = inserted.id;
}
// Insert new/updated answers
if (answersList.length > 0) {
await db.insert(answers).values(
answersList.map((a) => ({
submissionId,
questionId: a.questionId,
value: a.value,
}))
);
}
// Send Discord Webhook
const templateToUse = isUpdate ? (form as any).discordWebhookUpdateTemplate : form.discordWebhookTemplate;
if (form.discordWebhookUrl && templateToUse) {
try {
const questionsList = await db.query.questions.findMany({
where: (q, { eq }) => eq(q.formId, formId),
orderBy: (q, { asc }) => asc(q.displayOrder)
});
const questionMap = questionsList.reduce((acc, q, index) => {
acc[q.id] = String(index + 1);
return acc;
}, {} as Record<string, string>);
const answersDict = answersList.reduce((acc, a) => {
const idx = questionMap[a.questionId];
if (idx) {
acc[idx] = a.value;
}
return acc;
}, {} as Record<string, string>);
let message = templateToUse;
// Replace {1}, {2}, etc. with value, and new variables
message = message.replace(/\{([^}]+)\}/g, (match: string, p1: string) => {
const key = p1.toLowerCase();
if (key === "username" || key === "displayname") {
return session.user?.name || "Unknown User";
}
if (key === "id") {
return discordId || "Unknown ID";
}
return answersDict[p1] || "Not Provided";
});
await fetch(form.discordWebhookUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
content: message,
}),
});
} catch (e) {
console.error("Failed to send webhook", e);
}
}
revalidatePath("/form/admin/result");
return { ok: true };
}
export async function deleteSubmission(id: string) {
const session = await getServerSession(authOptions);
if (!session?.user) throw new Error("Not authenticated");
await db.delete(submissions).where(eq(submissions.id, id));
revalidatePath("/form/admin/result");
return { ok: true };
}
export async function deleteAllSubmissions(formId: string) {
const session = await getServerSession(authOptions);
if (!session?.user) throw new Error("Not authenticated");
await db.delete(submissions).where(eq(submissions.formId, formId));
revalidatePath(`/admin/form/${formId}/result`);
return { ok: true };
}
export async function deleteOwnSubmission(formId: string) {
const session = await getServerSession(authOptions);
if (!session?.user) throw new Error("Not authenticated");
const discordId = (session.user as { discordId?: string }).discordId;
if (!discordId) throw new Error("No Discord ID found");
const { and } = await import("drizzle-orm");
await db.delete(submissions).where(
and(
eq(submissions.formId, formId),
eq(submissions.userDiscordId, discordId)
)
);
revalidatePath(`/form`);
revalidatePath("/form/admin/result");
return { ok: true };
}