import { db } from "@/shared/db" import { exams } 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)], with: { subject: true, gradeEntity: true, } }) // 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: exam.subject?.name ?? getString(meta, "subject") ?? "General", grade: exam.gradeEntity?.name ?? 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: { subject: true, gradeEntity: true, 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: exam.subject?.name ?? getString(meta, "subject") ?? "General", grade: exam.gradeEntity?.name ?? 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 omitScheduledAtFromDescription = (description: string | null): string => { if (!description) return "{}" try { const meta = JSON.parse(description) if (typeof meta === "object" && meta !== null) { const rest = { ...(meta as Record) } delete rest.scheduledAt return JSON.stringify(rest) } return description } catch { return description || "{}" } }