- Update attendance components and data-access for record management - Update audit log views, filters, and data-access - Update auth login and register forms - Update classes actions, components, and data-access (admin, schedule, stats) - Update course-plans actions, form, list, progress, and schema - Update exams actions, AI pipeline, preview components, and hooks - Update files components (icon, list, preview, upload) and data-access - Update homework assignment form, review view, auto-save hook, and stats-service - Update layout sidebar, header, and navigation config - Update proctoring actions, anti-cheat monitor, and data-access - Update questions actions, components (dialog, actions, columns, filters), and data-access - Update scheduling actions, auto-scheduler, components, and schema - Update textbooks constants and text-selection hook - Update users class-registration, import-dialog, data-access, and user-service
159 lines
4.7 KiB
TypeScript
159 lines
4.7 KiB
TypeScript
"use server"
|
|
|
|
import { requirePermission } from "@/shared/lib/auth-guard"
|
|
import { Permissions } from "@/shared/types/permissions"
|
|
import { CreateQuestionSchema } from "./schema"
|
|
import type { CreateQuestionInput } from "./schema"
|
|
import type { ActionState } from "@/shared/types/action-state"
|
|
import { revalidatePath } from "next/cache"
|
|
import { z } from "zod"
|
|
import {
|
|
createQuestionWithRelations,
|
|
deleteQuestionByIdRecursive,
|
|
getKnowledgePointOptions,
|
|
getQuestions,
|
|
updateQuestionById,
|
|
type GetQuestionsParams,
|
|
} from "./data-access"
|
|
import type { KnowledgePointOption } from "./types"
|
|
import { handleActionError, safeJsonParse } from "@/shared/lib/action-utils"
|
|
|
|
/** Result type of getQuestions (data + meta) */
|
|
type QuestionsListResult = Awaited<ReturnType<typeof getQuestions>>
|
|
|
|
/** Result type of getKnowledgePointOptions */
|
|
type KnowledgePointOptionsResult = KnowledgePointOption[]
|
|
|
|
export async function createQuestionAction(
|
|
prevState: ActionState<string> | undefined,
|
|
formData: FormData | CreateQuestionInput,
|
|
): Promise<ActionState<string>> {
|
|
try {
|
|
const ctx = await requirePermission(Permissions.QUESTION_CREATE)
|
|
|
|
let rawInput: unknown = formData
|
|
|
|
if (formData instanceof FormData) {
|
|
const jsonString = formData.get("json")
|
|
if (typeof jsonString === "string") {
|
|
rawInput = safeJsonParse<unknown>(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
|
|
|
|
const questionId = await createQuestionWithRelations(input, ctx.userId)
|
|
|
|
revalidatePath("/teacher/questions")
|
|
|
|
return {
|
|
success: true,
|
|
message: "Question created successfully",
|
|
data: questionId,
|
|
}
|
|
} catch (e) {
|
|
return handleActionError(e)
|
|
}
|
|
}
|
|
|
|
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.record(z.string(), z.unknown()),
|
|
knowledgePointIds: z.array(z.string()).optional(),
|
|
})
|
|
|
|
export async function updateQuestionAction(
|
|
prevState: ActionState<string> | undefined,
|
|
formData: FormData,
|
|
): Promise<ActionState<string>> {
|
|
try {
|
|
const ctx = await requirePermission(Permissions.QUESTION_UPDATE)
|
|
const canEditAll = ctx.dataScope.type === "all"
|
|
|
|
const jsonString = formData.get("json")
|
|
if (typeof jsonString !== "string") {
|
|
return { success: false, message: "Invalid submission format. Expected JSON." }
|
|
}
|
|
|
|
const parsed = UpdateQuestionSchema.safeParse(safeJsonParse<unknown>(jsonString, "题目内容格式无效"))
|
|
if (!parsed.success) {
|
|
return {
|
|
success: false,
|
|
message: "Validation failed",
|
|
errors: parsed.error.flatten().fieldErrors,
|
|
}
|
|
}
|
|
|
|
const { id, ...updateData } = parsed.data
|
|
|
|
await updateQuestionById(id, updateData, canEditAll, ctx.userId)
|
|
|
|
revalidatePath("/teacher/questions")
|
|
|
|
return { success: true, message: "Question updated successfully", data: id }
|
|
} catch (e) {
|
|
return handleActionError(e)
|
|
}
|
|
}
|
|
|
|
export async function deleteQuestionAction(
|
|
prevState: ActionState<string> | undefined,
|
|
formData: FormData,
|
|
): Promise<ActionState<string>> {
|
|
try {
|
|
const ctx = await requirePermission(Permissions.QUESTION_DELETE)
|
|
const canDeleteAll = ctx.dataScope.type === "all"
|
|
|
|
const questionId = formData.get("questionId")
|
|
if (typeof questionId !== "string") {
|
|
return { success: false, message: "Invalid question ID" }
|
|
}
|
|
|
|
await deleteQuestionByIdRecursive(questionId, canDeleteAll, ctx.userId)
|
|
|
|
revalidatePath("/teacher/questions")
|
|
|
|
return { success: true, message: "Question deleted successfully", data: questionId }
|
|
} catch (e) {
|
|
return handleActionError(e)
|
|
}
|
|
}
|
|
|
|
export async function getQuestionsAction(
|
|
params: GetQuestionsParams,
|
|
): Promise<ActionState<QuestionsListResult>> {
|
|
try {
|
|
await requirePermission(Permissions.QUESTION_READ)
|
|
const data = await getQuestions(params)
|
|
return { success: true, data }
|
|
} catch (e) {
|
|
return handleActionError(e)
|
|
}
|
|
}
|
|
|
|
export async function getKnowledgePointOptionsAction(): Promise<
|
|
ActionState<KnowledgePointOptionsResult>
|
|
> {
|
|
try {
|
|
await requirePermission(Permissions.QUESTION_READ)
|
|
const data = await getKnowledgePointOptions()
|
|
return { success: true, data }
|
|
} catch (e) {
|
|
return handleActionError(e)
|
|
}
|
|
}
|