Module Update
This commit is contained in:
140
src/modules/questions/actions.ts
Normal file
140
src/modules/questions/actions.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
"use server";
|
||||
|
||||
import { db } from "@/shared/db";
|
||||
import { questions, questionsToKnowledgePoints } from "@/shared/db/schema";
|
||||
import { CreateQuestionInput, CreateQuestionSchema } from "./schema";
|
||||
import { ActionState } from "@/shared/types/action-state";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { ZodError } from "zod";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
|
||||
// --- Mock Auth Helper (Replace with actual Auth.js call) ---
|
||||
async function getCurrentUser() {
|
||||
// In production: const session = await auth(); return session?.user;
|
||||
// Mocking a teacher user for this demonstration
|
||||
return {
|
||||
id: "user_teacher_123",
|
||||
role: "teacher", // or "admin"
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// --- Recursive Insert Helper ---
|
||||
// We pass 'tx' to ensure all operations run within the same transaction
|
||||
async function insertQuestionWithRelations(
|
||||
tx: any, // using any or strict Drizzle Transaction type if imported
|
||||
input: CreateQuestionInput,
|
||||
authorId: string,
|
||||
parentId: string | null = null
|
||||
) {
|
||||
// We generate ID explicitly here.
|
||||
const newQuestionId = createId();
|
||||
|
||||
await tx.insert(questions).values({
|
||||
id: newQuestionId,
|
||||
content: input.content,
|
||||
type: input.type,
|
||||
difficulty: input.difficulty,
|
||||
authorId: authorId,
|
||||
parentId: parentId,
|
||||
});
|
||||
|
||||
// 2. Link Knowledge Points
|
||||
if (input.knowledgePointIds && input.knowledgePointIds.length > 0) {
|
||||
await tx.insert(questionsToKnowledgePoints).values(
|
||||
input.knowledgePointIds.map((kpId) => ({
|
||||
questionId: newQuestionId,
|
||||
knowledgePointId: kpId,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Handle Sub-Questions (Recursion)
|
||||
if (input.subQuestions && input.subQuestions.length > 0) {
|
||||
for (const subQ of input.subQuestions) {
|
||||
await insertQuestionWithRelations(tx, subQ, authorId, newQuestionId);
|
||||
}
|
||||
}
|
||||
|
||||
return newQuestionId;
|
||||
}
|
||||
|
||||
// --- Main Server Action ---
|
||||
|
||||
export async function createNestedQuestion(
|
||||
prevState: ActionState<string> | undefined,
|
||||
formData: FormData | CreateQuestionInput // Support both FormData and JSON input
|
||||
): Promise<ActionState<string>> {
|
||||
try {
|
||||
// 1. Auth Check
|
||||
const user = await ensureTeacher();
|
||||
|
||||
// 2. Parse Input
|
||||
// If formData is actual FormData, we need to convert it.
|
||||
// For complex nested structures, frontend usually sends JSON string or pure JSON object if using `useServerAction` with arguments.
|
||||
// Here we assume the client might send a raw object (if using direct function call) or we parse FormData.
|
||||
let rawInput: any = formData;
|
||||
|
||||
if (formData instanceof FormData) {
|
||||
// Parsing complex nested JSON from FormData is messy.
|
||||
// We assume one field 'data' contains the JSON, or we expect direct object usage (common in modern Next.js RPC).
|
||||
const jsonString = formData.get("json");
|
||||
if (typeof jsonString === "string") {
|
||||
rawInput = JSON.parse(jsonString);
|
||||
} 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;
|
||||
|
||||
// 3. Database Transaction
|
||||
await db.transaction(async (tx) => {
|
||||
await insertQuestionWithRelations(tx, input, user.id, null);
|
||||
});
|
||||
|
||||
// 4. Revalidate Cache
|
||||
revalidatePath("/questions");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Question created successfully",
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error("Failed to create question:", error);
|
||||
|
||||
// Drizzle/DB Error Handling (Generic)
|
||||
if (error instanceof Error) {
|
||||
// Check for specific DB errors (constraints, etc.)
|
||||
// e.g., if (error.message.includes("Duplicate entry")) ...
|
||||
return {
|
||||
success: false,
|
||||
message: error.message || "Database error occurred",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: "An unexpected error occurred",
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user