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