feat: add submission deletion support, transition webhook templates to index-based placeholders, enforce image-only uploads, and localize UI to Thai.
This commit is contained in:
@@ -96,23 +96,24 @@ export async function submitForm(
|
||||
if (form.discordWebhookUrl && templateToUse) {
|
||||
try {
|
||||
const questionsList = await db.query.questions.findMany({
|
||||
where: (q, { eq }) => eq(q.formId, formId)
|
||||
where: (q, { eq }) => eq(q.formId, formId),
|
||||
orderBy: (q, { asc }) => asc(q.displayOrder)
|
||||
});
|
||||
const questionMap = questionsList.reduce((acc, q) => {
|
||||
acc[q.id] = q.label;
|
||||
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 label = questionMap[a.questionId];
|
||||
if (label) {
|
||||
acc[label] = a.value;
|
||||
const idx = questionMap[a.questionId];
|
||||
if (idx) {
|
||||
acc[idx] = a.value;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
|
||||
let message = templateToUse;
|
||||
// Replace {Label} with value
|
||||
// Replace {1}, {2}, etc. with value
|
||||
message = message.replace(/\{([^}]+)\}/g, (match: string, p1: string) => {
|
||||
return answersDict[p1] || "Not Provided";
|
||||
});
|
||||
@@ -152,3 +153,23 @@ export async function deleteAllSubmissions() {
|
||||
revalidatePath("/form/admin/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 };
|
||||
}
|
||||
|
||||
+12
-2
@@ -20,8 +20,18 @@ export async function uploadImage(formData: FormData): Promise<string> {
|
||||
const file = formData.get("file") as File;
|
||||
if (!file) throw new Error("No file provided");
|
||||
|
||||
const ext = file.name.split(".").pop() ?? "png";
|
||||
const filename = `${crypto.randomUUID()}.${ext}`;
|
||||
if (!file.type.startsWith("image/")) {
|
||||
throw new Error("Only image files are allowed");
|
||||
}
|
||||
|
||||
const allowedExtensions = ["png", "jpg", "jpeg", "webp", "gif"];
|
||||
const originalExt = (file.name.split(".").pop() || "").toLowerCase();
|
||||
|
||||
if (!allowedExtensions.includes(originalExt)) {
|
||||
throw new Error("Invalid image extension");
|
||||
}
|
||||
|
||||
const filename = `${crypto.randomUUID()}.${originalExt}`;
|
||||
const uploadDir = path.join(process.cwd(), "public", "form");
|
||||
|
||||
await mkdir(uploadDir, { recursive: true });
|
||||
|
||||
Reference in New Issue
Block a user