diff --git a/src/modules/classes/data-access-admin.ts b/src/modules/classes/data-access-admin.ts index 556a628..19ca236 100644 --- a/src/modules/classes/data-access-admin.ts +++ b/src/modules/classes/data-access-admin.ts @@ -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 => { +export const getAdminClassesRaw = async (): Promise => { const [rows, subjectRows] = await Promise.all([ (async () => { try { @@ -183,9 +183,14 @@ export const getAdminClasses = cache(async (): Promise => 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 => { +export const getGradeManagedClassesRaw = async (userId: string): Promise => { const managedGradeIds = await db .select({ id: grades.id }) .from(grades) @@ -308,9 +313,14 @@ export const getGradeManagedClasses = cache(async (userId: string): Promise => { +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 { diff --git a/src/modules/classes/data-access-schedule.ts b/src/modules/classes/data-access-schedule.ts index ced2eae..a43101e 100644 --- a/src/modules/classes/data-access-schedule.ts +++ b/src/modules/classes/data-access-schedule.ts @@ -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 => { +export const getStudentScheduleRaw = async (studentId: string): Promise => { const id = studentId.trim() if (!id) return [] @@ -68,10 +68,16 @@ export const getStudentSchedule = cache(async (studentId: string): Promise => { +export const getClassScheduleRaw = async ( + params?: { classId?: string; teacherId?: string } +): Promise => { 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"], +}) diff --git a/src/modules/classes/data-access-stats.ts b/src/modules/classes/data-access-stats.ts index 56f5b52..ae905b0 100644 --- a/src/modules/classes/data-access-stats.ts +++ b/src/modules/classes/data-access-stats.ts @@ -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 => { +export const getClassHomeworkInsightsRaw = async ( + params: { classId: string; teacherId?: string; limit?: number } +): Promise => { 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 => { +export const getGradeHomeworkInsightsRaw = async ( + params: { gradeId: string; limit?: number } +): Promise => { 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 => { +export const getClassesDashboardStatsRaw = async (): Promise => { 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"], }) diff --git a/src/modules/classes/data-access-students.ts b/src/modules/classes/data-access-students.ts index 6ac0430..aac13c5 100644 --- a/src/modules/classes/data-access-students.ts +++ b/src/modules/classes/data-access-students.ts @@ -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>> => { +export const getStudentsSubjectScoresRaw = async ( + studentIds: string[] +): Promise>> => { 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>> => { +export const getClassStudentSubjectScoresV2Raw = async ( + params: { classId: string; teacherId?: string } +): Promise>> => { 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 => { +export const getStudentClassesRaw = async (studentId: string): Promise => { const id = studentId.trim() if (!id) return [] @@ -190,14 +200,21 @@ export const getStudentClasses = cache(async (studentId: string): Promise => { +export const getStudentClassByIdRaw = async ( + studentId: string, + classId: string +): Promise => { 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 => { +export const getClassStudentsRaw = async ( + params?: { classId?: string; q?: string; status?: string; teacherId?: string } +): Promise => { 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) diff --git a/src/modules/classes/data-access-teacher.ts b/src/modules/classes/data-access-teacher.ts index 4add709..5ddc54e 100644 --- a/src/modules/classes/data-access-teacher.ts +++ b/src/modules/classes/data-access-teacher.ts @@ -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 => { +export const getTeacherClassesRaw = async (params?: { teacherId?: string }): Promise => { 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 => { +export const getTeacherOptionsRaw = async (): Promise => { 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 => { 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 => { +export const getTeacherTeachingSubjectsRaw = async (): Promise => { const teacherId = await getSessionTeacherId() if (!teacherId) return [] @@ -149,6 +159,11 @@ export const getTeacherTeachingSubjects = cache(async (): Promise 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 {