refactor: P0-1/2/4 解耦修复 - 拆分过耦合文件 + dashboard 解耦
This commit is contained in:
230
src/modules/classes/data-access-schedule.ts
Normal file
230
src/modules/classes/data-access-schedule.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
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 {
|
||||
insertClassScheduleItem,
|
||||
updateClassScheduleItemById,
|
||||
deleteClassScheduleItemById,
|
||||
} from "@/modules/scheduling/data-access"
|
||||
import type {
|
||||
ClassScheduleItem,
|
||||
CreateClassScheduleItemInput,
|
||||
StudentScheduleItem,
|
||||
UpdateClassScheduleItemInput,
|
||||
} from "./types"
|
||||
import {
|
||||
getAccessibleClassIdsForTeacher,
|
||||
getSessionTeacherId,
|
||||
getTeacherIdForMutations,
|
||||
} from "./data-access"
|
||||
|
||||
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: r.weekday as StudentScheduleItem["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: r.weekday as ClassScheduleItem["weekday"],
|
||||
startTime: r.startTime,
|
||||
endTime: r.endTime,
|
||||
course: r.course,
|
||||
location: r.location,
|
||||
}))
|
||||
}
|
||||
)
|
||||
|
||||
const isTimeHHMM = (v: string) => /^\d{2}:\d{2}$/.test(v)
|
||||
|
||||
export async function createClassScheduleItem(data: CreateClassScheduleItemInput): Promise<string> {
|
||||
const teacherId = await getTeacherIdForMutations()
|
||||
|
||||
const classId = data.classId.trim()
|
||||
const course = data.course.trim()
|
||||
const startTime = data.startTime.trim()
|
||||
const endTime = data.endTime.trim()
|
||||
const location = data.location?.trim() || null
|
||||
const weekday = data.weekday
|
||||
|
||||
if (!classId) throw new Error("Class is required")
|
||||
if (!course) throw new Error("Course is required")
|
||||
if (!isTimeHHMM(startTime) || !isTimeHHMM(endTime)) throw new Error("Invalid time format")
|
||||
if (startTime >= endTime) throw new Error("Start time must be earlier than end time")
|
||||
if (weekday < 1 || weekday > 7) throw new Error("Invalid weekday")
|
||||
|
||||
const [owned] = await db
|
||||
.select({ id: classes.id })
|
||||
.from(classes)
|
||||
.where(and(eq(classes.id, classId), eq(classes.teacherId, teacherId)))
|
||||
.limit(1)
|
||||
|
||||
if (!owned) throw new Error("Class not found")
|
||||
|
||||
// Delegate DB write to scheduling module (unified write entry point)
|
||||
return insertClassScheduleItem({
|
||||
classId,
|
||||
weekday,
|
||||
startTime,
|
||||
endTime,
|
||||
course,
|
||||
location,
|
||||
})
|
||||
}
|
||||
|
||||
export async function updateClassScheduleItem(scheduleId: string, data: UpdateClassScheduleItemInput): Promise<void> {
|
||||
const teacherId = await getTeacherIdForMutations()
|
||||
const id = scheduleId.trim()
|
||||
if (!id) throw new Error("Missing schedule id")
|
||||
|
||||
const [existing] = await db
|
||||
.select({
|
||||
id: classSchedule.id,
|
||||
classId: classSchedule.classId,
|
||||
startTime: classSchedule.startTime,
|
||||
endTime: classSchedule.endTime,
|
||||
})
|
||||
.from(classSchedule)
|
||||
.innerJoin(classes, eq(classes.id, classSchedule.classId))
|
||||
.where(and(eq(classSchedule.id, id), eq(classes.teacherId, teacherId)))
|
||||
.limit(1)
|
||||
|
||||
if (!existing) throw new Error("Schedule item not found")
|
||||
|
||||
const update: Partial<typeof classSchedule.$inferSelect> = {}
|
||||
|
||||
if (typeof data.classId === "string") {
|
||||
const nextClassId = data.classId.trim()
|
||||
if (!nextClassId) throw new Error("Class is required")
|
||||
|
||||
const [ownedNext] = await db
|
||||
.select({ id: classes.id })
|
||||
.from(classes)
|
||||
.where(and(eq(classes.id, nextClassId), eq(classes.teacherId, teacherId)))
|
||||
.limit(1)
|
||||
|
||||
if (!ownedNext) throw new Error("Class not found")
|
||||
update.classId = nextClassId
|
||||
}
|
||||
|
||||
if (typeof data.weekday === "number") {
|
||||
if (data.weekday < 1 || data.weekday > 7) throw new Error("Invalid weekday")
|
||||
update.weekday = data.weekday
|
||||
}
|
||||
|
||||
if (typeof data.course === "string") {
|
||||
const course = data.course.trim()
|
||||
if (!course) throw new Error("Course is required")
|
||||
update.course = course
|
||||
}
|
||||
|
||||
const nextStart = typeof data.startTime === "string" ? data.startTime.trim() : undefined
|
||||
const nextEnd = typeof data.endTime === "string" ? data.endTime.trim() : undefined
|
||||
if (nextStart !== undefined) {
|
||||
if (!isTimeHHMM(nextStart)) throw new Error("Invalid time format")
|
||||
update.startTime = nextStart
|
||||
}
|
||||
if (nextEnd !== undefined) {
|
||||
if (!isTimeHHMM(nextEnd)) throw new Error("Invalid time format")
|
||||
update.endTime = nextEnd
|
||||
}
|
||||
|
||||
if (update.startTime !== undefined || update.endTime !== undefined) {
|
||||
const mergedStart = update.startTime ?? existing.startTime
|
||||
const mergedEnd = update.endTime ?? existing.endTime
|
||||
if (typeof mergedStart === "string" && typeof mergedEnd === "string" && mergedStart >= mergedEnd) {
|
||||
throw new Error("Start time must be earlier than end time")
|
||||
}
|
||||
}
|
||||
|
||||
if (data.location !== undefined) {
|
||||
update.location = data.location?.trim() || null
|
||||
}
|
||||
|
||||
if (Object.keys(update).length === 0) return
|
||||
|
||||
// Delegate DB write to scheduling module (unified write entry point)
|
||||
await updateClassScheduleItemById(id, update)
|
||||
}
|
||||
|
||||
export async function deleteClassScheduleItem(scheduleId: string): Promise<void> {
|
||||
const teacherId = await getTeacherIdForMutations()
|
||||
const id = scheduleId.trim()
|
||||
if (!id) throw new Error("Missing schedule id")
|
||||
|
||||
const [owned] = await db
|
||||
.select({ id: classSchedule.id })
|
||||
.from(classSchedule)
|
||||
.innerJoin(classes, eq(classes.id, classSchedule.classId))
|
||||
.where(and(eq(classSchedule.id, id), eq(classes.teacherId, teacherId)))
|
||||
.limit(1)
|
||||
|
||||
if (!owned) throw new Error("Schedule item not found")
|
||||
|
||||
// Delegate DB write to scheduling module (unified write entry point)
|
||||
await deleteClassScheduleItemById(id)
|
||||
}
|
||||
Reference in New Issue
Block a user