// Moved from src/app/api/proctoring/event/route.ts // P0-6 fix: duplicate event reporting channel removed. // The canonical path is the Server Action `recordProctoringEventAction` // in src/modules/proctoring/actions.ts. This REST route was dead code // (no client referenced /api/proctoring/event) and duplicated the // Server Action's submission-ownership check + recordProctoringEvent call. import { NextResponse } from "next/server" import { z } from "zod" import { requireAuth, PermissionDeniedError } from "@/shared/lib/auth-guard" import { db } from "@/shared/db" import { examSubmissions } from "@/shared/db/schema" import { and, eq } from "drizzle-orm" import { recordProctoringEvent } from "@/modules/proctoring/data-access" import type { ProctoringEventType } from "@/modules/proctoring/types" export const dynamic = "force-dynamic" const EventSchema = z.object({ submissionId: z.string().min(1), eventType: z.enum([ "tab_switch", "window_blur", "copy_attempt", "paste_attempt", "right_click", "devtools_open", "fullscreen_exit", "idle_timeout", ]) as z.ZodType, eventDetail: z.string().optional(), }) export async function POST(req: Request) { try { const ctx = await requireAuth() const body = await req.json().catch(() => null) if (!body) { return NextResponse.json( { success: false, message: "Invalid JSON body" }, { status: 400 }, ) } const parsed = EventSchema.safeParse(body) if (!parsed.success) { return NextResponse.json( { success: false, message: parsed.error.issues[0]?.message ?? "Invalid payload", }, { status: 400 }, ) } // 安全校验:submission 必须属于当前学生 const submission = await db.query.examSubmissions.findFirst({ where: and( eq(examSubmissions.id, parsed.data.submissionId), eq(examSubmissions.studentId, ctx.userId), ), columns: { id: true, examId: true, }, }) if (!submission) { return NextResponse.json( { success: false, message: "Submission not found for current user" }, { status: 404 }, ) } await recordProctoringEvent({ submissionId: parsed.data.submissionId, studentId: ctx.userId, examId: submission.examId, eventType: parsed.data.eventType, eventDetail: parsed.data.eventDetail, }) return NextResponse.json({ success: true }) } catch (error) { if (error instanceof PermissionDeniedError) { return NextResponse.json( { success: false, message: error.message }, { status: 401 }, ) } console.error("POST /api/proctoring/event error:", error) return NextResponse.json( { success: false, message: "Failed to record proctoring event" }, { status: 500 }, ) } }