refactor: fix all P0/P1/P2 bugs and architecture issues
Bug fixes (from bugs/ directory): - Fix cross-module DB queries in 9 modules (homework, grades, parent, diagnostic, elective, proctoring, notifications, scheduling, classes) by routing through data-access functions - Fix shared/lib <-> auth circular dependency via new session.ts module - Fix divide-by-zero guard in grades data-access - Fix audit export data truncation (paginated fetch for full datasets) - Fix missing transactions in homework grading and elective lottery - Fix missing revalidatePath in course-plans actions - Fix frontend permission checks using requirePermission instead of requireAuth - Fix dashboard role routing using session.user.roles - Fix student auth pattern (migrate getDemoStudentUser to users module) - Fix ActionState return type handling in components Code quality fixes: - Remove 60+ as type assertions (replace with type guards) - Remove non-null assertions (use optional chaining or explicit checks) - Convert dynamic imports to static imports (grades, diagnostic) - Add React.cache() wrapping for read functions - Parallelize independent queries with Promise.all - Add explicit return types to 30+ arrow functions - Replace any with unknown + type guards - Fix import type for type-only imports - Add Zod validation schemas for classes and diagnostic modules - Extract duplicate code (normalizeRoleName, normalizeBcryptHash, logger IP extraction) - Add console.error to silent catch blocks - Fix permission naming consistency (exam:proctor_read -> exam:proctor:read) Architecture doc sync: - Update 004_architecture_impact_map.md and 005_architecture_data.json - Update management-modules-audit.md for P0-7 cross-module fix Moved deleted proctoring event route to deletes/ folder.
This commit is contained in:
@@ -1,23 +1,25 @@
|
||||
import "server-only"
|
||||
|
||||
import { cache } from "react"
|
||||
import { and, asc, eq } from "drizzle-orm"
|
||||
import { asc, eq } from "drizzle-orm"
|
||||
|
||||
import { db } from "@/shared/db"
|
||||
import { parentStudentRelations } from "@/shared/db/schema"
|
||||
import {
|
||||
classes,
|
||||
classEnrollments,
|
||||
grades,
|
||||
parentStudentRelations,
|
||||
users,
|
||||
} from "@/shared/db/schema"
|
||||
import { getStudentClasses, getStudentSchedule } from "@/modules/classes/data-access"
|
||||
getClassNameById,
|
||||
getStudentActiveClassId,
|
||||
getStudentClasses,
|
||||
getStudentSchedule,
|
||||
} from "@/modules/classes/data-access"
|
||||
import {
|
||||
getStudentDashboardGrades,
|
||||
getStudentHomeworkAssignments,
|
||||
} from "@/modules/homework/data-access"
|
||||
import { getStudentGradeSummary } from "@/modules/grades/data-access"
|
||||
import { getGradeOptions } from "@/modules/school/data-access"
|
||||
import { getUserBasicInfo, getUserNamesByIds } from "@/modules/users/data-access"
|
||||
import type {
|
||||
ChildBasicInfo,
|
||||
ChildDashboardData,
|
||||
ChildHomeworkSummary,
|
||||
ChildScheduleItem,
|
||||
@@ -25,9 +27,15 @@ import type {
|
||||
ParentDashboardData,
|
||||
} from "./types"
|
||||
|
||||
const toWeekday = (d: Date): 1 | 2 | 3 | 4 | 5 | 6 | 7 => {
|
||||
type Weekday = 1 | 2 | 3 | 4 | 5 | 6 | 7
|
||||
|
||||
const isWeekday = (n: number): n is Weekday => n >= 1 && n <= 7
|
||||
|
||||
const toWeekday = (d: Date): Weekday => {
|
||||
const day = d.getDay()
|
||||
return (day === 0 ? 7 : day) as 1 | 2 | 3 | 4 | 5 | 6 | 7
|
||||
// getDay() returns 0 (Sun) - 6 (Sat); normalize Sunday (0) to 7
|
||||
const normalized = day === 0 ? 7 : day
|
||||
return isWeekday(normalized) ? normalized : 1
|
||||
}
|
||||
|
||||
export const getChildren = cache(async (parentId: string): Promise<ParentChildRelation[]> => {
|
||||
@@ -55,66 +63,44 @@ export const getChildren = cache(async (parentId: string): Promise<ParentChildRe
|
||||
}))
|
||||
})
|
||||
|
||||
export const getChildBasicInfo = cache(async (studentId: string, relation: string | null = null) => {
|
||||
const [student] = await db
|
||||
.select({
|
||||
id: users.id,
|
||||
name: users.name,
|
||||
email: users.email,
|
||||
image: users.image,
|
||||
gradeId: users.gradeId,
|
||||
})
|
||||
.from(users)
|
||||
.where(eq(users.id, studentId))
|
||||
.limit(1)
|
||||
export const getChildBasicInfo = cache(
|
||||
async (
|
||||
studentId: string,
|
||||
relation: string | null = null,
|
||||
): Promise<ChildBasicInfo | null> => {
|
||||
const student = await getUserBasicInfo(studentId)
|
||||
|
||||
if (!student) return null
|
||||
if (!student) return null
|
||||
|
||||
let gradeName: string | null = null
|
||||
if (student.gradeId) {
|
||||
const [grade] = await db
|
||||
.select({ name: grades.name })
|
||||
.from(grades)
|
||||
.where(eq(grades.id, student.gradeId))
|
||||
.limit(1)
|
||||
gradeName = grade?.name ?? null
|
||||
}
|
||||
// gradeName 与 classId 相互独立,并行拉取
|
||||
const [gradeOptions, classId] = await Promise.all([
|
||||
student.gradeId ? getGradeOptions() : Promise.resolve([]),
|
||||
getStudentActiveClassId(studentId),
|
||||
])
|
||||
|
||||
const [enrollment] = await db
|
||||
.select({
|
||||
classId: classEnrollments.classId,
|
||||
status: classEnrollments.status,
|
||||
})
|
||||
.from(classEnrollments)
|
||||
.where(and(eq(classEnrollments.studentId, studentId), eq(classEnrollments.status, "active")))
|
||||
.orderBy(asc(classEnrollments.createdAt))
|
||||
.limit(1)
|
||||
|
||||
let className: string | null = null
|
||||
let classId: string | null = null
|
||||
if (enrollment) {
|
||||
const [cls] = await db
|
||||
.select({ id: classes.id, name: classes.name })
|
||||
.from(classes)
|
||||
.where(eq(classes.id, enrollment.classId))
|
||||
.limit(1)
|
||||
if (cls) {
|
||||
classId = cls.id
|
||||
className = cls.name
|
||||
let gradeName: string | null = null
|
||||
if (student.gradeId) {
|
||||
const grade = gradeOptions.find((g) => g.id === student.gradeId)
|
||||
gradeName = grade?.name ?? null
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: student.id,
|
||||
name: student.name,
|
||||
email: student.email,
|
||||
image: student.image,
|
||||
gradeName,
|
||||
className,
|
||||
classId,
|
||||
relation,
|
||||
}
|
||||
})
|
||||
let className: string | null = null
|
||||
if (classId) {
|
||||
className = await getClassNameById(classId)
|
||||
}
|
||||
|
||||
return {
|
||||
id: student.id,
|
||||
name: student.name,
|
||||
email: student.email,
|
||||
image: student.image,
|
||||
gradeName,
|
||||
className,
|
||||
classId,
|
||||
relation,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const buildHomeworkSummary = (
|
||||
assignments: Awaited<ReturnType<typeof getStudentHomeworkAssignments>>,
|
||||
@@ -211,12 +197,12 @@ export const getParentDashboardData = cache(
|
||||
const id = parentId.trim()
|
||||
if (!id) return { parentName: null, children: [] }
|
||||
|
||||
const [parent, relations] = await Promise.all([
|
||||
db.select({ name: users.name }).from(users).where(eq(users.id, id)).limit(1),
|
||||
const [parentInfo, relations] = await Promise.all([
|
||||
getUserNamesByIds([id]),
|
||||
getChildren(id),
|
||||
])
|
||||
|
||||
const parentName = parent[0]?.name ?? null
|
||||
const parentName = parentInfo.get(id)?.name ?? null
|
||||
|
||||
if (relations.length === 0) {
|
||||
return { parentName, children: [] }
|
||||
|
||||
Reference in New Issue
Block a user