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

@@ -132,12 +132,13 @@ export async function getScheduleChanges(
const userMap = new Map<string, string>()
if (userIds.length > 0) {
const firstId = userIds[0]
const userRows = await db
.select({ id: users.id, name: users.name })
.from(users)
.where(
userIds.length === 1
? eq(users.id, userIds[0]!)
userIds.length === 1 && firstId
? eq(users.id, firstId)
: or(...userIds.map((id) => eq(users.id, id)))
)
for (const u of userRows) userMap.set(u.id, u.name ?? "Unknown")
@@ -218,8 +219,9 @@ export async function getClassConflicts(classId: string): Promise<ScheduleConfli
const conflicts: ScheduleConflict[] = []
for (let i = 0; i < rows.length; i += 1) {
for (let j = i + 1; j < rows.length; j += 1) {
const a = rows[i]!
const b = rows[j]!
const a = rows[i]
const b = rows[j]
if (!a || !b) continue
if (a.weekday !== b.weekday) continue
// Time overlap: a.start < b.end && b.start < a.end
if (a.startTime < b.endTime && b.startTime < a.endTime) {
@@ -236,14 +238,42 @@ export async function getClassConflicts(classId: string): Promise<ScheduleConfli
// --- Helpers for scheduling pages ---
export async function getAdminClassesForScheduling() {
/** Lightweight class info for scheduling selectors */
export type SchedulingClassOption = {
id: string
name: string
grade: string
}
/** Lightweight teacher info for scheduling selectors */
export type SchedulingTeacherOption = {
id: string
name: string | null
email: string
}
/** Lightweight classroom info for scheduling selectors */
export type SchedulingClassroomOption = {
id: string
name: string
building: string | null
}
/** Class subject with assigned teacher for scheduling */
export type SchedulingClassSubject = {
subjectId: string
subjectName: string
teacherId: string | null
}
export async function getAdminClassesForScheduling(): Promise<SchedulingClassOption[]> {
return await db
.select({ id: classes.id, name: classes.name, grade: classes.grade })
.from(classes)
.orderBy(classes.grade, classes.name)
}
export async function getTeachersForScheduling() {
export async function getTeachersForScheduling(): Promise<SchedulingTeacherOption[]> {
return await db
.select({ id: users.id, name: users.name, email: users.email })
.from(users)
@@ -252,14 +282,16 @@ export async function getTeachersForScheduling() {
.orderBy(users.name)
}
export async function getClassroomsForScheduling() {
export async function getClassroomsForScheduling(): Promise<SchedulingClassroomOption[]> {
return await db
.select({ id: classrooms.id, name: classrooms.name, building: classrooms.building })
.from(classrooms)
.orderBy(classrooms.name)
}
export async function getClassSubjectsForScheduling(classId: string) {
export async function getClassSubjectsForScheduling(
classId: string
): Promise<SchedulingClassSubject[]> {
return await db
.select({
subjectId: subjects.id,