Module Update
Some checks failed
CI / build-and-test (push) Failing after 1m31s
CI / deploy (push) Has been skipped

This commit is contained in:
SpecialX
2025-12-30 14:42:30 +08:00
parent f1797265b2
commit e7c902e8e1
148 changed files with 19317 additions and 113 deletions

View 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",
};
}
}