import "server-only"; import { cache } from "react" import { and, asc, eq, inArray, type SQL } from "drizzle-orm" import { db } from "@/shared/db" import { classes, classEnrollments, classSchedule, } from "@/shared/db/schema" import type { ClassScheduleItem, StudentScheduleItem, } from "./types" import { getAccessibleClassIdsForTeacher, getSessionTeacherId, } from "./data-access" /** * 根据课表项 ID 获取其所属班级 ID(P0-3 审计修复:供 actions-schedule 越权校验使用)。 * classes 模块对 classSchedule 表有读权限(usedBy 含 classes)。 */ export async function getClassIdByScheduleId(scheduleId: string): Promise { const [row] = await db .select({ classId: classSchedule.classId }) .from(classSchedule) .where(eq(classSchedule.id, scheduleId)) .limit(1) return row?.classId ?? null } const isWeekday = (n: unknown): n is 1 | 2 | 3 | 4 | 5 | 6 | 7 => typeof n === "number" && n >= 1 && n <= 7 && Number.isInteger(n) const toWeekday = (n: number): 1 | 2 | 3 | 4 | 5 | 6 | 7 => isWeekday(n) ? n : 1 export const getStudentSchedule = cache(async (studentId: string): Promise => { const id = studentId.trim() if (!id) return [] const rows = await db .select({ id: classSchedule.id, classId: classSchedule.classId, className: classes.name, weekday: classSchedule.weekday, startTime: classSchedule.startTime, endTime: classSchedule.endTime, course: classSchedule.course, location: classSchedule.location, }) .from(classEnrollments) .innerJoin(classes, eq(classes.id, classEnrollments.classId)) .innerJoin(classSchedule, eq(classSchedule.classId, classes.id)) .where(and(eq(classEnrollments.studentId, id), eq(classEnrollments.status, "active"))) .orderBy(asc(classSchedule.weekday), asc(classSchedule.startTime)) return rows.map((r) => ({ id: r.id, classId: r.classId, className: r.className, weekday: toWeekday(r.weekday), startTime: r.startTime, endTime: r.endTime, course: r.course, location: r.location, })) }) export const getClassSchedule = cache( async (params?: { classId?: string; teacherId?: string }): Promise => { const teacherId = params?.teacherId ?? (await getSessionTeacherId()) if (!teacherId) return [] const classId = params?.classId?.trim() const accessibleIds = await getAccessibleClassIdsForTeacher(teacherId) if (accessibleIds.length === 0) return [] const conditions: SQL[] = [inArray(classes.id, accessibleIds)] if (classId) conditions.push(eq(classSchedule.classId, classId)) const rows = await db .select({ id: classSchedule.id, classId: classSchedule.classId, weekday: classSchedule.weekday, startTime: classSchedule.startTime, endTime: classSchedule.endTime, course: classSchedule.course, location: classSchedule.location, }) .from(classSchedule) .innerJoin(classes, eq(classes.id, classSchedule.classId)) .where(and(...conditions)) .orderBy(asc(classSchedule.weekday), asc(classSchedule.startTime)) return rows.map((r) => ({ id: r.id, classId: r.classId, weekday: toWeekday(r.weekday), startTime: r.startTime, endTime: r.endTime, course: r.course, location: r.location, })) } )