feat(submission): allow resubmission and add file size limit
This commit is contained in:
@ -1,11 +1,12 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { uploadAssignmentFile } from "@/lib/s3";
|
||||
import { deleteAssignmentFile, uploadAssignmentFile } from "@/lib/s3";
|
||||
import { evaluateAssignment } from "@/lib/grading";
|
||||
import { getNumericParam } from "@/lib/route-params";
|
||||
import { submissionSchema } from "@/lib/validators";
|
||||
|
||||
const ACCEPT_EXTENSIONS = new Set([".pdf"]);
|
||||
const MAX_FILE_SIZE_BYTES = 20 * 1024 * 1024; // 20MB
|
||||
|
||||
type AssignmentRouteParams = { assignmentId: string | string[] | undefined };
|
||||
|
||||
@ -45,17 +46,22 @@ export async function POST(
|
||||
return NextResponse.json({ error: "请上传作业文件" }, { status: 400 });
|
||||
}
|
||||
|
||||
let metadata: unknown = {};
|
||||
if (metadataRaw) {
|
||||
try {
|
||||
metadata = JSON.parse(String(metadataRaw));
|
||||
} catch (error) {
|
||||
console.error("Failed to parse submission metadata", error);
|
||||
return NextResponse.json({ error: "表单信息格式错误" }, { status: 400 });
|
||||
let metadata: Record<string, unknown> & { formPayload?: unknown } = {};
|
||||
try {
|
||||
if (metadataRaw) {
|
||||
const parsedMetadata = JSON.parse(String(metadataRaw));
|
||||
if (parsedMetadata && typeof parsedMetadata === "object" && !Array.isArray(parsedMetadata)) {
|
||||
metadata = parsedMetadata as Record<string, unknown>;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to parse submission metadata", error);
|
||||
return NextResponse.json({ error: "表单信息格式错误" }, { status: 400 });
|
||||
}
|
||||
|
||||
const parsed = submissionSchema.safeParse(metadata ?? {});
|
||||
const parsed = submissionSchema.safeParse({
|
||||
...metadata,
|
||||
});
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json(
|
||||
{ error: parsed.error.flatten() },
|
||||
@ -73,6 +79,10 @@ export async function POST(
|
||||
return NextResponse.json({ error: "作业不存在" }, { status: 404 });
|
||||
}
|
||||
|
||||
if (file.size > MAX_FILE_SIZE_BYTES) {
|
||||
return NextResponse.json({ error: "PDF 文件大小不能超过 20MB" }, { status: 400 });
|
||||
}
|
||||
|
||||
const studentIdRaw = parsed.data.studentId.trim();
|
||||
const studentNameRaw = parsed.data.studentName.trim();
|
||||
|
||||
@ -93,13 +103,6 @@ export async function POST(
|
||||
},
|
||||
});
|
||||
|
||||
if (existingSubmission) {
|
||||
return NextResponse.json(
|
||||
{ error: "该学号已提交,请勿重复提交" },
|
||||
{ status: 409 },
|
||||
);
|
||||
}
|
||||
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
const studentId = studentIdRaw;
|
||||
@ -121,18 +124,35 @@ export async function POST(
|
||||
let autoEvaluated = false;
|
||||
try {
|
||||
const uploadResult = await uploadAssignmentFile(buffer, file.name, assignmentId);
|
||||
const previousFileKey = existingSubmission?.fileKey ?? null;
|
||||
|
||||
const submission = await prisma.submission.create({
|
||||
data: {
|
||||
assignmentId,
|
||||
studentId,
|
||||
studentName,
|
||||
originalFilename: file.name,
|
||||
fileUrl: uploadResult.url,
|
||||
fileKey: uploadResult.key,
|
||||
formPayload: parsed.data.formPayload ?? undefined,
|
||||
},
|
||||
});
|
||||
const submission = existingSubmission
|
||||
? await prisma.submission.update({
|
||||
where: { id: existingSubmission.id },
|
||||
data: {
|
||||
studentId,
|
||||
studentName,
|
||||
originalFilename: file.name,
|
||||
fileUrl: uploadResult.url,
|
||||
fileKey: uploadResult.key,
|
||||
formPayload: parsed.data.formPayload ?? undefined,
|
||||
submittedAt: new Date(),
|
||||
evaluationScore: null,
|
||||
evaluationComment: null,
|
||||
evaluatedAt: null,
|
||||
},
|
||||
})
|
||||
: await prisma.submission.create({
|
||||
data: {
|
||||
assignmentId,
|
||||
studentId,
|
||||
studentName,
|
||||
originalFilename: file.name,
|
||||
fileUrl: uploadResult.url,
|
||||
fileKey: uploadResult.key,
|
||||
formPayload: parsed.data.formPayload ?? undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const shouldAutoEvaluate = Boolean(
|
||||
assignment.autoEvaluate && assignment.gradingCriteria?.trim(),
|
||||
@ -160,6 +180,13 @@ export async function POST(
|
||||
console.error("Auto evaluation failed", error);
|
||||
}
|
||||
}
|
||||
if (previousFileKey) {
|
||||
try {
|
||||
await deleteAssignmentFile(previousFileKey);
|
||||
} catch (error) {
|
||||
console.error("Failed to delete previous submission file", error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to submit assignment", error);
|
||||
return NextResponse.json({ error: "上传失败" }, { status: 500 });
|
||||
|
||||
Reference in New Issue
Block a user