"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 { getServerSession } from "next-auth"; import { authOptions } from "@/app/api/auth/[...nextauth]/route"; async function assertAdmin() { const session = await getServerSession(authOptions); const adminIds = (process.env.ADMIN_DISCORD_IDS ?? "").split(",").map((s) => s.trim()).filter(Boolean); const discordId = (session?.user as { discordId?: string } | undefined)?.discordId; if (!discordId || !adminIds.includes(discordId)) { throw new Error("Unauthorized"); } } export async function createQuestion( formId: string, data: { type: QuestionType; label: string; required: boolean; displayOrder: number; options?: string[]; } ) { await assertAdmin(); await db.insert(questions).values({ formId, type: data.type, label: data.label, required: data.required, displayOrder: data.displayOrder, options: data.options ?? [], }); revalidatePath("/form"); revalidatePath("/form/admin"); } export async function updateQuestion( id: string, formId: string, data: Partial<{ label: string; type: QuestionType; required: boolean; displayOrder: number; options: string[]; imageUrl: string | null; }> ) { await assertAdmin(); 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); } } await db.update(questions).set(data).where(eq(questions.id, id)); revalidatePath("/form"); revalidatePath("/form/admin"); } export async function bulkUpdateQuestions( formId: string, updates: { id: string; label: string; type: QuestionType; required: boolean; displayOrder: number; options: string[]; }[] ) { await assertAdmin(); // Use Promise.all to update all questions concurrently await Promise.all( updates.map((u) => db .update(questions) .set({ label: u.label, type: u.type, required: u.required, displayOrder: u.displayOrder, options: u.options ?? [], }) .where(eq(questions.id, u.id)) ) ); revalidatePath("/form"); revalidatePath("/form/admin"); } 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 assertAdmin(); 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("/form/admin"); } export async function reorderQuestions( formId: string, orderedIds: string[] ) { await assertAdmin(); await Promise.all( orderedIds.map((id, index) => db .update(questions) .set({ displayOrder: index }) .where(eq(questions.id, id)) ) ); revalidatePath("/form"); revalidatePath("/form/admin"); }