2026-04-05 00:55:53 -04:00

201 lines
4.7 KiB
TypeScript

import { NextResponse } from "next/server";
import { db } from "@/db";
import { quizSessions } from "@/db/schema";
import { eq, and } from "drizzle-orm";
import { getSession } from "@/lib/auth";
import {
drawQuizQuestions,
QUIZ_QUESTIONS,
type QuizQuestion,
} from "@/lib/quiz-questions";
const QUIZ_SESSION_EXPIRY_MS = 2 * 60 * 60 * 1000; // 2 hours (for in-progress quiz attempts only)
interface StoredAnswer {
questionId: string;
answer: string;
correct: boolean;
}
export async function GET() {
const session = await getSession();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// Quiz only needs to be passed once — no time-based expiry
const [passedQuiz] = await db
.select()
.from(quizSessions)
.where(
and(
eq(quizSessions.annotatorId, session.annotatorId),
eq(quizSessions.passed, true),
),
)
.orderBy(quizSessions.startedAt)
.limit(1);
if (passedQuiz) {
return NextResponse.json({
hasPassedQuiz: true,
quizSessionId: passedQuiz.id,
});
}
return NextResponse.json({ hasPassedQuiz: false });
}
export async function POST(request: Request) {
const session = await getSession();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await request.json();
const { action } = body as { action: string };
if (action === "start") {
return handleStart(session.annotatorId);
}
if (action === "answer") {
const { quizSessionId, questionId, answer } = body as {
quizSessionId: string;
questionId: string;
answer: string;
};
if (!quizSessionId || !questionId || !answer) {
return NextResponse.json(
{ error: "Missing required fields" },
{ status: 400 },
);
}
return handleAnswer(
session.annotatorId,
quizSessionId,
questionId,
answer,
);
}
return NextResponse.json({ error: "Invalid action" }, { status: 400 });
}
async function handleStart(annotatorId: string) {
const questions = drawQuizQuestions(8);
const quizSessionId = crypto.randomUUID();
await db.insert(quizSessions).values({
id: quizSessionId,
annotatorId,
startedAt: new Date(),
totalQuestions: 8,
answers: "[]",
});
return NextResponse.json({ quizSessionId, questions });
}
async function handleAnswer(
annotatorId: string,
quizSessionId: string,
questionId: string,
answer: string,
) {
const [quizSession] = await db
.select()
.from(quizSessions)
.where(eq(quizSessions.id, quizSessionId))
.limit(1);
if (!quizSession) {
return NextResponse.json(
{ error: "Quiz session not found" },
{ status: 404 },
);
}
if (quizSession.annotatorId !== annotatorId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
}
if (quizSession.completedAt) {
return NextResponse.json(
{ error: "Quiz already completed" },
{ status: 400 },
);
}
const elapsed = Date.now() - quizSession.startedAt.getTime();
if (elapsed > QUIZ_SESSION_EXPIRY_MS) {
return NextResponse.json({ error: "Quiz session expired" }, { status: 400 });
}
const question = QUIZ_QUESTIONS.find(
(q: QuizQuestion) => q.id === questionId,
);
if (!question) {
return NextResponse.json({ error: "Question not found" }, { status: 404 });
}
const existingAnswers: StoredAnswer[] = JSON.parse(quizSession.answers);
const alreadyAnswered = existingAnswers.some(
(a) => a.questionId === questionId,
);
if (alreadyAnswered) {
return NextResponse.json(
{ error: "Question already answered" },
{ status: 400 },
);
}
const correct = answer === question.correctAnswer;
const updatedAnswers: StoredAnswer[] = [
...existingAnswers,
{ questionId, answer, correct },
];
const allAnswered = updatedAnswers.length >= quizSession.totalQuestions;
if (allAnswered) {
const score = updatedAnswers.filter((a) => a.correct).length;
const passed = score >= 7;
await db
.update(quizSessions)
.set({
answers: JSON.stringify(updatedAnswers),
completedAt: new Date(),
score,
passed,
})
.where(eq(quizSessions.id, quizSessionId));
return NextResponse.json({
correct,
correctAnswer: question.correctAnswer,
explanation: question.explanation,
score,
passed,
completed: true,
});
}
await db
.update(quizSessions)
.set({ answers: JSON.stringify(updatedAnswers) })
.where(eq(quizSessions.id, quizSessionId));
return NextResponse.json({
correct,
correctAnswer: question.correctAnswer,
explanation: question.explanation,
completed: false,
});
}