import "server-only" import { cache } from "react" import { and, asc, desc, eq, sql, type SQL } from "drizzle-orm" import { db } from "@/shared/db" import { courseSelections, electiveCourses, } from "@/shared/db/schema" import { getStudentActiveGradeId } from "@/modules/classes/data-access" import { getGradeOptions, getSubjectOptions } from "@/modules/school/data-access" import { getUserNamesByIds } from "@/modules/users/data-access" import type { CourseSelectionWithDetails, ElectiveCourseWithDetails, } from "./types" type CourseCoreRow = typeof electiveCourses.$inferSelect type SelectionCoreRow = { id: string courseId: string studentId: string status: (typeof courseSelections.status.enumValues)[number] priority: number | null selectedAt: Date enrolledAt: Date | null droppedAt: Date | null lotteryRank: number | null createdAt: Date updatedAt: Date courseName: string | null courseCapacity: number | null courseEnrolledCount: number | null courseStatus: (typeof electiveCourses.status.enumValues)[number] | null } const toIso = (d: Date | null | undefined): string | null => d ? d.toISOString() : null const toIsoRequired = (d: Date): string => d.toISOString() const mapCourseRow = ( r: CourseCoreRow, teacherNames: Map, subjectNames: Map, gradeNames: Map ): ElectiveCourseWithDetails => ({ id: r.id, name: r.name, subjectId: r.subjectId, teacherId: r.teacherId, gradeId: r.gradeId, description: r.description, capacity: r.capacity, enrolledCount: r.enrolledCount, classroom: r.classroom, schedule: r.schedule, startDate: r.startDate ? new Date(r.startDate).toISOString().slice(0, 10) : null, endDate: r.endDate ? new Date(r.endDate).toISOString().slice(0, 10) : null, selectionStartAt: toIso(r.selectionStartAt), selectionEndAt: toIso(r.selectionEndAt), status: r.status, selectionMode: r.selectionMode, credit: String(r.credit), createdAt: toIsoRequired(r.createdAt), updatedAt: toIsoRequired(r.updatedAt), teacherName: r.teacherId ? (teacherNames.get(r.teacherId) ?? null) : null, subjectName: r.subjectId ? (subjectNames.get(r.subjectId) ?? null) : null, gradeName: r.gradeId ? (gradeNames.get(r.gradeId) ?? null) : null, }) const mapSelectionRow = ( r: SelectionCoreRow, studentNames: Map ): CourseSelectionWithDetails => ({ id: r.id, courseId: r.courseId, studentId: r.studentId, status: r.status, priority: r.priority, selectedAt: toIsoRequired(r.selectedAt), enrolledAt: toIso(r.enrolledAt), droppedAt: toIso(r.droppedAt), lotteryRank: r.lotteryRank, createdAt: toIsoRequired(r.createdAt), updatedAt: toIsoRequired(r.updatedAt), courseName: r.courseName, studentName: r.studentId ? (studentNames.get(r.studentId) ?? null) : null, courseCapacity: r.courseCapacity, courseEnrolledCount: r.courseEnrolledCount, courseStatus: r.courseStatus, }) const buildCourseCoreSelect = () => db .select({ id: electiveCourses.id, name: electiveCourses.name, subjectId: electiveCourses.subjectId, teacherId: electiveCourses.teacherId, gradeId: electiveCourses.gradeId, description: electiveCourses.description, capacity: electiveCourses.capacity, enrolledCount: electiveCourses.enrolledCount, classroom: electiveCourses.classroom, schedule: electiveCourses.schedule, startDate: electiveCourses.startDate, endDate: electiveCourses.endDate, selectionStartAt: electiveCourses.selectionStartAt, selectionEndAt: electiveCourses.selectionEndAt, status: electiveCourses.status, selectionMode: electiveCourses.selectionMode, credit: electiveCourses.credit, createdAt: electiveCourses.createdAt, updatedAt: electiveCourses.updatedAt, }) .from(electiveCourses) const buildSelectionCoreSelect = () => db .select({ id: courseSelections.id, courseId: courseSelections.courseId, studentId: courseSelections.studentId, status: courseSelections.status, priority: courseSelections.priority, selectedAt: courseSelections.selectedAt, enrolledAt: courseSelections.enrolledAt, droppedAt: courseSelections.droppedAt, lotteryRank: courseSelections.lotteryRank, createdAt: courseSelections.createdAt, updatedAt: courseSelections.updatedAt, courseName: electiveCourses.name, courseCapacity: electiveCourses.capacity, courseEnrolledCount: electiveCourses.enrolledCount, courseStatus: electiveCourses.status, }) .from(courseSelections) .leftJoin(electiveCourses, eq(electiveCourses.id, courseSelections.courseId)) const resolveCourseDisplayNames = async (rows: CourseCoreRow[]): Promise<{ teacherNames: Map subjectNames: Map gradeNames: Map }> => { const teacherIds = Array.from(new Set(rows.map((r) => r.teacherId).filter((v): v is string => typeof v === "string" && v.length > 0))) const [userMap, subjects, grades] = await Promise.all([ getUserNamesByIds(teacherIds), getSubjectOptions(), getGradeOptions(), ]) const teacherNames = new Map() for (const [id, user] of userMap.entries()) { teacherNames.set(id, user.name) } const subjectNames = new Map() for (const s of subjects) subjectNames.set(s.id, s.name) const gradeNames = new Map() for (const g of grades) gradeNames.set(g.id, g.name) return { teacherNames, subjectNames, gradeNames } } const resolveStudentDisplayNames = async (rows: SelectionCoreRow[]): Promise> => { const studentIds = Array.from(new Set(rows.map((r) => r.studentId).filter((v): v is string => typeof v === "string" && v.length > 0))) const userMap = await getUserNamesByIds(studentIds) const studentNames = new Map() for (const [id, user] of userMap.entries()) { studentNames.set(id, user.name) } return studentNames } export const getCourseSelections = cache( async ( courseId: string ): Promise => { const rows = await buildSelectionCoreSelect() .where(eq(courseSelections.courseId, courseId)) .orderBy(asc(courseSelections.priority), asc(courseSelections.selectedAt)) const studentNames = await resolveStudentDisplayNames(rows) return rows.map((r) => mapSelectionRow(r, studentNames)) } ) export const getStudentSelections = cache( async ( studentId: string ): Promise => { const rows = await buildSelectionCoreSelect() .where(eq(courseSelections.studentId, studentId)) .orderBy(desc(courseSelections.selectedAt)) const studentNames = await resolveStudentDisplayNames(rows) return rows.map((r) => mapSelectionRow(r, studentNames)) } ) export const getStudentGradeId = cache(async (studentId: string): Promise => { return getStudentActiveGradeId(studentId) }) export const getAvailableCoursesForStudent = cache( async ( studentId: string, gradeId?: string | null ): Promise => { const resolvedGradeId = gradeId ?? (await getStudentGradeId(studentId)) const conditions: SQL[] = [eq(electiveCourses.status, "open")] if (resolvedGradeId) { conditions.push( sql`(${electiveCourses.gradeId} = ${resolvedGradeId} OR ${electiveCourses.gradeId} IS NULL)` ) } const rows = await buildCourseCoreSelect() .where(and(...conditions)) .orderBy(desc(electiveCourses.createdAt)) const displayMaps = await resolveCourseDisplayNames(rows) return rows.map((r) => mapCourseRow(r, displayMaps.teacherNames, displayMaps.subjectNames, displayMaps.gradeNames)) } )