homework: - Add data-access-scans, data-access-student, data-access-utils, data-access-exam-cross - Add excellent-submissions, homework-take-confirm-dialog, homework-take-sidebar components classes: - Add class-delete-dialog, class-error-boundary, class-form-dialog, class-form-utils - Add class-list-table, class-list-toolbar, class-skeleton - Add schedule-create-dialog, schedule-delete-dialog, schedule-edit-dialog, schedule-utils - Add data-access-teacher and hooks directory course-plans: - Add course-plan-calendar, sortable-week-row, template-picker-dialog components - Add lib directory
112 lines
3.4 KiB
TypeScript
112 lines
3.4 KiB
TypeScript
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<string | null> {
|
||
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<StudentScheduleItem[]> => {
|
||
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<ClassScheduleItem[]> => {
|
||
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,
|
||
}))
|
||
}
|
||
)
|