"use server"; import { db } from "@/shared/db"; import { questions, questionsToKnowledgePoints } from "@/shared/db/schema"; import { CreateQuestionSchema } from "./schema"; import type { CreateQuestionInput } from "./schema"; import { ActionState } from "@/shared/types/action-state"; import { revalidatePath } from "next/cache"; import { createId } from "@paralleldrive/cuid2"; import { and, eq } from "drizzle-orm"; import { z } from "zod"; import { getQuestions, type GetQuestionsParams } from "./data-access"; async function getCurrentUser() { return { id: "user_teacher_math", role: "teacher", }; } async function ensureTeacher() { const user = await getCurrentUser(); if (!user || (user.role !== "teacher" && user.role !== "admin")) { throw new Error("Unauthorized: Only teachers can perform this action."); } return user; } type Tx = Parameters[0]>[0] async function insertQuestionWithRelations( tx: Tx, input: z.infer, authorId: string, parentId: string | null = null ) { const newQuestionId = createId(); await tx.insert(questions).values({ id: newQuestionId, content: input.content, type: input.type, difficulty: input.difficulty, authorId: authorId, parentId: parentId, }); if (input.knowledgePointIds && input.knowledgePointIds.length > 0) { await tx.insert(questionsToKnowledgePoints).values( input.knowledgePointIds.map((kpId) => ({ questionId: newQuestionId, knowledgePointId: kpId, })) ); } if (input.subQuestions && input.subQuestions.length > 0) { for (const subQ of input.subQuestions) { await insertQuestionWithRelations(tx, subQ, authorId, newQuestionId); } } return newQuestionId; } export async function createNestedQuestion( prevState: ActionState | undefined, formData: FormData | CreateQuestionInput ): Promise> { try { const user = await ensureTeacher(); let rawInput: unknown = formData; if (formData instanceof FormData) { const jsonString = formData.get("json"); if (typeof jsonString === "string") { rawInput = JSON.parse(jsonString) as unknown; } else { return { success: false, message: "Invalid submission format. Expected JSON." }; } } const validatedFields = CreateQuestionSchema.safeParse(rawInput); if (!validatedFields.success) { return { success: false, message: "Validation failed", errors: validatedFields.error.flatten().fieldErrors, }; } const input = validatedFields.data; await db.transaction(async (tx) => { await insertQuestionWithRelations(tx, input, user.id, null); }); revalidatePath("/teacher/questions"); return { success: true, message: "Question created successfully", }; } catch (error) { if (error instanceof Error) { return { success: false, message: error.message || "Database error occurred", }; } return { success: false, message: "An unexpected error occurred", }; } } const UpdateQuestionSchema = z.object({ id: z.string().min(1), type: z.enum(["single_choice", "multiple_choice", "text", "judgment", "composite"]), difficulty: z.number().min(1).max(5), content: z.any(), knowledgePointIds: z.array(z.string()).optional(), }); export async function updateQuestionAction( prevState: ActionState | undefined, formData: FormData ): Promise> { try { const user = await ensureTeacher(); const canEditAll = user.role === "admin"; const jsonString = formData.get("json"); if (typeof jsonString !== "string") { return { success: false, message: "Invalid submission format. Expected JSON." }; } const parsed = UpdateQuestionSchema.safeParse(JSON.parse(jsonString)); if (!parsed.success) { return { success: false, message: "Validation failed", errors: parsed.error.flatten().fieldErrors, }; } const input = parsed.data; await db.transaction(async (tx) => { await tx .update(questions) .set({ type: input.type, difficulty: input.difficulty, content: input.content, updatedAt: new Date(), }) .where(canEditAll ? eq(questions.id, input.id) : and(eq(questions.id, input.id), eq(questions.authorId, user.id))); await tx .delete(questionsToKnowledgePoints) .where(eq(questionsToKnowledgePoints.questionId, input.id)); if (input.knowledgePointIds && input.knowledgePointIds.length > 0) { await tx.insert(questionsToKnowledgePoints).values( input.knowledgePointIds.map((kpId) => ({ questionId: input.id, knowledgePointId: kpId, })) ); } }); revalidatePath("/teacher/questions"); return { success: true, message: "Question updated successfully" }; } catch (error) { if (error instanceof Error) { return { success: false, message: error.message }; } return { success: false, message: "An unexpected error occurred" }; } } async function deleteQuestionRecursive(tx: Tx, questionId: string) { const children = await tx .select({ id: questions.id }) .from(questions) .where(eq(questions.parentId, questionId)); for (const child of children) { await deleteQuestionRecursive(tx, child.id); } await tx.delete(questions).where(eq(questions.id, questionId)); } export async function deleteQuestionAction( prevState: ActionState | undefined, formData: FormData ): Promise> { try { const user = await ensureTeacher(); const canDeleteAll = user.role === "admin"; const questionId = formData.get("questionId"); if (typeof questionId !== "string") { return { success: false, message: "Invalid question ID" }; } await db.transaction(async (tx) => { const q = await tx.query.questions.findFirst({ where: eq(questions.id, questionId), }); if (!q) { throw new Error("Question not found"); } if (!canDeleteAll && q.authorId !== user.id) { throw new Error("Unauthorized"); } await deleteQuestionRecursive(tx, questionId); }); revalidatePath("/teacher/questions"); return { success: true, message: "Question deleted successfully" }; } catch (error) { if (error instanceof Error) { return { success: false, message: error.message }; } return { success: false, message: "Failed to delete question" }; } } export async function getQuestionsAction(params: GetQuestionsParams) { await ensureTeacher(); return await getQuestions(params); }