Files
erika/app/actions/questions.ts
T

146 lines
3.4 KiB
TypeScript

"use server";
import { db } from "@/db";
import { unlink } from "fs/promises";
import path from "path";
import { questions } from "@/db/schema";
import type { QuestionType } from "@/db/schema";
import { eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { requireAdmin } from "@/lib/auth";
import { sanitizeHtml } from "@/lib/sanitize-html";
export async function createQuestion(
formId: string,
data: {
type: QuestionType;
label: string;
required: boolean;
displayOrder: number;
options?: string[];
}
) {
await requireAdmin();
await db.insert(questions).values({
formId,
type: data.type,
label: sanitizeHtml(data.label),
required: data.required,
displayOrder: data.displayOrder,
options: data.options ?? [],
allowOther: false,
});
revalidatePath("/form");
revalidatePath("/admin/form");
}
export async function updateQuestion(
id: string,
formId: string,
data: Partial<{
label: string;
type: QuestionType;
required: boolean;
displayOrder: number;
options: string[];
imageUrl: string | null;
allowOther: boolean;
}>
) {
await requireAdmin();
if (data.imageUrl !== undefined) {
const q = await db.query.questions.findFirst({
where: (q, { eq }) => eq(q.id, id),
});
if (q?.imageUrl && q.imageUrl !== data.imageUrl) {
await deleteImageFile(q.imageUrl);
}
}
const nextData = {
...data,
label: data.label === undefined ? undefined : sanitizeHtml(data.label),
};
await db.update(questions).set(nextData).where(eq(questions.id, id));
revalidatePath("/form");
revalidatePath("/admin/form");
}
export async function bulkUpdateQuestions(
formId: string,
updates: {
id: string;
label: string;
type: QuestionType;
required: boolean;
displayOrder: number;
options: string[];
allowOther: boolean;
}[]
) {
await requireAdmin();
// Use Promise.all to update all questions concurrently
await Promise.all(
updates.map((u) =>
db
.update(questions)
.set({
label: sanitizeHtml(u.label),
type: u.type,
required: u.required,
displayOrder: u.displayOrder,
options: u.options ?? [],
allowOther: u.allowOther,
})
.where(eq(questions.id, u.id))
)
);
revalidatePath("/form");
revalidatePath("/admin/form");
}
async function deleteImageFile(imageUrl: string | null) {
if (!imageUrl) return;
try {
const filename = imageUrl.split("/").pop();
if (filename) {
const filepath = path.join(process.cwd(), "public", "form", filename);
await unlink(filepath);
}
} catch (err) {
console.error("Failed to delete image file", err);
}
}
export async function deleteQuestion(id: string, formId: string) {
await requireAdmin();
const q = await db.query.questions.findFirst({
where: (q, { eq }) => eq(q.id, id),
});
if (q?.imageUrl) {
await deleteImageFile(q.imageUrl);
}
await db.delete(questions).where(eq(questions.id, id));
revalidatePath("/form");
revalidatePath(`/admin/form/${formId}`);
}
export async function reorderQuestions(
formId: string,
orderedIds: string[]
) {
await requireAdmin();
await Promise.all(
orderedIds.map((id, index) =>
db
.update(questions)
.set({ displayOrder: index })
.where(eq(questions.id, id))
)
);
revalidatePath("/form");
revalidatePath(`/admin/form/${formId}`);
}