feat(shared): add errors lib, question-content, and update permissions and UI

- Add errors lib for standardized error handling

- Add question-content lib for question content processing

- Update action-utils, ai/provider-config, auth-guard, permissions, types/permissions

- Update UI sheet component

- Update proxy middleware
This commit is contained in:
SpecialX
2026-06-24 12:04:09 +08:00
parent e3b8455b31
commit 1f833097e2
9 changed files with 299 additions and 53 deletions

View File

@@ -7,17 +7,12 @@ import {
grades,
parentStudentRelations,
} from "@/shared/db/schema"
import { eq, or } from "drizzle-orm"
import { eq, inArray, or } from "drizzle-orm"
import { getSession } from "@/shared/lib/session"
import { PermissionDeniedError } from "@/shared/lib/errors"
export class PermissionDeniedError extends Error {
constructor(permission: string) {
super(
`权限不足:需要 ${permission} 权限。请联系管理员授权或切换账号后重试。`
)
this.name = "PermissionDeniedError"
}
}
// Re-export for backward compatibility (other modules still import from here)
export { PermissionDeniedError } from "@/shared/lib/errors"
/**
* Get the full authentication context for the current user.
@@ -114,16 +109,26 @@ async function resolveDataScope(userId: string, roleNames: Role[]): Promise<Data
}
// Student: can see data from their enrolled classes
// Pre-resolve classIds here to avoid N+1 queries in data-access layer
// Pre-resolve classIds and gradeIds here to avoid N+1 queries in data-access layer
if (roleNames.includes("student")) {
const enrolledClasses = await db
.select({ classId: classEnrollments.classId })
.select({ classId: classEnrollments.classId, gradeId: classes.gradeId })
.from(classEnrollments)
.innerJoin(classes, eq(classEnrollments.classId, classes.id))
.where(eq(classEnrollments.studentId, userId))
const gradeIds = [
...new Set(
enrolledClasses
.map((c) => c.gradeId)
.filter((g): g is string => g !== null),
),
]
return {
type: "class_members",
classIds: enrolledClasses.map((c) => c.classId),
gradeIds: gradeIds.length > 0 ? gradeIds : undefined,
}
}
@@ -134,7 +139,28 @@ async function resolveDataScope(userId: string, roleNames: Role[]): Promise<Data
.from(parentStudentRelations)
.where(eq(parentStudentRelations.parentId, userId))
return { type: "children", childrenIds: children.map((c) => c.studentId) }
const childrenIds = children.map((c) => c.studentId)
// Pre-resolve gradeIds from children's enrolled classes
let gradeIds: string[] | undefined
if (childrenIds.length > 0) {
const childrenClasses = await db
.select({ gradeId: classes.gradeId })
.from(classEnrollments)
.innerJoin(classes, eq(classEnrollments.classId, classes.id))
.where(inArray(classEnrollments.studentId, childrenIds))
const uniqueGradeIds = [
...new Set(
childrenClasses
.map((c) => c.gradeId)
.filter((g): g is string => g !== null),
),
]
gradeIds = uniqueGradeIds.length > 0 ? uniqueGradeIds : undefined
}
return { type: "children", childrenIds, gradeIds }
}
// Fallback: only own data