refactor: RBAC权限系统重构 + UI组件拆分 + 测试修复 + 架构文档
Some checks failed
CI / build-deploy (push) Has been cancelled
Some checks failed
CI / build-deploy (push) Has been cancelled
- RBAC: 新增30个权限点、DataScope行级权限、requirePermission守卫,所有57+ Server Action接入权限校验 - UI拆分: exam-form(1623行→11文件)、textbook-reader(744行→7文件),均降至300行以内 - 测试: 新增5个单元测试文件(19用例),修复4个集成测试文件(38用例全部通过) - 架构文档: 新增架构影响地图(004/005)、标准功能清单(006)、差距审计报告(007) - 项目规则: 架构图优先规则,改码必同步图 - 安全: rehype-sanitize净化、AES加密API Key、权限路由守卫 - 无障碍: skip-link、aria-label、prefers-reduced-motion - 性能: next/font优化、next/image、代码分割
This commit is contained in:
@@ -36,6 +36,7 @@ import type {
|
||||
StudentRanking,
|
||||
TeacherGradeTrendItem,
|
||||
} from "./types"
|
||||
import type { DataScope } from "@/shared/types/permissions"
|
||||
|
||||
export const getTeacherGradeTrends = cache(async (teacherId: string, limit: number = 5): Promise<TeacherGradeTrendItem[]> => {
|
||||
const recentAssignments = await db.query.homeworkAssignments.findMany({
|
||||
@@ -122,7 +123,7 @@ const getAssignmentMaxScoreById = async (assignmentIds: string[]): Promise<Map<s
|
||||
return map
|
||||
}
|
||||
|
||||
export const getHomeworkAssignments = cache(async (params?: { creatorId?: string; ids?: string[]; classId?: string }) => {
|
||||
export const getHomeworkAssignments = cache(async (params?: { creatorId?: string; ids?: string[]; classId?: string; scope?: DataScope }) => {
|
||||
const conditions = []
|
||||
|
||||
if (params?.creatorId) conditions.push(eq(homeworkAssignments.creatorId, params.creatorId))
|
||||
@@ -141,6 +142,37 @@ export const getHomeworkAssignments = cache(async (params?: { creatorId?: string
|
||||
conditions.push(inArray(homeworkAssignments.id, targetAssignmentIds))
|
||||
}
|
||||
|
||||
// Data scope filtering
|
||||
if (params?.scope) {
|
||||
if (params.scope.type === "owned") {
|
||||
conditions.push(eq(homeworkAssignments.creatorId, params.scope.userId))
|
||||
}
|
||||
if (params.scope.type === "class_taught" && params.scope.classIds.length > 0) {
|
||||
// Filter homework by assignments targeting students in teacher's classes
|
||||
const classStudentIds = db
|
||||
.select({ studentId: classEnrollments.studentId })
|
||||
.from(classEnrollments)
|
||||
.where(inArray(classEnrollments.classId, params.scope.classIds))
|
||||
|
||||
const targetAssignmentIds = db
|
||||
.selectDistinct({ assignmentId: homeworkAssignmentTargets.assignmentId })
|
||||
.from(homeworkAssignmentTargets)
|
||||
.where(inArray(homeworkAssignmentTargets.studentId, classStudentIds))
|
||||
|
||||
conditions.push(inArray(homeworkAssignments.id, targetAssignmentIds))
|
||||
}
|
||||
if (params.scope.type === "grade_managed" && params.scope.gradeIds.length > 0) {
|
||||
// Homework links to exam via sourceExamId, exam has gradeId
|
||||
const gradeExamIds = db
|
||||
.select({ id: exams.id })
|
||||
.from(exams)
|
||||
.where(inArray(exams.gradeId, params.scope.gradeIds))
|
||||
|
||||
conditions.push(inArray(homeworkAssignments.sourceExamId, gradeExamIds))
|
||||
}
|
||||
// "all" type: no filtering
|
||||
}
|
||||
|
||||
const data = await db.query.homeworkAssignments.findMany({
|
||||
where: conditions.length ? and(...conditions) : undefined,
|
||||
orderBy: [desc(homeworkAssignments.createdAt)],
|
||||
@@ -168,12 +200,42 @@ export const getHomeworkAssignments = cache(async (params?: { creatorId?: string
|
||||
})
|
||||
})
|
||||
|
||||
export const getHomeworkAssignmentReviewList = cache(async (params: { creatorId: string }) => {
|
||||
export const getHomeworkAssignmentReviewList = cache(async (params: { creatorId: string; scope?: DataScope }) => {
|
||||
const creatorId = params.creatorId.trim()
|
||||
if (!creatorId) return []
|
||||
|
||||
const conditions = [eq(homeworkAssignments.creatorId, creatorId)]
|
||||
|
||||
// Data scope filtering
|
||||
if (params.scope) {
|
||||
if (params.scope.type === "owned") {
|
||||
// Already filtered by creatorId above
|
||||
}
|
||||
if (params.scope.type === "class_taught" && params.scope.classIds.length > 0) {
|
||||
const classStudentIds = db
|
||||
.select({ studentId: classEnrollments.studentId })
|
||||
.from(classEnrollments)
|
||||
.where(inArray(classEnrollments.classId, params.scope.classIds))
|
||||
|
||||
const targetAssignmentIds = db
|
||||
.selectDistinct({ assignmentId: homeworkAssignmentTargets.assignmentId })
|
||||
.from(homeworkAssignmentTargets)
|
||||
.where(inArray(homeworkAssignmentTargets.studentId, classStudentIds))
|
||||
|
||||
conditions.push(inArray(homeworkAssignments.id, targetAssignmentIds))
|
||||
}
|
||||
if (params.scope.type === "grade_managed" && params.scope.gradeIds.length > 0) {
|
||||
const gradeExamIds = db
|
||||
.select({ id: exams.id })
|
||||
.from(exams)
|
||||
.where(inArray(exams.gradeId, params.scope.gradeIds))
|
||||
|
||||
conditions.push(inArray(homeworkAssignments.sourceExamId, gradeExamIds))
|
||||
}
|
||||
}
|
||||
|
||||
const assignments = await db.query.homeworkAssignments.findMany({
|
||||
where: eq(homeworkAssignments.creatorId, creatorId),
|
||||
where: and(...conditions),
|
||||
orderBy: [desc(homeworkAssignments.createdAt)],
|
||||
with: { sourceExam: true },
|
||||
})
|
||||
@@ -239,7 +301,7 @@ export const getHomeworkAssignmentReviewList = cache(async (params: { creatorId:
|
||||
})
|
||||
})
|
||||
|
||||
export const getHomeworkSubmissions = cache(async (params?: { assignmentId?: string; classId?: string; creatorId?: string }) => {
|
||||
export const getHomeworkSubmissions = cache(async (params?: { assignmentId?: string; classId?: string; creatorId?: string; scope?: DataScope }) => {
|
||||
const conditions = []
|
||||
if (params?.assignmentId) conditions.push(eq(homeworkSubmissions.assignmentId, params.assignmentId))
|
||||
if (params?.classId) {
|
||||
@@ -265,6 +327,39 @@ export const getHomeworkSubmissions = cache(async (params?: { assignmentId?: str
|
||||
conditions.push(inArray(homeworkSubmissions.assignmentId, creatorAssignmentIds))
|
||||
}
|
||||
|
||||
// Data scope filtering
|
||||
if (params?.scope) {
|
||||
if (params.scope.type === "owned") {
|
||||
const creatorAssignmentIds = db
|
||||
.select({ assignmentId: homeworkAssignments.id })
|
||||
.from(homeworkAssignments)
|
||||
.where(eq(homeworkAssignments.creatorId, params.scope.userId))
|
||||
|
||||
conditions.push(inArray(homeworkSubmissions.assignmentId, creatorAssignmentIds))
|
||||
}
|
||||
if (params.scope.type === "class_taught" && params.scope.classIds.length > 0) {
|
||||
const classStudentIds = db
|
||||
.select({ studentId: classEnrollments.studentId })
|
||||
.from(classEnrollments)
|
||||
.where(inArray(classEnrollments.classId, params.scope.classIds))
|
||||
|
||||
conditions.push(inArray(homeworkSubmissions.studentId, classStudentIds))
|
||||
}
|
||||
if (params.scope.type === "grade_managed" && params.scope.gradeIds.length > 0) {
|
||||
const gradeExamIds = db
|
||||
.select({ id: exams.id })
|
||||
.from(exams)
|
||||
.where(inArray(exams.gradeId, params.scope.gradeIds))
|
||||
|
||||
const gradeAssignmentIds = db
|
||||
.select({ assignmentId: homeworkAssignments.id })
|
||||
.from(homeworkAssignments)
|
||||
.where(inArray(homeworkAssignments.sourceExamId, gradeExamIds))
|
||||
|
||||
conditions.push(inArray(homeworkSubmissions.assignmentId, gradeAssignmentIds))
|
||||
}
|
||||
}
|
||||
|
||||
const data = await db.query.homeworkSubmissions.findMany({
|
||||
where: conditions.length ? and(...conditions) : undefined,
|
||||
orderBy: [desc(homeworkSubmissions.updatedAt)],
|
||||
@@ -289,7 +384,7 @@ export const getHomeworkSubmissions = cache(async (params?: { assignmentId?: str
|
||||
})
|
||||
})
|
||||
|
||||
export const getHomeworkAssignmentById = cache(async (id: string) => {
|
||||
export const getHomeworkAssignmentById = cache(async (id: string, scope?: DataScope) => {
|
||||
const assignment = await db.query.homeworkAssignments.findFirst({
|
||||
where: eq(homeworkAssignments.id, id),
|
||||
with: {
|
||||
@@ -299,6 +394,41 @@ export const getHomeworkAssignmentById = cache(async (id: string) => {
|
||||
|
||||
if (!assignment) return null
|
||||
|
||||
// Data scope verification for single-item fetch
|
||||
if (scope && scope.type !== "all") {
|
||||
if (scope.type === "owned" && assignment.creatorId !== scope.userId) {
|
||||
return null
|
||||
}
|
||||
if (scope.type === "grade_managed" && scope.gradeIds.length > 0) {
|
||||
const gradeExamIds = await db
|
||||
.select({ id: exams.id })
|
||||
.from(exams)
|
||||
.where(inArray(exams.gradeId, scope.gradeIds))
|
||||
const examIds = gradeExamIds.map(e => e.id)
|
||||
if (!examIds.includes(assignment.sourceExamId)) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
if (scope.type === "class_taught" && scope.classIds.length > 0) {
|
||||
const classStudentIds = await db
|
||||
.select({ studentId: classEnrollments.studentId })
|
||||
.from(classEnrollments)
|
||||
.where(inArray(classEnrollments.classId, scope.classIds))
|
||||
const studentIds = classStudentIds.map(s => s.studentId)
|
||||
if (studentIds.length > 0) {
|
||||
const target = await db.query.homeworkAssignmentTargets.findFirst({
|
||||
where: and(
|
||||
eq(homeworkAssignmentTargets.assignmentId, id),
|
||||
inArray(homeworkAssignmentTargets.studentId, studentIds)
|
||||
),
|
||||
})
|
||||
if (!target) return null
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [targetsRow] = await db
|
||||
.select({ c: count() })
|
||||
.from(homeworkAssignmentTargets)
|
||||
|
||||
Reference in New Issue
Block a user