feat(homework,classes,course-plans): add scans, student data, take confirm, error boundaries, dialogs, hooks, calendar

homework:

- Add data-access-scans, data-access-student, data-access-utils, data-access-exam-cross

- Add excellent-submissions, homework-take-confirm-dialog, homework-take-sidebar components

classes:

- Add class-delete-dialog, class-error-boundary, class-form-dialog, class-form-utils

- Add class-list-table, class-list-toolbar, class-skeleton

- Add schedule-create-dialog, schedule-delete-dialog, schedule-edit-dialog, schedule-utils

- Add data-access-teacher and hooks directory

course-plans:

- Add course-plan-calendar, sortable-week-row, template-picker-dialog components

- Add lib directory
This commit is contained in:
SpecialX
2026-07-03 10:25:35 +08:00
parent 20023e13fd
commit dfffb61e94
82 changed files with 6100 additions and 3321 deletions

View File

@@ -294,3 +294,62 @@ export const getClassStudents = cache(
}))
}
)
// ---------------------------------------------------------------------------
// DataScope resolver helpers (P1-5/P1-6 audit fix)
// These lightweight functions return only the IDs needed by the RBAC
// data-scope resolver, so shared/lib/auth-guard no longer queries classes/
// classEnrollments/classSubjectTeachers tables directly.
// ---------------------------------------------------------------------------
/**
* Get a student's class IDs and grade IDs for DataScope resolution.
* Joins classEnrollments with classes to resolve gradeId per enrollment.
*/
export async function getStudentScopeData(
studentId: string,
): Promise<{ classIds: string[]; gradeIds: string[] }> {
const rows = await db
.select({ classId: classEnrollments.classId, gradeId: classes.gradeId })
.from(classEnrollments)
.innerJoin(classes, eq(classEnrollments.classId, classes.id))
.where(eq(classEnrollments.studentId, studentId))
const classIds = rows.map((r) => r.classId)
const gradeIdSet = new Set<string>()
for (const row of rows) {
if (row.gradeId !== null && row.gradeId.trim().length > 0) {
gradeIdSet.add(row.gradeId)
}
}
return {
classIds,
gradeIds: Array.from(gradeIdSet),
}
}
/**
* Get grade IDs for a list of student IDs (used by parent DataScope).
* Queries classEnrollments JOIN classes for all students in a single query.
*/
export async function getGradeIdsForStudentIds(
studentIds: string[],
): Promise<string[]> {
if (studentIds.length === 0) return []
const rows = await db
.select({ gradeId: classes.gradeId })
.from(classEnrollments)
.innerJoin(classes, eq(classEnrollments.classId, classes.id))
.where(inArray(classEnrollments.studentId, studentIds))
const gradeIdSet = new Set<string>()
for (const row of rows) {
if (row.gradeId !== null && row.gradeId.trim().length > 0) {
gradeIdSet.add(row.gradeId)
}
}
return Array.from(gradeIdSet)
}