200 lines
5.9 KiB
TypeScript
200 lines
5.9 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;
|
|
let oldAnswersDict: Record<string, string> = {};
|
|
|
|
if (existingSubmission) {
|
|
submissionId = existingSubmission.id;
|
|
|
|
// Fetch old answers
|
|
const oldAnswersList = await db.query.answers.findMany({
|
|
where: (a, { eq }) => eq(a.submissionId, submissionId),
|
|
});
|
|
|
|
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 updatesText = "";
|
|
if (isUpdate) {
|
|
const changed: string[] = [];
|
|
for (const ans of answersList) {
|
|
const oldVal = oldAnswersDict[ans.questionId];
|
|
if (oldVal !== ans.value) {
|
|
const qTitle = questionsList.find(q => q.id === ans.questionId)?.label || `Question`;
|
|
changed.push(`**${qTitle}**: \`${oldVal || "Empty"}\` ➡️ \`${ans.value || "Empty"}\``);
|
|
}
|
|
}
|
|
updatesText = changed.length > 0 ? changed.join("\n") : "No changes made.";
|
|
}
|
|
|
|
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";
|
|
}
|
|
if (key === "update") {
|
|
return updatesText;
|
|
}
|
|
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 };
|
|
}
|