feat(shared-ts): 同步 3 个 BFF GraphQL schema + downstream-client/logger/outbox 增强
This commit is contained in:
@@ -10,10 +10,13 @@
|
|||||||
# - C1:错误码前缀 BFF_PARENT_
|
# - C1:错误码前缀 BFF_PARENT_
|
||||||
# - U4:BFF 豁免 @RequirePermission,仅校验 x-user-id + ChildGuard
|
# - U4:BFF 豁免 @RequirePermission,仅校验 x-user-id + ChildGuard
|
||||||
# - 02 §9 #5:depth ≤ 7 / cost ≤ 1000
|
# - 02 §9 #5:depth ≤ 7 / cost ≤ 1000
|
||||||
|
#
|
||||||
|
# 版本:v2(32 Query + 6 Mutation,对齐 parent-portal 全部 operations)
|
||||||
|
|
||||||
scalar DateTime
|
scalar DateTime
|
||||||
|
scalar JSON
|
||||||
|
|
||||||
# ============ Types ============
|
# ============ Legacy Types ============
|
||||||
|
|
||||||
type Parent {
|
type Parent {
|
||||||
id: ID!
|
id: ID!
|
||||||
@@ -21,7 +24,9 @@ type Parent {
|
|||||||
name: String!
|
name: String!
|
||||||
avatar: String
|
avatar: String
|
||||||
roles: [String!]!
|
roles: [String!]!
|
||||||
|
permissions: [String!]
|
||||||
dataScope: DataScope!
|
dataScope: DataScope!
|
||||||
|
schoolId: ID
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DataScope {
|
enum DataScope {
|
||||||
@@ -184,9 +189,413 @@ type DashboardData {
|
|||||||
degraded: Boolean!
|
degraded: Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============ Query ============
|
# ============ Extended Types(v2 新增) ============
|
||||||
|
|
||||||
|
type ChildBrief {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
grade: String!
|
||||||
|
classId: ID
|
||||||
|
className: String
|
||||||
|
avatar: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChildSummary {
|
||||||
|
childId: ID!
|
||||||
|
avgScore: Float
|
||||||
|
classRank: Int
|
||||||
|
classSize: Int
|
||||||
|
attendanceRate: Float
|
||||||
|
pendingHomeworkCount: Int!
|
||||||
|
recentGradeTrend: Float
|
||||||
|
recentScores: [ChildGrade!]!
|
||||||
|
upcomingEvents: [UpcomingEvent!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpcomingEvent {
|
||||||
|
id: ID!
|
||||||
|
type: String!
|
||||||
|
title: String!
|
||||||
|
dueDate: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChildDetail {
|
||||||
|
childId: ID!
|
||||||
|
basicInfo: ChildBasicInfo!
|
||||||
|
todaySchedule: [JSON!]!
|
||||||
|
weeklySchedule: [JSON!]!
|
||||||
|
homeworkSummary: HomeworkSummary!
|
||||||
|
gradeSummary: GradeSummary!
|
||||||
|
examResults: ExamResultSummary!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChildBasicInfo {
|
||||||
|
name: String!
|
||||||
|
avatar: String
|
||||||
|
grade: String!
|
||||||
|
className: String!
|
||||||
|
schoolName: String!
|
||||||
|
relation: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type HomeworkSummary {
|
||||||
|
pendingCount: Int!
|
||||||
|
overdueCount: Int!
|
||||||
|
submittedCount: Int!
|
||||||
|
gradedCount: Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
type GradeSummary {
|
||||||
|
avgScore: Float!
|
||||||
|
classRank: Int
|
||||||
|
classSize: Int
|
||||||
|
trend: Float
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExamResultSummary {
|
||||||
|
upcoming: Int!
|
||||||
|
completed: Int!
|
||||||
|
avgScore: Float
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChildGrade {
|
||||||
|
examId: ID!
|
||||||
|
examName: String!
|
||||||
|
examDate: DateTime!
|
||||||
|
subject: String!
|
||||||
|
studentScore: Float!
|
||||||
|
classAverage: Float
|
||||||
|
classMax: Float
|
||||||
|
classMin: Float
|
||||||
|
gradeLevel: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChildHomework {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
subject: String!
|
||||||
|
className: String!
|
||||||
|
assignedDate: DateTime!
|
||||||
|
dueDate: DateTime!
|
||||||
|
status: String!
|
||||||
|
score: Float
|
||||||
|
maxScore: Float
|
||||||
|
feedback: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChildExam {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
subject: String!
|
||||||
|
status: String!
|
||||||
|
startsAt: DateTime!
|
||||||
|
expiresAt: DateTime
|
||||||
|
durationSeconds: Int
|
||||||
|
questionCount: Int
|
||||||
|
totalScore: Float
|
||||||
|
submittedAt: DateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
type AttendanceRecord {
|
||||||
|
id: ID!
|
||||||
|
date: DateTime!
|
||||||
|
status: String!
|
||||||
|
checkInTime: DateTime
|
||||||
|
checkOutTime: DateTime
|
||||||
|
note: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExamResult {
|
||||||
|
examId: ID!
|
||||||
|
childId: ID!
|
||||||
|
score: Float
|
||||||
|
rank: Int
|
||||||
|
subjectScores: [SubjectScore!]!
|
||||||
|
feedback: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubjectScore {
|
||||||
|
subject: String!
|
||||||
|
score: Float!
|
||||||
|
fullScore: Float!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChildClass {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
homeroomTeacher: String
|
||||||
|
studentCount: Int
|
||||||
|
grade: String!
|
||||||
|
year: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportCard {
|
||||||
|
childId: ID!
|
||||||
|
academicYearId: ID!
|
||||||
|
semester: Int!
|
||||||
|
subjects: [ReportCardSubject!]!
|
||||||
|
overallComment: String
|
||||||
|
classRank: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportCardSubject {
|
||||||
|
subject: String!
|
||||||
|
score: Float!
|
||||||
|
grade: String!
|
||||||
|
teacherComment: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type GrowthArchive {
|
||||||
|
childId: ID!
|
||||||
|
subject: String
|
||||||
|
dataPoints: [GrowthDataPoint!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type GrowthDataPoint {
|
||||||
|
date: DateTime!
|
||||||
|
category: String!
|
||||||
|
title: String!
|
||||||
|
description: String!
|
||||||
|
evidence: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type WeaknessItem {
|
||||||
|
id: ID!
|
||||||
|
knowledgePoint: String!
|
||||||
|
masteryLevel: Float!
|
||||||
|
subject: String!
|
||||||
|
recommendation: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChildTrend {
|
||||||
|
childId: ID!
|
||||||
|
period: TrendPeriod!
|
||||||
|
dataPoints: [TrendDataPoint!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrendDataPoint {
|
||||||
|
date: DateTime!
|
||||||
|
score: Float!
|
||||||
|
subject: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type LearningPathItem {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
subject: String!
|
||||||
|
order: Int!
|
||||||
|
masteryLevel: Float!
|
||||||
|
resources: [String!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorBookStats {
|
||||||
|
childId: ID!
|
||||||
|
totalCount: Int!
|
||||||
|
newCount: Int!
|
||||||
|
learningCount: Int!
|
||||||
|
masteredCount: Int!
|
||||||
|
dueReviewCount: Int!
|
||||||
|
masteredRate: Float!
|
||||||
|
}
|
||||||
|
|
||||||
|
type WrongQuestion {
|
||||||
|
id: ID!
|
||||||
|
questionId: ID!
|
||||||
|
subject: String!
|
||||||
|
content: String!
|
||||||
|
wrongAnswer: String!
|
||||||
|
correctAnswer: String!
|
||||||
|
addedAt: DateTime!
|
||||||
|
status: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type WeakKp {
|
||||||
|
id: ID!
|
||||||
|
knowledgePoint: String!
|
||||||
|
subject: String!
|
||||||
|
masteryLevel: Float!
|
||||||
|
recommendation: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type MasterySummary {
|
||||||
|
childId: ID!
|
||||||
|
overallMastery: Float!
|
||||||
|
subjectMastery: [SubjectMastery!]!
|
||||||
|
totalKps: Int!
|
||||||
|
masteredKps: Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubjectMastery {
|
||||||
|
subject: String!
|
||||||
|
mastery: Float!
|
||||||
|
totalKps: Int!
|
||||||
|
masteredKps: Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiagnosticReport {
|
||||||
|
id: ID!
|
||||||
|
childId: ID!
|
||||||
|
subject: String!
|
||||||
|
reportDate: DateTime!
|
||||||
|
summary: String!
|
||||||
|
recommendations: [String!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type PracticeStats {
|
||||||
|
childId: ID!
|
||||||
|
totalSessions: Int!
|
||||||
|
completedSessions: Int!
|
||||||
|
totalQuestionsAnswered: Int!
|
||||||
|
overallAccuracy: Float!
|
||||||
|
}
|
||||||
|
|
||||||
|
type PracticeSession {
|
||||||
|
id: ID!
|
||||||
|
childId: ID!
|
||||||
|
subject: String!
|
||||||
|
startedAt: DateTime!
|
||||||
|
completedAt: DateTime
|
||||||
|
questionCount: Int!
|
||||||
|
correctCount: Int!
|
||||||
|
accuracy: Float!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CoursePlan {
|
||||||
|
id: ID!
|
||||||
|
childId: ID!
|
||||||
|
subject: String!
|
||||||
|
title: String!
|
||||||
|
startDate: DateTime!
|
||||||
|
endDate: DateTime!
|
||||||
|
progress: Float!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CoursePlanDetail {
|
||||||
|
id: ID!
|
||||||
|
childId: ID!
|
||||||
|
subject: String!
|
||||||
|
title: String!
|
||||||
|
startDate: DateTime!
|
||||||
|
endDate: DateTime!
|
||||||
|
progress: Float!
|
||||||
|
lessons: [CoursePlanLesson!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CoursePlanLesson {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
date: DateTime!
|
||||||
|
completed: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
|
type LessonPlan {
|
||||||
|
id: ID!
|
||||||
|
childId: ID!
|
||||||
|
subject: String!
|
||||||
|
title: String!
|
||||||
|
date: DateTime!
|
||||||
|
teacherName: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type LessonPlanDetail {
|
||||||
|
id: ID!
|
||||||
|
childId: ID!
|
||||||
|
subject: String!
|
||||||
|
title: String!
|
||||||
|
date: DateTime!
|
||||||
|
teacherName: String!
|
||||||
|
objectives: [String!]!
|
||||||
|
content: String!
|
||||||
|
homework: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type ElectiveCourse {
|
||||||
|
id: ID!
|
||||||
|
childId: ID!
|
||||||
|
name: String!
|
||||||
|
subject: String!
|
||||||
|
teacher: String!
|
||||||
|
schedule: String!
|
||||||
|
selected: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
|
type LeaveRequestItem {
|
||||||
|
id: ID!
|
||||||
|
childId: ID!
|
||||||
|
childName: String
|
||||||
|
className: String
|
||||||
|
type: String!
|
||||||
|
startDate: DateTime!
|
||||||
|
endDate: DateTime!
|
||||||
|
reason: String!
|
||||||
|
status: String!
|
||||||
|
submittedAt: DateTime!
|
||||||
|
reviewedAt: DateTime
|
||||||
|
reviewerName: String
|
||||||
|
reviewComment: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type AcademicYear {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
startDate: DateTime!
|
||||||
|
endDate: DateTime!
|
||||||
|
isCurrent: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
|
type MyNotification {
|
||||||
|
id: ID!
|
||||||
|
childId: ID
|
||||||
|
eventType: String!
|
||||||
|
title: String!
|
||||||
|
body: String!
|
||||||
|
read: Boolean!
|
||||||
|
createdAt: DateTime!
|
||||||
|
actionUrl: String
|
||||||
|
pinned: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
|
type MyNotificationPreferences {
|
||||||
|
parentId: ID!
|
||||||
|
preferences: JSON!
|
||||||
|
defaults: NotificationPreferencesDefaults!
|
||||||
|
updatedAt: DateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotificationPreferencesDefaults {
|
||||||
|
channels: [NotificationChannel!]!
|
||||||
|
eventTypes: NotificationEventTypes!
|
||||||
|
}
|
||||||
|
|
||||||
|
type MarkAsReadResult {
|
||||||
|
id: ID!
|
||||||
|
read: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
|
type MarkAllAsReadResult {
|
||||||
|
count: Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
type SwitchChildResult {
|
||||||
|
childId: ID!
|
||||||
|
childName: String!
|
||||||
|
selectedAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExportChildGradesResult {
|
||||||
|
downloadUrl: String!
|
||||||
|
expiresAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateNotificationPreferencesResult {
|
||||||
|
parentId: ID!
|
||||||
|
updatedAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============ Query(32 个) ============
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
|
# Legacy(11 个)
|
||||||
dashboard: DashboardData!
|
dashboard: DashboardData!
|
||||||
viewports: [ViewportItem!]!
|
viewports: [ViewportItem!]!
|
||||||
me: Parent!
|
me: Parent!
|
||||||
@@ -207,27 +616,81 @@ type Query {
|
|||||||
pageSize: Int = 20
|
pageSize: Int = 20
|
||||||
): [Notification!]!
|
): [Notification!]!
|
||||||
notificationPreferences: NotificationPreferences!
|
notificationPreferences: NotificationPreferences!
|
||||||
|
|
||||||
|
# Extended(21 个)
|
||||||
|
currentUser: Parent!
|
||||||
|
myChildren: [ChildBrief!]!
|
||||||
|
childSummary(childId: ID!): ChildSummary
|
||||||
|
childDetail(childId: ID!): ChildDetail
|
||||||
|
childAttendance(
|
||||||
|
childId: ID!
|
||||||
|
startDate: DateTime!
|
||||||
|
endDate: DateTime!
|
||||||
|
): [AttendanceRecord!]!
|
||||||
|
childExamResult(childId: ID!, examId: ID!): ExamResult
|
||||||
|
childClasses(childId: ID!): [ChildClass!]!
|
||||||
|
childReportCard(
|
||||||
|
childId: ID!
|
||||||
|
academicYearId: ID!
|
||||||
|
semester: Int!
|
||||||
|
): ReportCard
|
||||||
|
childGrowthArchive(childId: ID!, subject: String): GrowthArchive!
|
||||||
|
childWeakness(childId: ID!): [WeaknessItem!]!
|
||||||
|
childTrend(childId: ID!, period: TrendPeriod!): ChildTrend!
|
||||||
|
childLearningPath(childId: ID!): [LearningPathItem!]!
|
||||||
|
childErrorBookStats(childId: ID!): ErrorBookStats!
|
||||||
|
childTopWrongQuestions(childId: ID!, limit: Int): [WrongQuestion!]!
|
||||||
|
childWeakKps(childId: ID!, limit: Int): [WeakKp!]!
|
||||||
|
childMasterySummary(childId: ID!): MasterySummary!
|
||||||
|
childDiagnosticReports(childId: ID!): [DiagnosticReport!]!
|
||||||
|
childPracticeStats(childId: ID!): PracticeStats!
|
||||||
|
childPracticeSessions(childId: ID!, limit: Int): [PracticeSession!]!
|
||||||
|
childCoursePlans(childId: ID!): [CoursePlan!]!
|
||||||
|
childCoursePlanDetail(childId: ID!, planId: ID!): CoursePlanDetail
|
||||||
|
childLessonPlans(childId: ID!, subject: String): [LessonPlan!]!
|
||||||
|
childLessonPlanDetail(childId: ID!, planId: ID!): LessonPlanDetail
|
||||||
|
childElective(childId: ID!): [ElectiveCourse!]!
|
||||||
|
childLeaveRequests(childId: ID!): [LeaveRequestItem!]!
|
||||||
|
academicYears: [AcademicYear!]!
|
||||||
|
myNotifications(unreadOnly: Boolean, limit: Int): [MyNotification!]!
|
||||||
|
myNotificationPreferences: MyNotificationPreferences!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============ Mutation(6 个) ============
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
# Legacy(3 个)
|
||||||
|
selectChild(childId: ID!): SelectChildResult!
|
||||||
|
markNotificationRead(notificationId: ID!): Notification!
|
||||||
|
updateNotificationPreferences(
|
||||||
|
input: UpdateNotificationPreferencesInput!
|
||||||
|
): NotificationPreferences!
|
||||||
|
|
||||||
|
# Extended(6 个新 mutation,覆盖前端全部操作)
|
||||||
|
markAsRead(notificationId: ID!): MarkAsReadResult!
|
||||||
|
markAllAsRead: MarkAllAsReadResult!
|
||||||
|
switchChild(childId: ID!): SwitchChildResult!
|
||||||
|
updateMyNotificationPreferences(
|
||||||
|
parentId: ID!
|
||||||
|
preferences: JSON!
|
||||||
|
defaults: JSON
|
||||||
|
): UpdateNotificationPreferencesResult!
|
||||||
|
createLeaveRequest(input: LeaveRequestInput!): LeaveRequestItem!
|
||||||
|
exportChildGrades(childId: ID!, subject: String): ExportChildGradesResult!
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============ Inputs ============
|
||||||
|
|
||||||
input DateRangeInput {
|
input DateRangeInput {
|
||||||
start: DateTime!
|
start: DateTime!
|
||||||
end: DateTime!
|
end: DateTime!
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============ Mutation ============
|
enum TrendPeriod {
|
||||||
|
WEEK
|
||||||
type Mutation {
|
MONTH
|
||||||
selectChild(childId: ID!): SelectChildResult!
|
SEMESTER
|
||||||
markNotificationRead(notificationId: ID!): Notification!
|
YEAR
|
||||||
updateNotificationPreferences(
|
|
||||||
input: UpdateNotificationPreferencesInput!
|
|
||||||
): NotificationPreferences!
|
|
||||||
}
|
|
||||||
|
|
||||||
type SelectChildResult {
|
|
||||||
childId: ID!
|
|
||||||
selectedAt: DateTime!
|
|
||||||
audited: Boolean!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input UpdateNotificationPreferencesInput {
|
input UpdateNotificationPreferencesInput {
|
||||||
@@ -242,3 +705,17 @@ input NotificationEventTypesInput {
|
|||||||
attendanceAlert: Boolean
|
attendanceAlert: Boolean
|
||||||
schoolAnnouncement: Boolean
|
schoolAnnouncement: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input LeaveRequestInput {
|
||||||
|
childId: ID!
|
||||||
|
type: String!
|
||||||
|
startDate: DateTime!
|
||||||
|
endDate: DateTime!
|
||||||
|
reason: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelectChildResult {
|
||||||
|
childId: ID!
|
||||||
|
selectedAt: DateTime!
|
||||||
|
audited: Boolean!
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
# student-bff GraphQL Schema (v1)
|
# student-bff GraphQL Schema (v2)
|
||||||
#
|
#
|
||||||
# 负责人: ai04
|
# 负责人: ai04
|
||||||
# 仲裁依据: coord-final-decisions §2 B1-B8 + president-final-rulings §2.2
|
# 仲裁依据: coord-final-decisions §2 B1-B8 + president-final-rulings §2.2
|
||||||
# 存放路径: packages/shared-ts/contracts/graphql/student-bff.schema.graphql (president §2.2.1)
|
# 存放路径: packages/shared-ts/contracts/graphql/student-bff.schema.graphql (president §2.2.1)
|
||||||
# 起草与仲裁流程: ai04 起草 → coord 在批次 2 启动前仲裁第一版 → ai14 (student-portal) 消费
|
|
||||||
#
|
#
|
||||||
# 设计规范 (president §2.2.5):
|
# 设计规范 (president §2.2.5):
|
||||||
# - Query/Mutation 用 camelCase
|
# - Query/Mutation 用 camelCase
|
||||||
@@ -17,6 +16,12 @@
|
|||||||
# 降级模式 (president §2.6 方案 B):
|
# 降级模式 (president §2.6 方案 B):
|
||||||
# - 下游不可用时 success=true + error=null + data 内 degraded=true
|
# - 下游不可用时 success=true + error=null + data 内 degraded=true
|
||||||
# - 降级字段返回 null, 父对象加 degraded/degradedReason/degradedFields
|
# - 降级字段返回 null, 父对象加 degraded/degradedReason/degradedFields
|
||||||
|
#
|
||||||
|
# v2 变更:
|
||||||
|
# - 补全 22 个扩展 Query (examDetail/homeworkDetail/serverTime/mySchedule/...)
|
||||||
|
# - 补全 21 个扩展 Mutation (submitExam/saveExamDraft/updateProfile/...)
|
||||||
|
# - startPracticeSession 从 Query 移到 Mutation (语义为创建会话)
|
||||||
|
# - 总计 36 Query + 23 Mutation + 1 Subscription = 60 operations
|
||||||
|
|
||||||
scalar DateTime
|
scalar DateTime
|
||||||
scalar JSON
|
scalar JSON
|
||||||
@@ -197,6 +202,16 @@ type ExamListPayload implements Degradable {
|
|||||||
degradedFields: [String!]
|
degradedFields: [String!]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 考试详情 (含题目与学生提交)
|
||||||
|
type ExamDetailPayload implements Degradable {
|
||||||
|
exam: JSON
|
||||||
|
questions: [JSON!]
|
||||||
|
mySubmission: JSON
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 作业 (下游: core-edu HomeworkService)
|
# 作业 (下游: core-edu HomeworkService)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -251,6 +266,14 @@ type HomeworkListPayload implements Degradable {
|
|||||||
degradedFields: [String!]
|
degradedFields: [String!]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 作业详情
|
||||||
|
type HomeworkDetailPayload implements Degradable {
|
||||||
|
homework: JSON
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 成绩 (下游: core-edu GradeService)
|
# 成绩 (下游: core-edu GradeService)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -292,8 +315,16 @@ type GradeListPayload implements Degradable {
|
|||||||
degradedFields: [String!]
|
degradedFields: [String!]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 成绩报告卡
|
||||||
|
type ReportCardPayload implements Degradable {
|
||||||
|
reportCard: JSON
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 考勤 (下游: core-edu AttendanceService, P3 预留)
|
# 考勤 (下游: core-edu AttendanceService)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
enum AttendanceStatus {
|
enum AttendanceStatus {
|
||||||
@@ -337,6 +368,155 @@ type AttendanceListPayload implements Degradable {
|
|||||||
degradedFields: [String!]
|
degradedFields: [String!]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# 课表 / 请假 / 选课 / 课案 / 课程计划 (下游: core-edu + content)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# 服务器时间
|
||||||
|
type ServerTimePayload {
|
||||||
|
serverTime: String!
|
||||||
|
timezone: String!
|
||||||
|
timestamp: Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
# 课表
|
||||||
|
type ScheduleItem {
|
||||||
|
id: ID!
|
||||||
|
classId: ID!
|
||||||
|
className: String
|
||||||
|
subject: String!
|
||||||
|
date: DateTime!
|
||||||
|
startTime: DateTime!
|
||||||
|
endTime: DateTime!
|
||||||
|
teacher: TeacherBrief
|
||||||
|
room: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type SchedulePayload implements Degradable {
|
||||||
|
items: [ScheduleItem!]!
|
||||||
|
weekStart: String
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 请假记录
|
||||||
|
type LeaveRequest {
|
||||||
|
id: ID!
|
||||||
|
studentId: ID!
|
||||||
|
type: String!
|
||||||
|
startDate: DateTime!
|
||||||
|
endDate: DateTime!
|
||||||
|
reason: String!
|
||||||
|
status: String!
|
||||||
|
attachments: [String!]
|
||||||
|
createdAt: DateTime!
|
||||||
|
updatedAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type LeaveRequestsPayload implements Degradable {
|
||||||
|
requests: [LeaveRequest!]!
|
||||||
|
totalCount: Int!
|
||||||
|
pendingCount: Int!
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 选课记录
|
||||||
|
type ElectiveSelection {
|
||||||
|
id: ID!
|
||||||
|
studentId: ID!
|
||||||
|
courseId: ID!
|
||||||
|
courseName: String!
|
||||||
|
status: String!
|
||||||
|
selectedAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ElectiveSelectionsPayload implements Degradable {
|
||||||
|
selections: [ElectiveSelection!]!
|
||||||
|
totalCount: Int!
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 可选课程
|
||||||
|
type ElectiveCourse {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
subject: String!
|
||||||
|
description: String
|
||||||
|
capacity: Int!
|
||||||
|
enrolledCount: Int!
|
||||||
|
teacher: TeacherBrief
|
||||||
|
}
|
||||||
|
|
||||||
|
type AvailableElectiveCoursesPayload implements Degradable {
|
||||||
|
courses: [ElectiveCourse!]!
|
||||||
|
totalCount: Int!
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 课案
|
||||||
|
type LessonPlan {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
subject: String!
|
||||||
|
description: String
|
||||||
|
objectives: [String!]
|
||||||
|
content: JSON
|
||||||
|
createdBy: ID!
|
||||||
|
createdAt: DateTime!
|
||||||
|
updatedAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type LessonPlansPayload implements Degradable {
|
||||||
|
plans: [LessonPlan!]!
|
||||||
|
totalCount: Int!
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
type LessonPlanDetailPayload implements Degradable {
|
||||||
|
plan: LessonPlan
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 课程计划
|
||||||
|
type CoursePlan {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
subject: String!
|
||||||
|
description: String
|
||||||
|
startDate: DateTime!
|
||||||
|
endDate: DateTime!
|
||||||
|
objectives: [String!]
|
||||||
|
createdBy: ID!
|
||||||
|
createdAt: DateTime!
|
||||||
|
updatedAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CoursePlansPayload implements Degradable {
|
||||||
|
plans: [CoursePlan!]!
|
||||||
|
totalCount: Int!
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
type CoursePlanDetailPayload implements Degradable {
|
||||||
|
plan: CoursePlan
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 教材与章节 (下游: content, P4)
|
# 教材与章节 (下游: content, P4)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -491,8 +671,98 @@ type TrendPayload implements Degradable {
|
|||||||
degradedFields: [String!]
|
degradedFields: [String!]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 学生成长曲线
|
||||||
|
type StudentGrowthPayload implements Degradable {
|
||||||
|
studentId: ID!
|
||||||
|
subjectId: ID
|
||||||
|
points: [TrendPoint!]!
|
||||||
|
averageScore: Float
|
||||||
|
classAverage: Float
|
||||||
|
growthRate: Float
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 作业分析
|
||||||
|
type AssignmentAnalysisPayload implements Degradable {
|
||||||
|
analysis: JSON
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 个人资料
|
||||||
|
type MyProfilePayload implements Degradable {
|
||||||
|
profile: JSON
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 掌握度概览
|
||||||
|
type MasterySummaryPayload implements Degradable {
|
||||||
|
summary: JSON
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 诊断报告
|
||||||
|
type DiagnosticReportsPayload implements Degradable {
|
||||||
|
reports: [JSON!]!
|
||||||
|
totalCount: Int!
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 错题本
|
||||||
|
type ErrorBookItem {
|
||||||
|
id: ID!
|
||||||
|
questionId: ID!
|
||||||
|
subjectId: ID!
|
||||||
|
knowledgePointId: ID!
|
||||||
|
myAnswer: String
|
||||||
|
correctAnswer: String!
|
||||||
|
note: String
|
||||||
|
tags: [String!]
|
||||||
|
mastered: Boolean!
|
||||||
|
createdAt: DateTime!
|
||||||
|
updatedAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorBookPayload implements Degradable {
|
||||||
|
items: [ErrorBookItem!]!
|
||||||
|
totalCount: Int!
|
||||||
|
subjectStats: [JSON!]!
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 练习会话
|
||||||
|
type PracticeSession {
|
||||||
|
id: ID!
|
||||||
|
studentId: ID!
|
||||||
|
subjectId: ID!
|
||||||
|
status: String!
|
||||||
|
totalQuestions: Int!
|
||||||
|
answeredCount: Int!
|
||||||
|
startedAt: DateTime!
|
||||||
|
completedAt: DateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
type PracticeSessionsPayload implements Degradable {
|
||||||
|
sessions: [PracticeSession!]!
|
||||||
|
totalCount: Int!
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 通知 (下游: msg, P5)
|
# 通知 / 公告 (下游: msg, P5)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
enum NotificationType {
|
enum NotificationType {
|
||||||
@@ -550,6 +820,38 @@ type UnreadCountPayload implements Degradable {
|
|||||||
degradedFields: [String!]
|
degradedFields: [String!]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 公告
|
||||||
|
type Announcement {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
content: String!
|
||||||
|
authorId: ID!
|
||||||
|
status: String!
|
||||||
|
category: String
|
||||||
|
targetAudience: String
|
||||||
|
publishedAt: DateTime
|
||||||
|
createdAt: DateTime!
|
||||||
|
updatedAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnnouncementConnection {
|
||||||
|
edges: [AnnouncementEdge!]!
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
totalCount: Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnnouncementEdge {
|
||||||
|
node: Announcement!
|
||||||
|
cursor: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnnouncementDetailPayload implements Degradable {
|
||||||
|
announcement: Announcement
|
||||||
|
degraded: Boolean!
|
||||||
|
degradedReason: String
|
||||||
|
degradedFields: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# AI 答疑 (下游: ai, P5)
|
# AI 答疑 (下游: ai, P5)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -583,6 +885,13 @@ input AIChatInput {
|
|||||||
# Mutation 结果
|
# Mutation 结果
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
type MutationError {
|
||||||
|
code: String! # BFF_STUDENT_* 前缀
|
||||||
|
message: String!
|
||||||
|
traceId: String
|
||||||
|
i18nKey: String
|
||||||
|
}
|
||||||
|
|
||||||
type SubmitHomeworkResult {
|
type SubmitHomeworkResult {
|
||||||
success: Boolean!
|
success: Boolean!
|
||||||
submissionId: ID
|
submissionId: ID
|
||||||
@@ -599,15 +908,121 @@ type MarkNotificationReadResult {
|
|||||||
error: MutationError
|
error: MutationError
|
||||||
}
|
}
|
||||||
|
|
||||||
type MutationError {
|
# 通用 Mutation 结果
|
||||||
code: String! # BFF_STUDENT_* 前缀
|
type GenericMutationResult {
|
||||||
message: String!
|
success: Boolean!
|
||||||
traceId: String
|
error: MutationError
|
||||||
i18nKey: String
|
}
|
||||||
|
|
||||||
|
# 通知相关 Mutation 结果
|
||||||
|
type MarkAllAsReadResult {
|
||||||
|
success: Boolean!
|
||||||
|
markedCount: Int!
|
||||||
|
error: MutationError
|
||||||
|
}
|
||||||
|
|
||||||
|
# 考试相关 Mutation 结果
|
||||||
|
type SubmitExamResult {
|
||||||
|
success: Boolean!
|
||||||
|
submissionId: ID
|
||||||
|
examId: ID!
|
||||||
|
submittedAt: DateTime
|
||||||
|
score: Float
|
||||||
|
status: String
|
||||||
|
error: MutationError
|
||||||
|
}
|
||||||
|
|
||||||
|
type SaveExamDraftResult {
|
||||||
|
success: Boolean!
|
||||||
|
draftId: ID
|
||||||
|
examId: ID!
|
||||||
|
savedAt: DateTime
|
||||||
|
error: MutationError
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecordExamViolationResult {
|
||||||
|
success: Boolean!
|
||||||
|
violationId: ID
|
||||||
|
examId: ID!
|
||||||
|
violationType: String!
|
||||||
|
recordedAt: DateTime
|
||||||
|
error: MutationError
|
||||||
|
}
|
||||||
|
|
||||||
|
# 个人资料相关 Mutation 结果
|
||||||
|
type UpdateProfileResult {
|
||||||
|
success: Boolean!
|
||||||
|
profile: JSON
|
||||||
|
error: MutationError
|
||||||
|
}
|
||||||
|
|
||||||
|
# 作业延期申请
|
||||||
|
type RequestExtensionResult {
|
||||||
|
success: Boolean!
|
||||||
|
requestId: ID
|
||||||
|
homeworkId: ID!
|
||||||
|
status: String!
|
||||||
|
error: MutationError
|
||||||
|
}
|
||||||
|
|
||||||
|
# 班级相关 Mutation 结果
|
||||||
|
type JoinClassResult {
|
||||||
|
success: Boolean!
|
||||||
|
classId: ID!
|
||||||
|
joinedAt: DateTime
|
||||||
|
error: MutationError
|
||||||
|
}
|
||||||
|
|
||||||
|
type LeaveClassResult {
|
||||||
|
success: Boolean!
|
||||||
|
classId: ID!
|
||||||
|
leftAt: DateTime
|
||||||
|
error: MutationError
|
||||||
|
}
|
||||||
|
|
||||||
|
# 错题相关 Mutation 结果
|
||||||
|
type ErrorBookItemResult {
|
||||||
|
success: Boolean!
|
||||||
|
itemId: ID
|
||||||
|
error: MutationError
|
||||||
|
}
|
||||||
|
|
||||||
|
# 请假相关 Mutation 结果
|
||||||
|
type CreateLeaveRequestResult {
|
||||||
|
success: Boolean!
|
||||||
|
requestId: ID
|
||||||
|
status: String!
|
||||||
|
error: MutationError
|
||||||
|
}
|
||||||
|
|
||||||
|
# 选课相关 Mutation 结果
|
||||||
|
type SelectElectiveResult {
|
||||||
|
success: Boolean!
|
||||||
|
selectionId: ID
|
||||||
|
courseId: ID!
|
||||||
|
status: String!
|
||||||
|
error: MutationError
|
||||||
|
}
|
||||||
|
|
||||||
|
# 练习相关 Mutation 结果
|
||||||
|
type SubmitPracticeAnswerResult {
|
||||||
|
success: Boolean!
|
||||||
|
questionId: ID!
|
||||||
|
isCorrect: Boolean
|
||||||
|
correctAnswer: String
|
||||||
|
explanation: String
|
||||||
|
error: MutationError
|
||||||
|
}
|
||||||
|
|
||||||
|
type StartPracticeSessionResult {
|
||||||
|
success: Boolean!
|
||||||
|
session: JSON
|
||||||
|
questions: [JSON!]!
|
||||||
|
error: MutationError
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Query 根类型
|
# Query 根类型 (36 Queries)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
@@ -740,10 +1155,131 @@ type Query {
|
|||||||
# @permission: STUDENT_AI_CHAT
|
# @permission: STUDENT_AI_CHAT
|
||||||
# @dataScope: OWN
|
# @dataScope: OWN
|
||||||
aiChat(input: AIChatInput!): AIChatPayload!
|
aiChat(input: AIChatInput!): AIChatPayload!
|
||||||
|
|
||||||
|
# ===== 扩展 Query (v2 新增, P3-P5) =====
|
||||||
|
|
||||||
|
"""考试详情 (含题目与学生提交, core-edu.ExamService.GetExam)"""
|
||||||
|
# @permission: EXAM_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
examDetail(examId: ID!): ExamDetailPayload!
|
||||||
|
|
||||||
|
"""作业详情 (core-edu.HomeworkService.GetHomework)"""
|
||||||
|
# @permission: HOMEWORK_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
homeworkDetail(homeworkId: ID!): HomeworkDetailPayload!
|
||||||
|
|
||||||
|
"""服务器时间 (BFF 本地, 无下游)"""
|
||||||
|
serverTime: ServerTimePayload!
|
||||||
|
|
||||||
|
"""我的课表 (周视图, core-edu.ScheduleService.GetScheduleByStudent)"""
|
||||||
|
# @permission: SCHEDULE_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
mySchedule(weekStart: DateTime): SchedulePayload!
|
||||||
|
|
||||||
|
"""学生成长曲线 (data-ana.GetStudentGrowth)"""
|
||||||
|
# @permission: ANALYTICS_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
studentGrowth(
|
||||||
|
subjectId: ID
|
||||||
|
startDate: DateTime
|
||||||
|
endDate: DateTime
|
||||||
|
): StudentGrowthPayload!
|
||||||
|
|
||||||
|
"""作业分析 (data-ana.GetAssignmentAnalysis)"""
|
||||||
|
# @permission: ANALYTICS_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
assignmentAnalysis(homeworkId: ID): AssignmentAnalysisPayload!
|
||||||
|
|
||||||
|
"""个人资料 (iam.GetUserProfile)"""
|
||||||
|
# @permission: AUTH_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
myProfile: MyProfilePayload!
|
||||||
|
|
||||||
|
"""掌握度概览 (data-ana.GetMasterySummary)"""
|
||||||
|
# @permission: ANALYTICS_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
myMasterySummary(subjectId: ID): MasterySummaryPayload!
|
||||||
|
|
||||||
|
"""诊断报告列表 (data-ana.ListDiagnosticReports)"""
|
||||||
|
# @permission: ANALYTICS_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
myDiagnosticReports: DiagnosticReportsPayload!
|
||||||
|
|
||||||
|
"""错题本 (data-ana.ListErrorBookItems)"""
|
||||||
|
# @permission: ANALYTICS_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
myErrorBook(
|
||||||
|
subjectId: ID
|
||||||
|
mastered: Boolean
|
||||||
|
page: Int = 1
|
||||||
|
pageSize: Int = 20
|
||||||
|
): ErrorBookPayload!
|
||||||
|
|
||||||
|
"""公告列表 (msg.ListAnnouncements)"""
|
||||||
|
# @permission: ANNOUNCEMENT_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
announcements(
|
||||||
|
first: Int = 20
|
||||||
|
category: String
|
||||||
|
): AnnouncementConnection!
|
||||||
|
|
||||||
|
"""公告详情 (msg.GetAnnouncement)"""
|
||||||
|
# @permission: ANNOUNCEMENT_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
announcementDetail(announcementId: ID!): AnnouncementDetailPayload!
|
||||||
|
|
||||||
|
"""我的请假记录 (core-edu.ListLeaveRequestsByStudent)"""
|
||||||
|
# @permission: LEAVE_REQUEST_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
myLeaveRequests: LeaveRequestsPayload!
|
||||||
|
|
||||||
|
"""我的选课记录 (content.ListElectiveSelectionsByStudent)"""
|
||||||
|
# @permission: ELECTIVE_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
myElectiveSelections: ElectiveSelectionsPayload!
|
||||||
|
|
||||||
|
"""可选课程 (content.ListAvailableElectiveCourses)"""
|
||||||
|
# @permission: ELECTIVE_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
availableElectiveCourses: AvailableElectiveCoursesPayload!
|
||||||
|
|
||||||
|
"""我的课案 (content.ListLessonPlansByStudent)"""
|
||||||
|
# @permission: LESSON_PLAN_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
myLessonPlans: LessonPlansPayload!
|
||||||
|
|
||||||
|
"""课案详情 (content.GetLessonPlan)"""
|
||||||
|
# @permission: LESSON_PLAN_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
lessonPlanDetail(lessonPlanId: ID!): LessonPlanDetailPayload!
|
||||||
|
|
||||||
|
"""我的课程计划 (content.ListCoursePlansByStudent)"""
|
||||||
|
# @permission: COURSE_PLAN_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
myCoursePlans: CoursePlansPayload!
|
||||||
|
|
||||||
|
"""课程计划详情 (content.GetCoursePlan)"""
|
||||||
|
# @permission: COURSE_PLAN_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
coursePlanDetail(coursePlanId: ID!): CoursePlanDetailPayload!
|
||||||
|
|
||||||
|
"""成绩报告卡 (core-edu.GradeService.GetReportCard)"""
|
||||||
|
# @permission: GRADE_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
myReportCard(
|
||||||
|
examId: ID
|
||||||
|
term: String
|
||||||
|
academicYear: String
|
||||||
|
): ReportCardPayload!
|
||||||
|
|
||||||
|
"""我的练习会话列表 (data-ana.ListPracticeSessionsByStudent)"""
|
||||||
|
# @permission: ANALYTICS_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
myPracticeSessions: PracticeSessionsPayload!
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Mutation 根类型
|
# Mutation 根类型 (23 Mutations)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
@@ -760,6 +1296,120 @@ type Mutation {
|
|||||||
markNotificationAsRead(
|
markNotificationAsRead(
|
||||||
input: MarkNotificationReadInput!
|
input: MarkNotificationReadInput!
|
||||||
): MarkNotificationReadResult!
|
): MarkNotificationReadResult!
|
||||||
|
|
||||||
|
# ===== 扩展 Mutation (v2 新增, P3-P5) =====
|
||||||
|
|
||||||
|
"""标记通知已读 (别名, msg.MarkAsRead)"""
|
||||||
|
# @permission: NOTIFICATION_UPDATE
|
||||||
|
# @dataScope: OWN
|
||||||
|
markAsRead(input: MarkAsReadInput!): MarkNotificationReadResult!
|
||||||
|
|
||||||
|
"""全部通知标记已读 (msg.MarkAllNotificationsAsRead)"""
|
||||||
|
# @permission: NOTIFICATION_UPDATE
|
||||||
|
# @dataScope: OWN
|
||||||
|
markAllAsRead(input: MarkAllAsReadInput!): MarkAllAsReadResult!
|
||||||
|
|
||||||
|
"""更新通知偏好 (msg.UpdateNotificationPreferences)"""
|
||||||
|
# @permission: NOTIFICATION_UPDATE
|
||||||
|
# @dataScope: OWN
|
||||||
|
updateNotificationPreference(
|
||||||
|
input: UpdateNotificationPreferenceInput!
|
||||||
|
): GenericMutationResult!
|
||||||
|
|
||||||
|
"""提交考试 (含幂等键, core-edu.ExamService.SubmitExam)"""
|
||||||
|
# @permission: EXAM_SUBMIT
|
||||||
|
# @dataScope: OWN
|
||||||
|
submitExam(input: SubmitExamInput!): SubmitExamResult!
|
||||||
|
|
||||||
|
"""保存考试草稿 (core-edu.ExamService.SaveExamDraft)"""
|
||||||
|
# @permission: EXAM_SUBMIT
|
||||||
|
# @dataScope: OWN
|
||||||
|
saveExamDraft(input: SaveExamDraftInput!): SaveExamDraftResult!
|
||||||
|
|
||||||
|
"""记录考试违规 (防作弊, core-edu.ExamService.RecordExamViolation)"""
|
||||||
|
# @permission: EXAM_RECORD_VIOLATION
|
||||||
|
# @dataScope: OWN
|
||||||
|
recordExamViolation(input: RecordViolationInput!): RecordExamViolationResult!
|
||||||
|
|
||||||
|
"""记录粘贴事件 (防作弊, core-edu.ExamService.RecordPasteEvent)"""
|
||||||
|
# @permission: EXAM_SUBMIT
|
||||||
|
# @dataScope: OWN
|
||||||
|
recordPasteEvent(input: RecordPasteEventInput!): RecordExamViolationResult!
|
||||||
|
|
||||||
|
"""更新个人资料 (iam.UpdateUserProfile)"""
|
||||||
|
# @permission: AUTH_UPDATE
|
||||||
|
# @dataScope: OWN
|
||||||
|
updateProfile(input: UpdateProfileInput!): UpdateProfileResult!
|
||||||
|
|
||||||
|
"""修改密码 (iam.ChangePassword)"""
|
||||||
|
# @permission: AUTH_UPDATE
|
||||||
|
# @dataScope: OWN
|
||||||
|
changePassword(input: ChangePasswordInput!): GenericMutationResult!
|
||||||
|
|
||||||
|
"""作业延期申请 (core-edu.RequestHomeworkExtension)"""
|
||||||
|
# @permission: HOMEWORK_UPDATE
|
||||||
|
# @dataScope: OWN
|
||||||
|
requestExtension(input: RequestExtensionInput!): RequestExtensionResult!
|
||||||
|
|
||||||
|
"""加入班级 (core-edu.JoinClass)"""
|
||||||
|
# @permission: CLASS_JOIN
|
||||||
|
# @dataScope: OWN
|
||||||
|
joinClass(input: JoinClassInput!): JoinClassResult!
|
||||||
|
|
||||||
|
"""退出班级 (core-edu.LeaveClass)"""
|
||||||
|
# @permission: CLASS_LEAVE
|
||||||
|
# @dataScope: OWN
|
||||||
|
leaveClass(input: LeaveClassInput!): LeaveClassResult!
|
||||||
|
|
||||||
|
"""添加错题 (data-ana.AddErrorBookItem)"""
|
||||||
|
# @permission: ANALYTICS_UPDATE
|
||||||
|
# @dataScope: OWN
|
||||||
|
addErrorBookItem(input: AddErrorBookItemInput!): ErrorBookItemResult!
|
||||||
|
|
||||||
|
"""更新错题 (data-ana.UpdateErrorBookItem)"""
|
||||||
|
# @permission: ANALYTICS_UPDATE
|
||||||
|
# @dataScope: OWN
|
||||||
|
updateErrorBookItem(input: UpdateErrorBookItemInput!): ErrorBookItemResult!
|
||||||
|
|
||||||
|
"""删除错题 (data-ana.DeleteErrorBookItem)"""
|
||||||
|
# @permission: ANALYTICS_UPDATE
|
||||||
|
# @dataScope: OWN
|
||||||
|
deleteErrorBookItem(input: DeleteErrorBookItemInput!): ErrorBookItemResult!
|
||||||
|
|
||||||
|
"""标记公告已读 (msg.MarkAnnouncementAsRead)"""
|
||||||
|
# @permission: ANNOUNCEMENT_READ
|
||||||
|
# @dataScope: OWN
|
||||||
|
markAnnouncementRead(input: MarkAnnouncementReadInput!): GenericMutationResult!
|
||||||
|
|
||||||
|
"""创建请假申请 (core-edu.CreateLeaveRequest)"""
|
||||||
|
# @permission: LEAVE_REQUEST_CREATE
|
||||||
|
# @dataScope: OWN
|
||||||
|
createLeaveRequest(input: CreateLeaveRequestInput!): CreateLeaveRequestResult!
|
||||||
|
|
||||||
|
"""取消请假申请 (core-edu.CancelLeaveRequest)"""
|
||||||
|
# @permission: LEAVE_REQUEST_UPDATE
|
||||||
|
# @dataScope: OWN
|
||||||
|
cancelLeaveRequest(input: CancelLeaveRequestInput!): CreateLeaveRequestResult!
|
||||||
|
|
||||||
|
"""选择课程 (content.SelectElectiveCourse)"""
|
||||||
|
# @permission: ELECTIVE_SELECT
|
||||||
|
# @dataScope: OWN
|
||||||
|
selectElectiveCourse(input: SelectElectiveInput!): SelectElectiveResult!
|
||||||
|
|
||||||
|
"""退选课程 (content.DropElectiveCourse)"""
|
||||||
|
# @permission: ELECTIVE_DROP
|
||||||
|
# @dataScope: OWN
|
||||||
|
dropElectiveCourse(input: DropElectiveInput!): SelectElectiveResult!
|
||||||
|
|
||||||
|
"""启动练习会话 (data-ana.StartPracticeSession, 从 Query 移到 Mutation)"""
|
||||||
|
# @permission: ANALYTICS_UPDATE
|
||||||
|
# @dataScope: OWN
|
||||||
|
startPracticeSession(input: StartPracticeSessionInput!): StartPracticeSessionResult!
|
||||||
|
|
||||||
|
"""提交练习答案 (data-ana.SubmitPracticeAnswer)"""
|
||||||
|
# @permission: ANALYTICS_UPDATE
|
||||||
|
# @dataScope: OWN
|
||||||
|
submitPracticeAnswer(input: SubmitPracticeAnswerInput!): SubmitPracticeAnswerResult!
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -783,6 +1433,182 @@ input MarkNotificationReadInput {
|
|||||||
userId: ID! # 必须与 x-user-id 一致 (B4 越权防御)
|
userId: ID! # 必须与 x-user-id 一致 (B4 越权防御)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input MarkAsReadInput {
|
||||||
|
notificationId: ID!
|
||||||
|
studentId: ID # 可选, 若提供必须与 x-user-id 一致
|
||||||
|
}
|
||||||
|
|
||||||
|
input MarkAllAsReadInput {
|
||||||
|
studentId: ID # 可选, 若提供必须与 x-user-id 一致
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateNotificationPreferenceInput {
|
||||||
|
channel: NotificationChannel!
|
||||||
|
enabled: Boolean!
|
||||||
|
categories: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
input SubmitExamInput {
|
||||||
|
examId: ID!
|
||||||
|
studentId: ID # 可选, 若提供必须与 x-user-id 一致
|
||||||
|
answers: [ExamAnswerInput!]!
|
||||||
|
idempotencyKey: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input ExamAnswerInput {
|
||||||
|
questionId: ID!
|
||||||
|
content: String!
|
||||||
|
attachments: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
input SaveExamDraftInput {
|
||||||
|
examId: ID!
|
||||||
|
studentId: ID # 可选, 若提供必须与 x-user-id 一致
|
||||||
|
answers: [ExamAnswerInput!]!
|
||||||
|
draftId: ID
|
||||||
|
}
|
||||||
|
|
||||||
|
input RecordViolationInput {
|
||||||
|
examId: ID!
|
||||||
|
studentId: ID # 可选, 若提供必须与 x-user-id 一致
|
||||||
|
violationType: ViolationType!
|
||||||
|
severity: ViolationSeverity!
|
||||||
|
details: String
|
||||||
|
timestamp: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ViolationType {
|
||||||
|
TAB_SWITCH
|
||||||
|
COPY_PASTE
|
||||||
|
WINDOW_BLUR
|
||||||
|
FULLSCREEN_EXIT
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ViolationSeverity {
|
||||||
|
LOW
|
||||||
|
MEDIUM
|
||||||
|
HIGH
|
||||||
|
}
|
||||||
|
|
||||||
|
input RecordPasteEventInput {
|
||||||
|
examId: ID!
|
||||||
|
studentId: ID # 可选, 若提供必须与 x-user-id 一致
|
||||||
|
questionId: ID!
|
||||||
|
pastedContent: String!
|
||||||
|
timestamp: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateProfileInput {
|
||||||
|
name: String
|
||||||
|
avatar: String
|
||||||
|
phone: String
|
||||||
|
address: String
|
||||||
|
bio: String
|
||||||
|
birthday: String
|
||||||
|
gender: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input ChangePasswordInput {
|
||||||
|
currentPassword: String!
|
||||||
|
newPassword: String!
|
||||||
|
studentId: ID # 可选, 若提供必须与 x-user-id 一致
|
||||||
|
}
|
||||||
|
|
||||||
|
input RequestExtensionInput {
|
||||||
|
homeworkId: ID!
|
||||||
|
studentId: ID # 可选, 若提供必须与 x-user-id 一致
|
||||||
|
reason: String!
|
||||||
|
requestedDays: Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
input JoinClassInput {
|
||||||
|
classCode: String!
|
||||||
|
studentId: ID # 可选, 若提供必须与 x-user-id 一致
|
||||||
|
}
|
||||||
|
|
||||||
|
input LeaveClassInput {
|
||||||
|
classId: ID!
|
||||||
|
studentId: ID # 可选, 若提供必须与 x-user-id 一致
|
||||||
|
reason: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input AddErrorBookItemInput {
|
||||||
|
questionId: ID!
|
||||||
|
subjectId: ID!
|
||||||
|
knowledgePointId: ID!
|
||||||
|
myAnswer: String
|
||||||
|
correctAnswer: String!
|
||||||
|
note: String
|
||||||
|
tags: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateErrorBookItemInput {
|
||||||
|
itemId: ID!
|
||||||
|
note: String
|
||||||
|
tags: [String!]
|
||||||
|
mastered: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
input DeleteErrorBookItemInput {
|
||||||
|
itemId: ID!
|
||||||
|
}
|
||||||
|
|
||||||
|
input MarkAnnouncementReadInput {
|
||||||
|
announcementId: ID!
|
||||||
|
studentId: ID # 可选, 若提供必须与 x-user-id 一致
|
||||||
|
}
|
||||||
|
|
||||||
|
input CreateLeaveRequestInput {
|
||||||
|
type: LeaveRequestType!
|
||||||
|
startDate: DateTime!
|
||||||
|
endDate: DateTime!
|
||||||
|
reason: String!
|
||||||
|
attachments: [String!]
|
||||||
|
studentId: ID # 可选, 若提供必须与 x-user-id 一致
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LeaveRequestType {
|
||||||
|
SICK
|
||||||
|
PERSONAL
|
||||||
|
FAMILY
|
||||||
|
OTHER
|
||||||
|
}
|
||||||
|
|
||||||
|
input CancelLeaveRequestInput {
|
||||||
|
requestId: ID!
|
||||||
|
studentId: ID # 可选, 若提供必须与 x-user-id 一致
|
||||||
|
}
|
||||||
|
|
||||||
|
input SelectElectiveInput {
|
||||||
|
courseId: ID!
|
||||||
|
studentId: ID # 可选, 若提供必须与 x-user-id 一致
|
||||||
|
}
|
||||||
|
|
||||||
|
input DropElectiveInput {
|
||||||
|
courseId: ID!
|
||||||
|
studentId: ID # 可选, 若提供必须与 x-user-id 一致
|
||||||
|
}
|
||||||
|
|
||||||
|
input StartPracticeSessionInput {
|
||||||
|
subjectId: ID!
|
||||||
|
knowledgePointIds: [ID!]
|
||||||
|
difficulty: PracticeDifficulty
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PracticeDifficulty {
|
||||||
|
EASY
|
||||||
|
MEDIUM
|
||||||
|
HARD
|
||||||
|
ADAPTIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
input SubmitPracticeAnswerInput {
|
||||||
|
sessionId: ID!
|
||||||
|
questionId: ID!
|
||||||
|
answer: String!
|
||||||
|
timeSpent: Int
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# AI 流式答疑 (SSE Subscription, P5)
|
# AI 流式答疑 (SSE Subscription, P5)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -348,7 +348,7 @@ export class DownstreamClient {
|
|||||||
resolveWait = resolve;
|
resolveWait = resolve;
|
||||||
});
|
});
|
||||||
if (result.done) break;
|
if (result.done) break;
|
||||||
yield result.value;
|
yield (result as { done: false; value: TResponse }).value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (streamError) {
|
if (streamError) {
|
||||||
|
|||||||
@@ -9,7 +9,14 @@
|
|||||||
*
|
*
|
||||||
* 该 logger 也作为 DownstreamClient 默认 logger, 避免循环依赖.
|
* 该 logger 也作为 DownstreamClient 默认 logger, 避免循环依赖.
|
||||||
*/
|
*/
|
||||||
import pino, { type Logger as PinoLogger, type LoggerOptions } from "pino";
|
import * as pinoNs from "pino";
|
||||||
|
import { type Logger as PinoLogger, type LoggerOptions } from "pino";
|
||||||
|
|
||||||
|
// pino 在 NodeNext + ESM 模式下 default import 丢失 callable 签名 (declaration merging 失效)
|
||||||
|
// 通过 namespace import + 显式类型断言恢复可调用性
|
||||||
|
type PinoFn = (options?: LoggerOptions | unknown) => PinoLogger;
|
||||||
|
const pino = ((pinoNs as unknown as { default: PinoFn }).default ??
|
||||||
|
(pinoNs as unknown as PinoFn)) as PinoFn;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认日志级别 (可通过环境变量 LOG_LEVEL 覆盖).
|
* 默认日志级别 (可通过环境变量 LOG_LEVEL 覆盖).
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { Module, type DynamicModule } from "@nestjs/common";
|
import { Module, type DynamicModule } from "@nestjs/common";
|
||||||
import pino from "pino";
|
import * as pinoNs from "pino";
|
||||||
|
import type { Logger as PinoLogger, LoggerOptions } from "pino";
|
||||||
|
|
||||||
|
// pino 在 NodeNext + ESM 模式下 default import 丢失 callable 签名 (declaration merging 失效)
|
||||||
|
// 通过 namespace import + 显式类型断言恢复可调用性
|
||||||
|
type PinoFn = (options?: LoggerOptions | unknown) => PinoLogger;
|
||||||
|
const pino = ((pinoNs as unknown as { default: PinoFn }).default ??
|
||||||
|
(pinoNs as unknown as PinoFn)) as PinoFn;
|
||||||
import { OutboxService } from "./outbox.service.js";
|
import { OutboxService } from "./outbox.service.js";
|
||||||
import { OutboxPublisher } from "./publisher.js";
|
import { OutboxPublisher } from "./publisher.js";
|
||||||
import { createOutboxTable } from "./schema.js";
|
import { createOutboxTable } from "./schema.js";
|
||||||
|
|||||||
Reference in New Issue
Block a user