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:
SpecialX
2026-06-19 05:13:09 +08:00
parent 063baffe4c
commit 49291fcc31
114 changed files with 12548 additions and 3395 deletions

View File

@@ -13,6 +13,14 @@ import {
publishDiagnosticReport,
deleteDiagnosticReport,
} from "./data-access-reports"
import {
GenerateStudentReportSchema,
GenerateClassReportSchema,
PublishReportSchema,
DeleteReportSchema,
GetDiagnosticReportsSchema,
GetDiagnosticReportByIdSchema,
} from "./schema"
import type { DiagnosticReportQueryParams } from "./types"
/** 生成学生个人诊断报告 */
@@ -23,15 +31,15 @@ export async function generateStudentReportAction(
try {
const ctx = await requirePermission(Permissions.DIAGNOSTIC_MANAGE)
const studentId = formData.get("studentId")
const period = formData.get("period")
if (typeof studentId !== "string" || studentId.length === 0) {
return { success: false, message: "Missing studentId" }
}
if (typeof period !== "string" || period.length === 0) {
return { success: false, message: "Missing period" }
const parsed = GenerateStudentReportSchema.safeParse({
studentId: formData.get("studentId"),
period: formData.get("period"),
})
if (!parsed.success) {
return { success: false, message: "Missing studentId or period" }
}
const { studentId, period } = parsed.data
const id = await generateDiagnosticReport(studentId, period, ctx.userId)
revalidatePath("/teacher/diagnostic")
revalidatePath(`/teacher/diagnostic/student/${studentId}`)
@@ -51,15 +59,15 @@ export async function generateClassReportAction(
try {
const ctx = await requirePermission(Permissions.DIAGNOSTIC_MANAGE)
const classId = formData.get("classId")
const period = formData.get("period")
if (typeof classId !== "string" || classId.length === 0) {
return { success: false, message: "Missing classId" }
}
if (typeof period !== "string" || period.length === 0) {
return { success: false, message: "Missing period" }
const parsed = GenerateClassReportSchema.safeParse({
classId: formData.get("classId"),
period: formData.get("period"),
})
if (!parsed.success) {
return { success: false, message: "Missing classId or period" }
}
const { classId, period } = parsed.data
const id = await generateClassDiagnosticReport(classId, period, ctx.userId)
revalidatePath("/teacher/diagnostic")
revalidatePath(`/teacher/diagnostic/class/${classId}`)
@@ -79,12 +87,14 @@ export async function publishReportAction(
try {
await requirePermission(Permissions.DIAGNOSTIC_MANAGE)
const id = formData.get("id")
if (typeof id !== "string" || id.length === 0) {
const parsed = PublishReportSchema.safeParse({
id: formData.get("id"),
})
if (!parsed.success) {
return { success: false, message: "Missing report id" }
}
await publishDiagnosticReport(id)
await publishDiagnosticReport(parsed.data.id)
revalidatePath("/teacher/diagnostic")
return { success: true, message: "Report published" }
} catch (e) {
@@ -102,12 +112,14 @@ export async function deleteReportAction(
try {
await requirePermission(Permissions.DIAGNOSTIC_MANAGE)
const id = formData.get("id")
if (typeof id !== "string" || id.length === 0) {
const parsed = DeleteReportSchema.safeParse({
id: formData.get("id"),
})
if (!parsed.success) {
return { success: false, message: "Missing report id" }
}
await deleteDiagnosticReport(id)
await deleteDiagnosticReport(parsed.data.id)
revalidatePath("/teacher/diagnostic")
return { success: true, message: "Report deleted" }
} catch (e) {
@@ -123,7 +135,13 @@ export async function getDiagnosticReportsAction(
): Promise<ActionState<Awaited<ReturnType<typeof getDiagnosticReports>>>> {
try {
await requirePermission(Permissions.DIAGNOSTIC_READ)
const reports = await getDiagnosticReports(params)
const parsed = GetDiagnosticReportsSchema.safeParse(params)
if (!parsed.success) {
return { success: false, message: "Invalid query params" }
}
const reports = await getDiagnosticReports(parsed.data)
return { success: true, data: reports }
} catch (e) {
if (e instanceof PermissionDeniedError) return { success: false, message: e.message }
@@ -138,7 +156,13 @@ export async function getDiagnosticReportByIdAction(
): Promise<ActionState<Awaited<ReturnType<typeof getDiagnosticReportById>>>> {
try {
await requirePermission(Permissions.DIAGNOSTIC_READ)
const report = await getDiagnosticReportById(id)
const parsed = GetDiagnosticReportByIdSchema.safeParse({ id })
if (!parsed.success) {
return { success: false, message: "Missing report id" }
}
const report = await getDiagnosticReportById(parsed.data.id)
return { success: true, data: report }
} catch (e) {
if (e instanceof PermissionDeniedError) return { success: false, message: e.message }