mirror of
https://github.com/cfngc4594/monaco-editor-lsp-next.git
synced 2026-05-31 10:18:52 +00:00
304 lines
7.4 KiB
TypeScript
304 lines
7.4 KiB
TypeScript
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
import prisma from "@/lib/prisma";
|
|
import {
|
|
assertCourseManagePermission,
|
|
assertTeacherOrAdmin,
|
|
getAuthenticatedActor,
|
|
} from "@/app/(protected)/dashboard/actions/course-auth";
|
|
|
|
interface AssignmentProblemInput {
|
|
problemId: string;
|
|
order?: number;
|
|
}
|
|
|
|
interface CreateAssignmentInput {
|
|
courseId: string;
|
|
title: string;
|
|
description?: string;
|
|
opensAt?: string;
|
|
dueAt?: string;
|
|
published?: boolean;
|
|
problems: AssignmentProblemInput[];
|
|
}
|
|
|
|
interface UpdateAssignmentInput {
|
|
title?: string;
|
|
description?: string | null;
|
|
opensAt?: string | null;
|
|
dueAt?: string | null;
|
|
published?: boolean;
|
|
problems?: AssignmentProblemInput[];
|
|
}
|
|
|
|
async function validateProblems(problems: AssignmentProblemInput[]) {
|
|
if (!Array.isArray(problems) || problems.length === 0) {
|
|
throw new Error("作业至少需要一道题目");
|
|
}
|
|
|
|
const uniqueProblemIds = [...new Set(problems.map((item) => item.problemId))];
|
|
const existingProblems = await prisma.problem.findMany({
|
|
where: {
|
|
id: { in: uniqueProblemIds },
|
|
isPublished: true,
|
|
},
|
|
select: { id: true },
|
|
});
|
|
|
|
if (existingProblems.length !== uniqueProblemIds.length) {
|
|
throw new Error("题目不存在或未发布");
|
|
}
|
|
}
|
|
|
|
function parseDateValue(value?: string | null) {
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
const date = new Date(value);
|
|
if (Number.isNaN(date.getTime())) {
|
|
throw new Error("日期格式不合法");
|
|
}
|
|
return date;
|
|
}
|
|
|
|
export async function createAssignment(input: CreateAssignmentInput) {
|
|
const actor = await getAuthenticatedActor();
|
|
assertTeacherOrAdmin(actor);
|
|
await assertCourseManagePermission(input.courseId, actor);
|
|
await validateProblems(input.problems);
|
|
|
|
const title = input.title.trim();
|
|
if (!title) {
|
|
throw new Error("作业标题不能为空");
|
|
}
|
|
|
|
const opensAt = parseDateValue(input.opensAt);
|
|
const dueAt = parseDateValue(input.dueAt);
|
|
|
|
if (opensAt && dueAt && opensAt >= dueAt) {
|
|
throw new Error("截止时间必须晚于开始时间");
|
|
}
|
|
|
|
const assignment = await prisma.assignment.create({
|
|
data: {
|
|
courseId: input.courseId,
|
|
title,
|
|
description: input.description?.trim() || null,
|
|
opensAt,
|
|
dueAt,
|
|
published: Boolean(input.published),
|
|
createdById: actor.id,
|
|
problems: {
|
|
create: input.problems.map((item, index) => ({
|
|
problemId: item.problemId,
|
|
order: item.order ?? index + 1,
|
|
})),
|
|
},
|
|
},
|
|
select: {
|
|
id: true,
|
|
courseId: true,
|
|
},
|
|
});
|
|
|
|
revalidatePath(`/dashboard/teacher/courses/${assignment.courseId}`);
|
|
revalidatePath(
|
|
`/dashboard/teacher/courses/${assignment.courseId}/assignments/${assignment.id}`
|
|
);
|
|
revalidatePath("/dashboard/student/courses");
|
|
|
|
return assignment;
|
|
}
|
|
|
|
export async function updateAssignment(
|
|
assignmentId: string,
|
|
input: UpdateAssignmentInput
|
|
) {
|
|
const actor = await getAuthenticatedActor();
|
|
assertTeacherOrAdmin(actor);
|
|
|
|
const assignment = await prisma.assignment.findUnique({
|
|
where: { id: assignmentId },
|
|
select: { id: true, courseId: true },
|
|
});
|
|
|
|
if (!assignment) {
|
|
throw new Error("作业不存在");
|
|
}
|
|
|
|
await assertCourseManagePermission(assignment.courseId, actor);
|
|
|
|
const nextData: {
|
|
title?: string;
|
|
description?: string | null;
|
|
opensAt?: Date | null;
|
|
dueAt?: Date | null;
|
|
published?: boolean;
|
|
} = {};
|
|
|
|
if (typeof input.title === "string") {
|
|
const title = input.title.trim();
|
|
if (!title) {
|
|
throw new Error("作业标题不能为空");
|
|
}
|
|
nextData.title = title;
|
|
}
|
|
if (typeof input.description !== "undefined") {
|
|
nextData.description = input.description?.trim() || null;
|
|
}
|
|
if (typeof input.opensAt !== "undefined") {
|
|
nextData.opensAt = parseDateValue(input.opensAt);
|
|
}
|
|
if (typeof input.dueAt !== "undefined") {
|
|
nextData.dueAt = parseDateValue(input.dueAt);
|
|
}
|
|
if (typeof input.published === "boolean") {
|
|
nextData.published = input.published;
|
|
}
|
|
|
|
if (nextData.opensAt && nextData.dueAt && nextData.opensAt >= nextData.dueAt) {
|
|
throw new Error("截止时间必须晚于开始时间");
|
|
}
|
|
|
|
await prisma.assignment.update({
|
|
where: { id: assignmentId },
|
|
data: nextData,
|
|
});
|
|
|
|
if (input.problems) {
|
|
await validateProblems(input.problems);
|
|
await prisma.assignmentProblem.deleteMany({
|
|
where: { assignmentId },
|
|
});
|
|
await prisma.assignmentProblem.createMany({
|
|
data: input.problems.map((item, index) => ({
|
|
assignmentId,
|
|
problemId: item.problemId,
|
|
order: item.order ?? index + 1,
|
|
})),
|
|
skipDuplicates: true,
|
|
});
|
|
}
|
|
|
|
revalidatePath(`/dashboard/teacher/courses/${assignment.courseId}`);
|
|
revalidatePath(
|
|
`/dashboard/teacher/courses/${assignment.courseId}/assignments/${assignmentId}`
|
|
);
|
|
revalidatePath(
|
|
`/dashboard/student/courses/${assignment.courseId}/assignments/${assignmentId}`
|
|
);
|
|
}
|
|
|
|
export async function publishAssignment(
|
|
assignmentId: string,
|
|
published: boolean = true
|
|
) {
|
|
return updateAssignment(assignmentId, { published });
|
|
}
|
|
|
|
export async function getAssignmentDetail(assignmentId: string) {
|
|
const actor = await getAuthenticatedActor();
|
|
assertTeacherOrAdmin(actor);
|
|
|
|
const assignment = await prisma.assignment.findUnique({
|
|
where: { id: assignmentId },
|
|
select: {
|
|
id: true,
|
|
title: true,
|
|
description: true,
|
|
opensAt: true,
|
|
dueAt: true,
|
|
published: true,
|
|
courseId: true,
|
|
course: {
|
|
select: {
|
|
id: true,
|
|
title: true,
|
|
teacherId: true,
|
|
teacher: {
|
|
select: {
|
|
name: true,
|
|
email: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
problems: {
|
|
orderBy: [{ order: "asc" }, { problem: { displayId: "asc" } }],
|
|
select: {
|
|
problemId: true,
|
|
order: true,
|
|
problem: {
|
|
select: {
|
|
displayId: true,
|
|
difficulty: true,
|
|
localizations: {
|
|
where: { type: "TITLE", locale: "zh" },
|
|
select: { content: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
_count: {
|
|
select: {
|
|
submissions: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!assignment) {
|
|
throw new Error("作业不存在");
|
|
}
|
|
|
|
await assertCourseManagePermission(assignment.courseId, actor);
|
|
return assignment;
|
|
}
|
|
|
|
export async function listCourseAssignments(courseId: string) {
|
|
const actor = await getAuthenticatedActor();
|
|
assertTeacherOrAdmin(actor);
|
|
await assertCourseManagePermission(courseId, actor);
|
|
|
|
return prisma.assignment.findMany({
|
|
where: { courseId },
|
|
orderBy: [{ dueAt: "asc" }, { createdAt: "desc" }],
|
|
select: {
|
|
id: true,
|
|
title: true,
|
|
description: true,
|
|
opensAt: true,
|
|
dueAt: true,
|
|
published: true,
|
|
createdAt: true,
|
|
_count: {
|
|
select: {
|
|
problems: true,
|
|
submissions: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function listAssignableProblems() {
|
|
const actor = await getAuthenticatedActor();
|
|
assertTeacherOrAdmin(actor);
|
|
|
|
return prisma.problem.findMany({
|
|
where: { isPublished: true },
|
|
orderBy: { displayId: "asc" },
|
|
select: {
|
|
id: true,
|
|
displayId: true,
|
|
difficulty: true,
|
|
localizations: {
|
|
where: { type: "TITLE", locale: "zh" },
|
|
select: { content: true },
|
|
},
|
|
},
|
|
});
|
|
}
|