import "server-only" import { cache } from "react" import { createId } from "@paralleldrive/cuid2" import { and, asc, desc, eq, inArray, sql, type SQL } from "drizzle-orm" import { db } from "@/shared/db" import { electiveCourses, grades, subjects, users, } from "@/shared/db/schema" import type { DataScope } from "@/shared/types/permissions" import type { ElectiveCourseWithDetails, GetElectiveCoursesParams, } from "./types" import type { CreateElectiveCourseInput, UpdateElectiveCourseInput, } from "./schema" const toIso = (d: Date | null | undefined): string | null => d ? d.toISOString() : null const toIsoRequired = (d: Date): string => d.toISOString() const buildScopeFilter = (scope: DataScope, userId?: string): SQL | null => { if (scope.type === "all") return null if (scope.type === "owned" && userId) return eq(electiveCourses.teacherId, userId) if (scope.type === "class_taught" && userId) { return eq(electiveCourses.teacherId, userId) } if (scope.type === "grade_managed") { return scope.gradeIds.length > 0 ? inArray(electiveCourses.gradeId, scope.gradeIds) : sql`1=0` } if (scope.type === "class_members") return null if (scope.type === "children") return null return sql`1=0` } const mapCourseRow = ( r: typeof electiveCourses.$inferSelect & { teacherName: string | null subjectName: string | null gradeName: string | null } ): 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.teacherName, subjectName: r.subjectName, gradeName: r.gradeName, }) const buildCourseSelect = () => 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, teacherName: users.name, subjectName: subjects.name, gradeName: grades.name, }) .from(electiveCourses) .leftJoin(users, eq(users.id, electiveCourses.teacherId)) .leftJoin(subjects, eq(subjects.id, electiveCourses.subjectId)) .leftJoin(grades, eq(grades.id, electiveCourses.gradeId)) export const getElectiveCourses = cache( async ( params?: GetElectiveCoursesParams & { scope?: DataScope; currentUserId?: string } ): Promise => { try { const conditions: SQL[] = [] if (params?.status) conditions.push( eq(electiveCourses.status, params.status) ) if (params?.gradeId) conditions.push(eq(electiveCourses.gradeId, params.gradeId)) if (params?.subjectId) conditions.push(eq(electiveCourses.subjectId, params.subjectId)) if (params?.teacherId) conditions.push(eq(electiveCourses.teacherId, params.teacherId)) if (params?.scope) { const scopeFilter = buildScopeFilter(params.scope, params.currentUserId) if (scopeFilter) conditions.push(scopeFilter) } const query = buildCourseSelect() const rows = await (conditions.length > 0 ? query.where(and(...conditions)) : query ).orderBy(desc(electiveCourses.createdAt)) return rows.map(mapCourseRow) } catch (error) { console.error("getElectiveCourses failed:", error) return [] } } ) export const getElectiveCourseById = cache( async (id: string): Promise => { try { const [row] = await buildCourseSelect() .where(eq(electiveCourses.id, id)) .limit(1) if (!row) return null return mapCourseRow(row) } catch (error) { console.error("getElectiveCourseById failed:", error) return null } } ) export async function createElectiveCourse( data: CreateElectiveCourseInput, teacherId: string ): Promise { const id = createId() await db.insert(electiveCourses).values({ id, name: data.name, subjectId: data.subjectId, teacherId: data.teacherId ?? teacherId, gradeId: data.gradeId, description: data.description, capacity: data.capacity, enrolledCount: 0, classroom: data.classroom, schedule: data.schedule, startDate: data.startDate ? new Date(data.startDate) : null, endDate: data.endDate ? new Date(data.endDate) : null, selectionStartAt: data.selectionStartAt ? new Date(data.selectionStartAt) : null, selectionEndAt: data.selectionEndAt ? new Date(data.selectionEndAt) : null, status: "draft", selectionMode: data.selectionMode, credit: data.credit, }) return id } export async function updateElectiveCourse( id: string, data: Partial ): Promise { const update: Partial = {} if (data.name !== undefined) update.name = data.name if (data.subjectId !== undefined) update.subjectId = data.subjectId if (data.teacherId !== undefined) update.teacherId = data.teacherId if (data.gradeId !== undefined) update.gradeId = data.gradeId if (data.description !== undefined) update.description = data.description if (data.capacity !== undefined) update.capacity = data.capacity if (data.classroom !== undefined) update.classroom = data.classroom if (data.schedule !== undefined) update.schedule = data.schedule if (data.startDate !== undefined) update.startDate = data.startDate ? new Date(data.startDate) : null if (data.endDate !== undefined) update.endDate = data.endDate ? new Date(data.endDate) : null if (data.selectionStartAt !== undefined) update.selectionStartAt = data.selectionStartAt ? new Date(data.selectionStartAt) : null if (data.selectionEndAt !== undefined) update.selectionEndAt = data.selectionEndAt ? new Date(data.selectionEndAt) : null if (data.status !== undefined) update.status = data.status if (data.selectionMode !== undefined) update.selectionMode = data.selectionMode if (data.credit !== undefined) update.credit = data.credit if (Object.keys(update).length === 0) return await db.update(electiveCourses).set(update).where(eq(electiveCourses.id, id)) } export async function deleteElectiveCourse(id: string): Promise { await db.delete(electiveCourses).where(eq(electiveCourses.id, id)) } export async function openSelection(courseId: string): Promise { await db .update(electiveCourses) .set({ status: "open", updatedAt: new Date() }) .where(eq(electiveCourses.id, courseId)) } export async function closeSelection(courseId: string): Promise { await db .update(electiveCourses) .set({ status: "closed", updatedAt: new Date() }) .where(eq(electiveCourses.id, courseId)) } export async function getSubjectOptions(): Promise<{ id: string; name: string }[]> { try { const rows = await db .select({ id: subjects.id, name: subjects.name }) .from(subjects) .orderBy(asc(subjects.order), asc(subjects.name)) return rows.map((r) => ({ id: r.id, name: r.name })) } catch (error) { console.error("getSubjectOptions failed:", error) return [] } } export type { ElectiveCourseWithDetails }