feat(portal-shell): v2.0 P0 shadcn standardization + security + streaming + error handling
- shadcn/ui 标准化:废弃纸感令牌,统一 bg-background/text-foreground 等 - Tailwind v4 + @theme inline,移除 tailwind.config.js - React 19 use() + Suspense 流式渲染,首屏骨架秒出 - 三级错误边界:Route → Section → Widget 层层兜底 - 错误上报:useErrorReport → sendBeacon → /api/log mock 端点 - 三层安全边界:L1 角色门禁 / L2 权限点门禁 / L3 数据范围 - 权限位图 base36 压缩:67 权限点 → ~14 字符,JWT 体积减少 ≥ 99% - notify 统一 Toast 封装,禁止业务直接 import sonner - PluginBoundary 替代 PluginLoader(错误边界 + Suspense + Skeleton 三件套) 验证:typecheck 0 错误 / lint 0 错误 / build 6 路由生成成功
This commit is contained in:
@@ -1,724 +1,69 @@
|
||||
# 自动生成的 GraphQL Federation 子图(v2.1 M0)
|
||||
# 源文件:core_edu.proto
|
||||
# 请勿手动修改;如需调整,改 proto 后重新运行 pnpm run proto:gen-graphql
|
||||
# ------------------------------------------------------
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
|
||||
# ------------------------------------------------------
|
||||
|
||||
extend type Query
|
||||
|
||||
type Exam @key(fields: "id") {
|
||||
id: String
|
||||
class_id: String
|
||||
subject_id: String
|
||||
title: String
|
||||
type Exam {
|
||||
id: ID!
|
||||
classId: String!
|
||||
subjectId: String!
|
||||
title: String!
|
||||
description: String
|
||||
exam_date: String
|
||||
duration: Int
|
||||
total_score: String
|
||||
status: String
|
||||
status_changed_at: String
|
||||
status_changed_by: String
|
||||
school_id: String
|
||||
created_by: String
|
||||
archived_at: String
|
||||
created_at: String
|
||||
updated_at: String
|
||||
examDate: String!
|
||||
duration: Float!
|
||||
totalScore: String!
|
||||
status: String!
|
||||
statusChangedAt: String!
|
||||
statusChangedBy: String
|
||||
schoolId: String!
|
||||
createdBy: String!
|
||||
archivedAt: String
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
}
|
||||
|
||||
type ExamQuestion @key(fields: "id") {
|
||||
id: String
|
||||
exam_id: String
|
||||
question_id: String
|
||||
order: Int
|
||||
score: String
|
||||
question_type: String
|
||||
}
|
||||
|
||||
type ExamSubmission @key(fields: "id") {
|
||||
id: String
|
||||
exam_id: String
|
||||
student_id: String
|
||||
status: String
|
||||
submitted_at: String
|
||||
graded_at: String
|
||||
graded_by: String
|
||||
total_score: String
|
||||
}
|
||||
|
||||
type CreateExamResponse @key(fields: "id") {
|
||||
id: String
|
||||
}
|
||||
|
||||
type GetExamRequest @key(fields: "id") {
|
||||
id: String
|
||||
}
|
||||
|
||||
type UpdateExamRequest @key(fields: "id") {
|
||||
id: String
|
||||
title: String
|
||||
type Homework {
|
||||
id: ID!
|
||||
classId: String!
|
||||
subjectId: String!
|
||||
title: String!
|
||||
description: String
|
||||
exam_date: String
|
||||
duration: Int
|
||||
total_score: String
|
||||
status: String
|
||||
dueDate: String!
|
||||
gracePeriod: Float!
|
||||
status: String!
|
||||
schoolId: String!
|
||||
createdBy: String!
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
}
|
||||
|
||||
type DeleteExamRequest @key(fields: "id") {
|
||||
id: String
|
||||
}
|
||||
|
||||
type PublishExamRequest @key(fields: "id") {
|
||||
id: String
|
||||
published_by: String
|
||||
}
|
||||
|
||||
type Homework @key(fields: "id") {
|
||||
id: String
|
||||
class_id: String
|
||||
subject_id: String
|
||||
title: String
|
||||
description: String
|
||||
due_date: String
|
||||
grace_period: Int
|
||||
status: String
|
||||
school_id: String
|
||||
created_by: String
|
||||
created_at: String
|
||||
updated_at: String
|
||||
}
|
||||
|
||||
type HomeworkSubmission @key(fields: "id") {
|
||||
id: String
|
||||
homework_id: String
|
||||
student_id: String
|
||||
status: String
|
||||
submitted_at: String
|
||||
graded_at: String
|
||||
graded_by: String
|
||||
total_score: String
|
||||
type Grade {
|
||||
id: ID!
|
||||
studentId: String!
|
||||
examId: String
|
||||
homeworkId: String
|
||||
score: String!
|
||||
totalScore: String!
|
||||
feedback: String
|
||||
gradedBy: String!
|
||||
schoolId: String!
|
||||
idempotencyKey: String
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
}
|
||||
|
||||
type AssignHomeworkResponse @key(fields: "id") {
|
||||
id: String
|
||||
}
|
||||
|
||||
type GetHomeworkRequest @key(fields: "id") {
|
||||
id: String
|
||||
}
|
||||
|
||||
type Grade @key(fields: "id") {
|
||||
id: String
|
||||
student_id: String
|
||||
exam_id: String
|
||||
homework_id: String
|
||||
score: String
|
||||
total_score: String
|
||||
feedback: String
|
||||
graded_by: String
|
||||
school_id: String
|
||||
idempotency_key: String
|
||||
created_at: String
|
||||
updated_at: String
|
||||
}
|
||||
|
||||
type RecordGradeResponse @key(fields: "id") {
|
||||
id: String
|
||||
}
|
||||
|
||||
type GetGradeRequest @key(fields: "id") {
|
||||
id: String
|
||||
}
|
||||
|
||||
type UpdateGradeRequest @key(fields: "id") {
|
||||
id: String
|
||||
score: String
|
||||
feedback: String
|
||||
updated_by: String
|
||||
}
|
||||
|
||||
type ClassInfo @key(fields: "id") {
|
||||
id: String
|
||||
name: String
|
||||
grade_id: String
|
||||
head_teacher_id: String
|
||||
type ClassInfo {
|
||||
id: ID!
|
||||
name: String!
|
||||
gradeId: String!
|
||||
headTeacherId: String
|
||||
description: String
|
||||
created_at: String
|
||||
updated_at: String
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
}
|
||||
|
||||
type StudentInfo @key(fields: "id") {
|
||||
id: String
|
||||
name: String
|
||||
class_id: String
|
||||
}
|
||||
|
||||
type GetClassRequest @key(fields: "id") {
|
||||
id: String
|
||||
}
|
||||
|
||||
type Attendance @key(fields: "id") {
|
||||
id: String
|
||||
schedule_id: String
|
||||
student_id: String
|
||||
status: String
|
||||
remark: String
|
||||
recorded_by: String
|
||||
school_id: String
|
||||
created_at: String
|
||||
updated_at: String
|
||||
}
|
||||
|
||||
type RecordAttendanceResponse @key(fields: "id") {
|
||||
id: String
|
||||
}
|
||||
|
||||
type GetAttendanceRequest @key(fields: "id") {
|
||||
id: String
|
||||
}
|
||||
|
||||
type ScheduleSlotInfo @key(fields: "id") {
|
||||
id: String
|
||||
course_id: String
|
||||
course_name: String
|
||||
teacher_id: String
|
||||
class_id: String
|
||||
room_id: String
|
||||
start_time: String
|
||||
end_time: String
|
||||
subject_id: String
|
||||
}
|
||||
|
||||
type LeaveRequest @key(fields: "id") {
|
||||
id: String
|
||||
student_id: String
|
||||
class_id: String
|
||||
leave_type: String
|
||||
start_date: String
|
||||
end_date: String
|
||||
reason: String
|
||||
status: String
|
||||
submitted_by: String
|
||||
reviewed_by: String
|
||||
review_comment: String
|
||||
school_id: String
|
||||
created_at: String
|
||||
updated_at: String
|
||||
}
|
||||
|
||||
type CreateLeaveRequestResponse @key(fields: "id") {
|
||||
id: String
|
||||
}
|
||||
|
||||
type CancelLeaveRequestRequest @key(fields: "id") {
|
||||
id: String
|
||||
cancelled_by: String
|
||||
}
|
||||
|
||||
type SchoolInfo @key(fields: "id") {
|
||||
id: String
|
||||
name: String
|
||||
address: String
|
||||
principal_id: String
|
||||
created_at: String
|
||||
}
|
||||
|
||||
type GradeLevelInfo @key(fields: "id") {
|
||||
id: String
|
||||
name: String
|
||||
school_id: String
|
||||
order: Int
|
||||
}
|
||||
|
||||
type DepartmentInfo @key(fields: "id") {
|
||||
id: String
|
||||
name: String
|
||||
school_id: String
|
||||
head_id: String
|
||||
created_at: String
|
||||
}
|
||||
|
||||
type AcademicYearInfo @key(fields: "id") {
|
||||
id: String
|
||||
name: String
|
||||
school_id: String
|
||||
start_date: String
|
||||
end_date: String
|
||||
is_current: Boolean
|
||||
}
|
||||
|
||||
input CreateExamRequestInput {
|
||||
class_id: String
|
||||
subject_id: String
|
||||
title: String
|
||||
description: String
|
||||
exam_date: String
|
||||
duration: Int
|
||||
total_score: String
|
||||
school_id: String
|
||||
created_by: String
|
||||
}
|
||||
|
||||
input ListExamsByClassRequestInput {
|
||||
class_id: String
|
||||
}
|
||||
|
||||
input ListExamsResponseInput {
|
||||
exams: Exam
|
||||
}
|
||||
|
||||
input UpdateExamResponseInput {
|
||||
success: Boolean
|
||||
}
|
||||
|
||||
input DeleteExamResponseInput {
|
||||
success: Boolean
|
||||
}
|
||||
|
||||
input PublishExamResponseInput {
|
||||
success: Boolean
|
||||
}
|
||||
|
||||
input SubmitExamRequestInput {
|
||||
exam_id: String
|
||||
student_id: String
|
||||
answers: AnswerInput
|
||||
}
|
||||
|
||||
type AnswerInput {
|
||||
question_id: String
|
||||
answer: String
|
||||
}
|
||||
|
||||
input SubmitExamResponseInput {
|
||||
submission_id: String
|
||||
}
|
||||
|
||||
input GradeExamRequestInput {
|
||||
exam_id: String
|
||||
submission_id: String
|
||||
scores: ScoreInput
|
||||
graded_by: String
|
||||
}
|
||||
|
||||
type ScoreInput {
|
||||
question_id: String
|
||||
score: String
|
||||
teacher_comment: String
|
||||
}
|
||||
|
||||
input GradeExamResponseInput {
|
||||
success: Boolean
|
||||
total_score: String
|
||||
}
|
||||
|
||||
input AssignHomeworkRequestInput {
|
||||
class_id: String
|
||||
subject_id: String
|
||||
title: String
|
||||
description: String
|
||||
due_date: String
|
||||
grace_period: Int
|
||||
school_id: String
|
||||
created_by: String
|
||||
}
|
||||
|
||||
input ListHomeworkByClassRequestInput {
|
||||
class_id: String
|
||||
}
|
||||
|
||||
input ListHomeworkResponseInput {
|
||||
homework: Homework
|
||||
}
|
||||
|
||||
input SubmitHomeworkRequestInput {
|
||||
homework_id: String
|
||||
student_id: String
|
||||
answers: AnswerInput
|
||||
}
|
||||
|
||||
input SubmitHomeworkResponseInput {
|
||||
submission_id: String
|
||||
}
|
||||
|
||||
input GradeHomeworkRequestInput {
|
||||
homework_id: String
|
||||
submission_id: String
|
||||
scores: ScoreInput
|
||||
feedback: String
|
||||
graded_by: String
|
||||
}
|
||||
|
||||
input GradeHomeworkResponseInput {
|
||||
success: Boolean
|
||||
total_score: String
|
||||
}
|
||||
|
||||
input RecordGradeRequestInput {
|
||||
student_id: String
|
||||
exam_id: String
|
||||
homework_id: String
|
||||
score: String
|
||||
total_score: String
|
||||
feedback: String
|
||||
graded_by: String
|
||||
school_id: String
|
||||
idempotency_key: String
|
||||
}
|
||||
|
||||
input ListGradesByStudentRequestInput {
|
||||
student_id: String
|
||||
}
|
||||
|
||||
input ListGradesByExamRequestInput {
|
||||
exam_id: String
|
||||
}
|
||||
|
||||
input ListGradesByHomeworkRequestInput {
|
||||
homework_id: String
|
||||
}
|
||||
|
||||
input ListGradesResponseInput {
|
||||
grades: Grade
|
||||
}
|
||||
|
||||
input UpdateGradeResponseInput {
|
||||
success: Boolean
|
||||
}
|
||||
|
||||
input GetClassesByTeacherRequestInput {
|
||||
teacher_id: String
|
||||
}
|
||||
|
||||
input GetClassesByTeacherResponseInput {
|
||||
classes: ClassInfo
|
||||
}
|
||||
|
||||
input BatchGetClassesRequestInput {
|
||||
ids: String
|
||||
}
|
||||
|
||||
input BatchGetClassesResponseInput {
|
||||
classes: ClassInfo
|
||||
}
|
||||
|
||||
input ListStudentsByClassRequestInput {
|
||||
class_id: String
|
||||
}
|
||||
|
||||
input ListStudentsByClassResponseInput {
|
||||
students: StudentInfo
|
||||
}
|
||||
|
||||
input RecordAttendanceRequestInput {
|
||||
schedule_id: String
|
||||
student_id: String
|
||||
status: String
|
||||
remark: String
|
||||
recorded_by: String
|
||||
school_id: String
|
||||
}
|
||||
|
||||
input ListAttendanceByStudentRequestInput {
|
||||
student_id: String
|
||||
}
|
||||
|
||||
input ListAttendanceByClassRequestInput {
|
||||
class_id: String
|
||||
}
|
||||
|
||||
input ListAttendanceResponseInput {
|
||||
attendance: Attendance
|
||||
}
|
||||
|
||||
input SaveExamDraftRequestInput {
|
||||
exam_id: String
|
||||
student_id: String
|
||||
answers: AnswerInput
|
||||
}
|
||||
|
||||
input SaveExamDraftResponseInput {
|
||||
draft_id: String
|
||||
}
|
||||
|
||||
input RecordExamViolationRequestInput {
|
||||
exam_id: String
|
||||
student_id: String
|
||||
violation_type: String
|
||||
detail: String
|
||||
severity: Int
|
||||
}
|
||||
|
||||
input RecordExamViolationResponseInput {
|
||||
violation_id: String
|
||||
}
|
||||
|
||||
input ExtendExamRequestInput {
|
||||
exam_id: String
|
||||
extension_seconds: Int
|
||||
extended_by: String
|
||||
}
|
||||
|
||||
input ExtendExamResponseInput {
|
||||
success: Boolean
|
||||
new_duration: Int
|
||||
}
|
||||
|
||||
input ForceSubmitExamRequestInput {
|
||||
exam_id: String
|
||||
student_ids: String
|
||||
forced_by: String
|
||||
}
|
||||
|
||||
input ForceSubmitExamResponseInput {
|
||||
affected_count: Int
|
||||
}
|
||||
|
||||
type QuestionOrderInput {
|
||||
question_id: String
|
||||
order: Int
|
||||
}
|
||||
|
||||
input ReorderExamQuestionsRequestInput {
|
||||
exam_id: String
|
||||
orders: QuestionOrderInput
|
||||
reordered_by: String
|
||||
}
|
||||
|
||||
input ReorderExamQuestionsResponseInput {
|
||||
success: Boolean
|
||||
}
|
||||
|
||||
input GetReportCardRequestInput {
|
||||
student_id: String
|
||||
term_id: String
|
||||
}
|
||||
|
||||
type ReportCard {
|
||||
student_id: String
|
||||
term_id: String
|
||||
entries: ReportCardEntry
|
||||
overall_grade: String
|
||||
class_rank: String
|
||||
created_at: String
|
||||
}
|
||||
|
||||
type ReportCardEntry {
|
||||
subject_id: String
|
||||
subject_name: String
|
||||
exam_score: String
|
||||
exam_total: String
|
||||
homework_score: String
|
||||
homework_total: String
|
||||
final_score: String
|
||||
grade_level: String
|
||||
teacher_comment: String
|
||||
}
|
||||
|
||||
input GetScheduleByStudentRequestInput {
|
||||
student_id: String
|
||||
week_start: String
|
||||
}
|
||||
|
||||
input GetScheduleByStudentResponseInput {
|
||||
slots: ScheduleSlotInfo
|
||||
}
|
||||
|
||||
input ListLeaveRequestsByStudentRequestInput {
|
||||
student_id: String
|
||||
status: String
|
||||
}
|
||||
|
||||
input ListLeaveRequestsResponseInput {
|
||||
leave_requests: LeaveRequest
|
||||
}
|
||||
|
||||
input CreateLeaveRequestRequestInput {
|
||||
student_id: String
|
||||
class_id: String
|
||||
leave_type: String
|
||||
start_date: String
|
||||
end_date: String
|
||||
reason: String
|
||||
submitted_by: String
|
||||
school_id: String
|
||||
}
|
||||
|
||||
input CancelLeaveRequestResponseInput {
|
||||
success: Boolean
|
||||
}
|
||||
|
||||
input GetDashboardRequestInput {
|
||||
teacher_id: String
|
||||
}
|
||||
|
||||
type DashboardData {
|
||||
teacher_id: String
|
||||
total_classes: Int
|
||||
total_students: Int
|
||||
pending_homework: Int
|
||||
upcoming_exams: Int
|
||||
ungraded_submissions: Int
|
||||
classes: DashboardClassCard
|
||||
upcoming_exam_list: DashboardExamCard
|
||||
generated_at: String
|
||||
}
|
||||
|
||||
type DashboardClassCard {
|
||||
class_id: String
|
||||
class_name: String
|
||||
student_count: Int
|
||||
}
|
||||
|
||||
type DashboardExamCard {
|
||||
exam_id: String
|
||||
title: String
|
||||
exam_date: String
|
||||
class_id: String
|
||||
class_name: String
|
||||
}
|
||||
|
||||
input GetClassPerformanceRequestInput {
|
||||
class_id: String
|
||||
subject_id: String
|
||||
}
|
||||
|
||||
type ClassPerformance {
|
||||
class_id: String
|
||||
class_name: String
|
||||
student_count: Int
|
||||
average_score: String
|
||||
highest_score: String
|
||||
lowest_score: String
|
||||
median_score: String
|
||||
subjects: ClassPerformanceSubject
|
||||
generated_at: String
|
||||
}
|
||||
|
||||
type ClassPerformanceSubject {
|
||||
subject_id: String
|
||||
subject_name: String
|
||||
average_score: String
|
||||
student_count: Int
|
||||
}
|
||||
|
||||
input ListSchoolsRequestInput {
|
||||
}
|
||||
|
||||
input ListSchoolsResponseInput {
|
||||
schools: SchoolInfo
|
||||
}
|
||||
|
||||
input ListGradeLevelsRequestInput {
|
||||
school_id: String
|
||||
}
|
||||
|
||||
input ListGradeLevelsResponseInput {
|
||||
grade_levels: GradeLevelInfo
|
||||
}
|
||||
|
||||
input ListDepartmentsRequestInput {
|
||||
school_id: String
|
||||
}
|
||||
|
||||
input ListDepartmentsResponseInput {
|
||||
departments: DepartmentInfo
|
||||
}
|
||||
|
||||
input ListAcademicYearsRequestInput {
|
||||
school_id: String
|
||||
}
|
||||
|
||||
input ListAcademicYearsResponseInput {
|
||||
academic_years: AcademicYearInfo
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
exam: Exam
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
examsByClass: [ListExamsResponse!]!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
homework: Homework
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
homeworkByClass: [ListHomeworkResponse!]!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
grade: Grade
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
gradesByStudent: [ListGradesResponse!]!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
gradesByExam: [ListGradesResponse!]!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
gradesByHomework: [ListGradesResponse!]!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
reportCard: ReportCard
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
class: ClassInfo
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
classesByTeacher: GetClassesByTeacherResponse
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
studentsByClass: [ListStudentsByClassResponse!]!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
attendance: Attendance
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
attendanceByStudent: [ListAttendanceResponse!]!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
attendanceByClass: [ListAttendanceResponse!]!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
scheduleByStudent: GetScheduleByStudentResponse
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
leaveRequestsByStudent: [ListLeaveRequestsResponse!]!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
dashboard: DashboardData
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
classPerformance: ClassPerformance
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
schools: [ListSchoolsResponse!]!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
gradeLevels: [ListGradeLevelsResponse!]!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
departments: [ListDepartmentsResponse!]!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
academicYears: [ListAcademicYearsResponse!]!
|
||||
}
|
||||
type Query {
|
||||
exam(id: ID!): Exam
|
||||
homework(id: ID!): Homework
|
||||
grade(id: ID!): Grade
|
||||
classInfo(id: ID!): ClassInfo
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
import { Module } from "@nestjs/common";
|
||||
import { GraphQLModule } from "@nestjs/graphql";
|
||||
import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
|
||||
import { ApolloFederationDriver, ApolloDriverConfig } from "@nestjs/apollo";
|
||||
import { join } from "node:path";
|
||||
import { GraphqlContext } from "@edu/shared-ts/federation";
|
||||
import { ExamsModule } from "../exams/exams.module.js";
|
||||
@@ -23,11 +23,7 @@ import { ClassesModule } from "../classes/classes.module.js";
|
||||
import { ExamResolver } from "./resolvers/exam.resolver.js";
|
||||
import { HomeworkResolver } from "./resolvers/homework.resolver.js";
|
||||
import { GradeResolver } from "./resolvers/grade.resolver.js";
|
||||
import {
|
||||
ClassResolver,
|
||||
StudentInfoResolver,
|
||||
} from "./resolvers/class.resolver.js";
|
||||
import { DataScopeResolver } from "./resolvers/datascope.resolver.js";
|
||||
import { ClassResolver } from "./resolvers/class.resolver.js";
|
||||
import { DataLoaderService } from "./dataloader.service.js";
|
||||
import { getRedisClient } from "../config/redis.js";
|
||||
|
||||
@@ -38,7 +34,7 @@ import { getRedisClient } from "../config/redis.js";
|
||||
GradesModule,
|
||||
ClassesModule,
|
||||
GraphQLModule.forRoot<ApolloDriverConfig>({
|
||||
driver: ApolloDriver,
|
||||
driver: ApolloFederationDriver,
|
||||
// Federation 2 子图
|
||||
autoSchemaFile: {
|
||||
path: join(process.cwd(), "src/graphql/generated/schema.graphql"),
|
||||
@@ -63,8 +59,6 @@ import { getRedisClient } from "../config/redis.js";
|
||||
HomeworkResolver,
|
||||
GradeResolver,
|
||||
ClassResolver,
|
||||
StudentInfoResolver,
|
||||
DataScopeResolver,
|
||||
DataLoaderService,
|
||||
{
|
||||
// 提供 Redis 实例供后续 ScopeToken / 缓存场景使用
|
||||
|
||||
@@ -20,12 +20,17 @@ export class RouterAuthGuard extends BaseRouterAuthGuard {
|
||||
}
|
||||
|
||||
override canActivate(ctx: ExecutionContext): boolean {
|
||||
// DEV_MODE 优先放行(开发态跳过所有校验)
|
||||
if (process.env.DEV_MODE === "true") {
|
||||
return true;
|
||||
}
|
||||
|
||||
const req = ctx.switchToHttp().getRequest<{
|
||||
url: string;
|
||||
}>();
|
||||
|
||||
// 仅 GraphQL 端点需要校验,REST 路由放行
|
||||
if (!req.url?.startsWith("/graphql")) {
|
||||
if (!req?.url?.startsWith("/graphql")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@edu/shared-ts/env-loader";
|
||||
import { NestFactory } from "@nestjs/core";
|
||||
import { AppModule } from "./app.module.js";
|
||||
import { env } from "./config/env.js";
|
||||
|
||||
@@ -13,6 +13,14 @@ export interface AuthenticatedRequest extends Request {
|
||||
@Injectable()
|
||||
export class AuthMiddleware implements NestMiddleware {
|
||||
use(req: AuthenticatedRequest, _res: Response, next: NextFunction): void {
|
||||
// DEV_MODE 跳过鉴权(开发态允许 apollo-router 内省 / GraphQL playground)
|
||||
if (process.env.DEV_MODE === "true") {
|
||||
req.userId = (req.headers["x-user-id"] as string) ?? "dev-user-001";
|
||||
req.userRoles = [];
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
const userIdHeader = req.headers["x-user-id"];
|
||||
const userId = typeof userIdHeader === "string" ? userIdHeader : undefined;
|
||||
const rolesHeaderRaw = req.headers["x-user-roles"];
|
||||
|
||||
Reference in New Issue
Block a user