import { db } from "@/shared/db" import { exams, examQuestions, examSubmissions, submissionAnswers } from "@/shared/db/schema" import { eq, desc, like, and, or } from "drizzle-orm" import { cache } from "react" import type { Exam, ExamDifficulty, ExamStatus } from "./types" export type GetExamsParams = { q?: string status?: string difficulty?: string page?: number pageSize?: number } const isRecord = (v: unknown): v is Record => typeof v === "object" && v !== null const parseExamMeta = (description: string | null): Record => { if (!description) return {} try { const parsed: unknown = JSON.parse(description) return isRecord(parsed) ? parsed : {} } catch { return {} } } const getString = (obj: Record, key: string): string | undefined => { const v = obj[key] return typeof v === "string" ? v : undefined } const getNumber = (obj: Record, key: string): number | undefined => { const v = obj[key] return typeof v === "number" ? v : undefined } const getStringArray = (obj: Record, key: string): string[] | undefined => { const v = obj[key] if (!Array.isArray(v)) return undefined const items = v.filter((x): x is string => typeof x === "string") return items.length === v.length ? items : undefined } const toExamDifficulty = (n: number | undefined): ExamDifficulty => { if (n === 1 || n === 2 || n === 3 || n === 4 || n === 5) return n return 1 } export const getExams = cache(async (params: GetExamsParams) => { const conditions = [] if (params.q) { const search = `%${params.q}%` conditions.push(or(like(exams.title, search), like(exams.description, search))) } if (params.status && params.status !== "all") { conditions.push(eq(exams.status, params.status)) } // Note: Difficulty is stored in JSON description field in current schema, // so we might need to filter in memory or adjust schema. // For now, let's fetch and filter in memory if difficulty is needed, // or just ignore strict DB filtering for JSON fields to keep it simple. const data = await db.query.exams.findMany({ where: conditions.length ? and(...conditions) : undefined, orderBy: [desc(exams.createdAt)], }) // Transform and Filter (especially for JSON fields) let result: Exam[] = data.map((exam) => { const meta = parseExamMeta(exam.description || null) return { id: exam.id, title: exam.title, status: (exam.status as ExamStatus) || "draft", subject: getString(meta, "subject") || "General", grade: getString(meta, "grade") || "General", difficulty: toExamDifficulty(getNumber(meta, "difficulty")), totalScore: getNumber(meta, "totalScore") || 100, durationMin: getNumber(meta, "durationMin") || 60, questionCount: getNumber(meta, "questionCount") || 0, scheduledAt: exam.startTime?.toISOString(), createdAt: exam.createdAt.toISOString(), updatedAt: exam.updatedAt?.toISOString(), tags: getStringArray(meta, "tags") || [], } }) if (params.difficulty && params.difficulty !== "all") { const d = parseInt(params.difficulty) result = result.filter((e) => e.difficulty === d) } return result }) export const getExamById = cache(async (id: string) => { const exam = await db.query.exams.findFirst({ where: eq(exams.id, id), with: { questions: { orderBy: (examQuestions, { asc }) => [asc(examQuestions.order)], with: { question: true } } } }) if (!exam) return null const meta = parseExamMeta(exam.description || null) return { id: exam.id, title: exam.title, status: (exam.status as ExamStatus) || "draft", subject: getString(meta, "subject") || "General", grade: getString(meta, "grade") || "General", difficulty: toExamDifficulty(getNumber(meta, "difficulty")), totalScore: getNumber(meta, "totalScore") || 100, durationMin: getNumber(meta, "durationMin") || 60, scheduledAt: exam.startTime?.toISOString(), createdAt: exam.createdAt.toISOString(), updatedAt: exam.updatedAt?.toISOString(), tags: getStringArray(meta, "tags") || [], structure: exam.structure as unknown, questions: exam.questions.map((eqRel) => ({ id: eqRel.questionId, score: eqRel.score ?? 0, order: eqRel.order ?? 0, })), } }) export const getExamSubmissions = cache(async () => { const data = await db.query.examSubmissions.findMany({ orderBy: [desc(examSubmissions.submittedAt)], with: { exam: true, student: true } }) return data.map(sub => ({ id: sub.id, examId: sub.examId, examTitle: sub.exam.title, studentName: sub.student.name || "Unknown", submittedAt: sub.submittedAt ? sub.submittedAt.toISOString() : new Date().toISOString(), score: sub.score || undefined, status: sub.status as "pending" | "graded", })) }) export const getSubmissionDetails = cache(async (submissionId: string) => { const submission = await db.query.examSubmissions.findFirst({ where: eq(examSubmissions.id, submissionId), with: { student: true, exam: true, } }) if (!submission) return null // Fetch answers const answers = await db.query.submissionAnswers.findMany({ where: eq(submissionAnswers.submissionId, submissionId), with: { question: true } }) // Fetch exam questions structure (to know max score and order) const examQ = await db.query.examQuestions.findMany({ where: eq(examQuestions.examId, submission.examId), orderBy: [desc(examQuestions.order)], }) type QuestionContent = { text?: string } & Record const toQuestionContent = (v: unknown): QuestionContent | null => { if (!isRecord(v)) return null return v as QuestionContent } // Map answers with question details const answersWithDetails = answers.map(ans => { const eqRel = examQ.find(q => q.questionId === ans.questionId) return { id: ans.id, questionId: ans.questionId, questionContent: toQuestionContent(ans.question.content), questionType: ans.question.type, maxScore: eqRel?.score || 0, studentAnswer: ans.answerContent, score: ans.score, feedback: ans.feedback, order: eqRel?.order || 0 } }).sort((a, b) => a.order - b.order) return { id: submission.id, studentName: submission.student.name || "Unknown", examTitle: submission.exam.title, submittedAt: submission.submittedAt ? submission.submittedAt.toISOString() : null, status: submission.status, totalScore: submission.score, answers: answersWithDetails } })