refactor(classes): data-access 迁移至 cacheFn + 双导出 raw 版本

This commit is contained in:
SpecialX
2026-07-05 18:27:32 +08:00
parent dd7a49504f
commit 220d702b44
5 changed files with 116 additions and 35 deletions

View File

@@ -1,6 +1,6 @@
import "server-only";
import { cache } from "react"
import { cacheFn } from "@/shared/lib/cache"
import { and, asc, eq, inArray, or, sql } from "drizzle-orm"
import { createId } from "@paralleldrive/cuid2"
@@ -39,7 +39,7 @@ const isClassSubject = (v: unknown): v is ClassSubject =>
const toClassSubject = (v: string): ClassSubject | null =>
isClassSubject(v) ? v : null
export const getAdminClasses = cache(async (): Promise<AdminClassListItem[]> => {
export const getAdminClassesRaw = async (): Promise<AdminClassListItem[]> => {
const [rows, subjectRows] = await Promise.all([
(async () => {
try {
@@ -183,9 +183,14 @@ export const getAdminClasses = cache(async (): Promise<AdminClassListItem[]> =>
list.sort(compareClassLike)
return list
}
export const getAdminClasses = cacheFn(getAdminClassesRaw, {
tags: ["classes", "classes:list"],
ttl: 300,
keyParts: ["classes", "admin", "list"],
})
export const getGradeManagedClasses = cache(async (userId: string): Promise<AdminClassListItem[]> => {
export const getGradeManagedClassesRaw = async (userId: string): Promise<AdminClassListItem[]> => {
const managedGradeIds = await db
.select({ id: grades.id })
.from(grades)
@@ -308,9 +313,14 @@ export const getGradeManagedClasses = cache(async (userId: string): Promise<Admi
list.sort(compareClassLike)
return list
}
export const getGradeManagedClasses = cacheFn(getGradeManagedClassesRaw, {
tags: ["classes"],
ttl: 300,
keyParts: ["classes", "admin", "getGradeManagedClasses"],
})
export const getManagedGrades = cache(async (userId: string): Promise<{ id: string; name: string; schoolId: string; schoolName: string }[]> => {
export const getManagedGradesRaw = async (userId: string): Promise<{ id: string; name: string; schoolId: string; schoolName: string }[]> => {
return await db
.select({
id: grades.id,
@@ -322,6 +332,11 @@ export const getManagedGrades = cache(async (userId: string): Promise<{ id: stri
.innerJoin(schools, eq(schools.id, grades.schoolId))
.where(or(eq(grades.gradeHeadId, userId), eq(grades.teachingHeadId, userId)))
.orderBy(asc(schools.name), asc(grades.name))
}
export const getManagedGrades = cacheFn(getManagedGradesRaw, {
tags: ["classes"],
ttl: 300,
keyParts: ["classes", "admin", "getManagedGrades"],
})
export async function createAdminClass(data: CreateTeacherClassInput & { teacherId: string }): Promise<string> {

View File

@@ -1,6 +1,6 @@
import "server-only";
import { cache } from "react"
import { cacheFn } from "@/shared/lib/cache"
import { and, asc, eq, inArray, type SQL } from "drizzle-orm"
import { db } from "@/shared/db"
@@ -37,7 +37,7 @@ const isWeekday = (n: unknown): n is 1 | 2 | 3 | 4 | 5 | 6 | 7 =>
const toWeekday = (n: number): 1 | 2 | 3 | 4 | 5 | 6 | 7 =>
isWeekday(n) ? n : 1
export const getStudentSchedule = cache(async (studentId: string): Promise<StudentScheduleItem[]> => {
export const getStudentScheduleRaw = async (studentId: string): Promise<StudentScheduleItem[]> => {
const id = studentId.trim()
if (!id) return []
@@ -68,10 +68,16 @@ export const getStudentSchedule = cache(async (studentId: string): Promise<Stude
course: r.course,
location: r.location,
}))
}
export const getStudentSchedule = cacheFn(getStudentScheduleRaw, {
tags: ["classes:schedule"],
ttl: 600,
keyParts: ["classes", "schedule", "getStudentSchedule"],
})
export const getClassSchedule = cache(
async (params?: { classId?: string; teacherId?: string }): Promise<ClassScheduleItem[]> => {
export const getClassScheduleRaw = async (
params?: { classId?: string; teacherId?: string }
): Promise<ClassScheduleItem[]> => {
const teacherId = params?.teacherId ?? (await getSessionTeacherId())
if (!teacherId) return []
@@ -108,4 +114,8 @@ export const getClassSchedule = cache(
location: r.location,
}))
}
)
export const getClassSchedule = cacheFn(getClassScheduleRaw, {
tags: ["classes:schedule:{classId}"],
ttl: 600,
keyParts: ["classes", "schedule", "by-class"],
})

View File

@@ -1,6 +1,6 @@
import "server-only";
import { cache } from "react"
import { cacheFn } from "@/shared/lib/cache"
import { and, asc, count, eq, inArray } from "drizzle-orm"
import { db } from "@/shared/db"
@@ -123,8 +123,9 @@ const computeAssignmentStats = (params: {
return { stats, allScored }
}
export const getClassHomeworkInsights = cache(
async (params: { classId: string; teacherId?: string; limit?: number }): Promise<ClassHomeworkInsights | null> => {
export const getClassHomeworkInsightsRaw = async (
params: { classId: string; teacherId?: string; limit?: number }
): Promise<ClassHomeworkInsights | null> => {
const teacherId = params.teacherId ?? (await getSessionTeacherId())
if (!teacherId) return null
@@ -274,7 +275,11 @@ export const getClassHomeworkInsights = cache(
overallScores,
}
}
)
export const getClassHomeworkInsights = cacheFn(getClassHomeworkInsightsRaw, {
tags: ["classes:stats:{classId}"],
ttl: 60,
keyParts: ["classes", "stats", "by-class"],
})
const avg = (values: number[]): number | null => {
if (values.length === 0) return null
@@ -282,8 +287,9 @@ const avg = (values: number[]): number | null => {
return sum / values.length
}
export const getGradeHomeworkInsights = cache(
async (params: { gradeId: string; limit?: number }): Promise<GradeHomeworkInsights | null> => {
export const getGradeHomeworkInsightsRaw = async (
params: { gradeId: string; limit?: number }
): Promise<GradeHomeworkInsights | null> => {
const gradeId = params.gradeId.trim()
if (!gradeId) return null
@@ -501,13 +507,22 @@ export const getGradeHomeworkInsights = cache(
classes: classSummaries,
}
}
)
export const getGradeHomeworkInsights = cacheFn(getGradeHomeworkInsightsRaw, {
tags: ["classes:stats"],
ttl: 60,
keyParts: ["classes", "stats", "getGradeHomeworkInsights"],
})
export type ClassesDashboardStats = {
classCount: number
}
export const getClassesDashboardStats = cache(async (): Promise<ClassesDashboardStats> => {
export const getClassesDashboardStatsRaw = async (): Promise<ClassesDashboardStats> => {
const [row] = await db.select({ value: count() }).from(classes)
return { classCount: Number(row?.value ?? 0) }
}
export const getClassesDashboardStats = cacheFn(getClassesDashboardStatsRaw, {
tags: ["classes:stats"],
ttl: 60,
keyParts: ["classes", "stats", "getClassesDashboardStats"],
})

View File

@@ -1,6 +1,6 @@
import "server-only";
import { cache } from "react"
import { cacheFn } from "@/shared/lib/cache"
import { and, asc, eq, inArray, sql, type SQL } from "drizzle-orm"
import { db } from "@/shared/db"
@@ -26,8 +26,9 @@ import {
getTeacherSubjectIdsForClass,
} from "./data-access"
export const getStudentsSubjectScores = cache(
async (studentIds: string[]): Promise<Map<string, Record<string, number | null>>> => {
export const getStudentsSubjectScoresRaw = async (
studentIds: string[]
): Promise<Map<string, Record<string, number | null>>> => {
if (studentIds.length === 0) return new Map()
// 1. Find assignments targeted at these students (via homework module data-access)
@@ -81,10 +82,15 @@ export const getStudentsSubjectScores = cache(
return studentScores
}
)
export const getStudentsSubjectScores = cacheFn(getStudentsSubjectScoresRaw, {
tags: ["classes:students"],
ttl: 60,
keyParts: ["classes", "students", "getStudentsSubjectScores"],
})
export const getClassStudentSubjectScoresV2 = cache(
async (params: { classId: string; teacherId?: string }): Promise<Map<string, Record<string, number | null>>> => {
export const getClassStudentSubjectScoresV2Raw = async (
params: { classId: string; teacherId?: string }
): Promise<Map<string, Record<string, number | null>>> => {
const teacherId = params.teacherId ?? (await getSessionTeacherId())
if (!teacherId) return new Map()
const classId = params.classId.trim()
@@ -132,9 +138,13 @@ export const getClassStudentSubjectScoresV2 = cache(
}
return filtered
}
)
export const getClassStudentSubjectScoresV2 = cacheFn(getClassStudentSubjectScoresV2Raw, {
tags: ["classes:students:{classId}"],
ttl: 60,
keyParts: ["classes", "students", "by-class"],
})
export const getStudentClasses = cache(async (studentId: string): Promise<StudentEnrolledClass[]> => {
export const getStudentClassesRaw = async (studentId: string): Promise<StudentEnrolledClass[]> => {
const id = studentId.trim()
if (!id) return []
@@ -190,14 +200,21 @@ export const getStudentClasses = cache(async (studentId: string): Promise<Studen
list.sort(compareClassLike)
return list
}
export const getStudentClasses = cacheFn(getStudentClassesRaw, {
tags: ["classes:students"],
ttl: 60,
keyParts: ["classes", "students", "getStudentClasses"],
})
/**
* Get a single class details for a student (verifies enrollment).
* Returns null if the student is not enrolled in the class.
*/
export const getStudentClassById = cache(
async (studentId: string, classId: string): Promise<StudentEnrolledClass | null> => {
export const getStudentClassByIdRaw = async (
studentId: string,
classId: string
): Promise<StudentEnrolledClass | null> => {
const sid = studentId.trim()
const cid = classId.trim()
if (!sid || !cid) return null
@@ -232,10 +249,15 @@ export const getStudentClassById = cache(
teacherEmail: r.teacherEmail,
}
}
)
export const getStudentClassById = cacheFn(getStudentClassByIdRaw, {
tags: ["classes:students:{classId}"],
ttl: 60,
keyParts: ["classes", "students", "by-class"],
})
export const getClassStudents = cache(
async (params?: { classId?: string; q?: string; status?: string; teacherId?: string }): Promise<ClassStudent[]> => {
export const getClassStudentsRaw = async (
params?: { classId?: string; q?: string; status?: string; teacherId?: string }
): Promise<ClassStudent[]> => {
const teacherId = params?.teacherId ?? (await getSessionTeacherId())
if (!teacherId) return []
@@ -293,7 +315,11 @@ export const getClassStudents = cache(
joinedAt: r.joinedAt,
}))
}
)
export const getClassStudents = cacheFn(getClassStudentsRaw, {
tags: ["classes:students:{classId}"],
ttl: 60,
keyParts: ["classes", "students", "by-class"],
})
// ---------------------------------------------------------------------------
// DataScope resolver helpers (P1-5/P1-6 audit fix)

View File

@@ -1,6 +1,6 @@
import "server-only";
import { cache } from "react"
import { cacheFn } from "@/shared/lib/cache"
import { and, asc, eq, inArray, isNull, sql } from "drizzle-orm"
import { createId } from "@paralleldrive/cuid2"
@@ -43,7 +43,7 @@ const isClassSubject = (v: unknown): v is ClassSubject =>
const toClassSubject = (v: string): ClassSubject | null =>
isClassSubject(v) ? v : null
export const getTeacherClasses = cache(async (params?: { teacherId?: string }): Promise<TeacherClass[]> => {
export const getTeacherClassesRaw = async (params?: { teacherId?: string }): Promise<TeacherClass[]> => {
const teacherId = params?.teacherId ?? (await getSessionTeacherId())
if (!teacherId) return []
@@ -116,9 +116,14 @@ export const getTeacherClasses = cache(async (params?: { teacherId?: string }):
)
return listWithTrends
}
export const getTeacherClasses = cacheFn(getTeacherClassesRaw, {
tags: ["classes", "classes:list"],
ttl: 300,
keyParts: ["classes", "teacher", "list"],
})
export const getTeacherOptions = cache(async (): Promise<TeacherOption[]> => {
export const getTeacherOptionsRaw = async (): Promise<TeacherOption[]> => {
const rows = await db
.select({ id: users.id, name: users.name, email: users.email })
.from(users)
@@ -132,9 +137,14 @@ export const getTeacherOptions = cache(async (): Promise<TeacherOption[]> => {
name: r.name ?? "Unnamed",
email: r.email,
}))
}
export const getTeacherOptions = cacheFn(getTeacherOptionsRaw, {
tags: ["classes"],
ttl: 300,
keyParts: ["classes", "teacher", "getTeacherOptions"],
})
export const getTeacherTeachingSubjects = cache(async (): Promise<ClassSubject[]> => {
export const getTeacherTeachingSubjectsRaw = async (): Promise<ClassSubject[]> => {
const teacherId = await getSessionTeacherId()
if (!teacherId) return []
@@ -149,6 +159,11 @@ export const getTeacherTeachingSubjects = cache(async (): Promise<ClassSubject[]
return rows
.map((r) => toClassSubject(r.subject))
.filter((s): s is ClassSubject => s !== null)
}
export const getTeacherTeachingSubjects = cacheFn(getTeacherTeachingSubjectsRaw, {
tags: ["classes"],
ttl: 300,
keyParts: ["classes", "teacher", "getTeacherTeachingSubjects"],
})
export async function createTeacherClass(data: CreateTeacherClassInput): Promise<string> {