Files
football-next/app/api/admin/quiz/[id]/route.ts

192 lines
5.9 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/db";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { Prisma } from "@/lib/generated/prisma";
async function requireAdmin() {
const session = await getServerSession(authOptions);
if (!session || (session.user as any).role !== "ADMIN") {
return null;
}
return session;
}
function calculateResult(answers: number[], questions: Array<{ correctAnswer: number }>) {
let correct = 0;
questions.forEach((question, index) => {
if (answers[index] === question.correctAnswer) {
correct += 1;
}
});
return {
correct,
score: questions.length > 0 ? Math.round((correct / questions.length) * 100) : 0,
};
}
function validateTierConfig(input: {
goldWinnersCount: number;
silverWinnersCount: number;
bronzeWinnersCount: number;
goldMinCorrect: number | null;
silverMinCorrect: number | null;
bronzeMinCorrect: number | null;
}) {
if (input.goldWinnersCount < 0 || input.silverWinnersCount < 0 || input.bronzeWinnersCount < 0) {
return "Winner counts cannot be negative";
}
if (input.goldWinnersCount + input.silverWinnersCount + input.bronzeWinnersCount <= 0) {
return "At least one winner must be configured";
}
if (input.goldWinnersCount > 0 && input.goldMinCorrect == null) {
return "Gold minimum correct answers is required";
}
if (input.silverWinnersCount > 0 && input.silverMinCorrect == null) {
return "Silver minimum correct answers is required";
}
if (input.bronzeWinnersCount > 0 && input.bronzeMinCorrect == null) {
return "Bronze minimum correct answers is required";
}
return null;
}
export async function PUT(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
try {
const session = await requireAdmin();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const { id } = await params;
const {
date,
windowStart,
windowEnd,
goldWinnersCount,
silverWinnersCount,
bronzeWinnersCount,
goldMinCorrect,
silverMinCorrect,
bronzeMinCorrect,
questions,
} = await req.json();
const parsedInput = {
goldWinnersCount: Number(goldWinnersCount),
silverWinnersCount: Number(silverWinnersCount),
bronzeWinnersCount: Number(bronzeWinnersCount),
goldMinCorrect: goldMinCorrect == null ? null : Number(goldMinCorrect),
silverMinCorrect: silverMinCorrect == null ? null : Number(silverMinCorrect),
bronzeMinCorrect: bronzeMinCorrect == null ? null : Number(bronzeMinCorrect),
};
const validationError = validateTierConfig(parsedInput);
if (validationError) {
return NextResponse.json({ error: validationError }, { status: 400 });
}
if (!Array.isArray(questions) || questions.length === 0) {
return NextResponse.json({ error: "At least one question is required" }, { status: 400 });
}
const quiz = await db.dailyQuiz.findUnique({
where: { id },
include: {
submissions: {
select: { id: true, answers: true },
},
},
});
if (!quiz) return NextResponse.json({ error: "Quiz not found" }, { status: 404 });
if (quiz.isProcessed) {
return NextResponse.json({ error: "Quiz can no longer be edited after lottery processing" }, { status: 400 });
}
const normalizedQuestions = questions.map((q: any, index: number) => ({
questionText: q.questionText,
options: q.options,
correctAnswer: Number(q.correctAnswer),
order: index,
}));
const updatedQuiz = await db.$transaction(async (tx) => {
await tx.quizQuestion.deleteMany({ where: { quizId: id } });
const updated = await tx.dailyQuiz.update({
where: { id },
data: {
date: new Date(`${date}T00:00:00.000Z`),
windowStart: new Date(windowStart),
windowEnd: new Date(windowEnd),
goldWinnersCount: parsedInput.goldWinnersCount,
silverWinnersCount: parsedInput.silverWinnersCount,
bronzeWinnersCount: parsedInput.bronzeWinnersCount,
goldMinCorrect: parsedInput.goldMinCorrect,
silverMinCorrect: parsedInput.silverMinCorrect,
bronzeMinCorrect: parsedInput.bronzeMinCorrect,
questions: {
create: normalizedQuestions,
},
},
include: {
questions: { orderBy: { order: "asc" } },
},
});
for (const submission of quiz.submissions) {
const result = calculateResult(submission.answers, normalizedQuestions);
await tx.quizSubmission.update({
where: { id: submission.id },
data: {
score: result.score,
correctAnswers: result.correct,
},
});
}
return updated;
});
return NextResponse.json(updatedQuiz);
} catch (error) {
console.error("Failed to update quiz", error);
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === "P2002") {
return NextResponse.json({ error: "Quiz date already exists" }, { status: 409 });
}
}
return NextResponse.json({ error: "Failed to update quiz" }, { status: 500 });
}
}
export async function DELETE(_: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const session = await requireAdmin();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const { id } = await params;
const quiz = await db.dailyQuiz.findUnique({
where: { id },
select: {
id: true,
isProcessed: true,
},
});
if (!quiz) return NextResponse.json({ error: "Quiz not found" }, { status: 404 });
if (quiz.isProcessed) {
return NextResponse.json({ error: "Quiz can no longer be deleted after lottery processing" }, { status: 400 });
}
await db.dailyQuiz.delete({ where: { id } });
return NextResponse.json({ success: true });
}