722 lines
13 KiB
GraphQL
722 lines
13 KiB
GraphQL
# parent-bff GraphQL Schema(SDL 契约,对齐 02-architecture-design.md §4.2)
|
||
#
|
||
# 本文件是 parent-bff GraphQL 端点的契约唯一源(single source of truth)。
|
||
# - 前端 parent-portal 引用本文件生成 TypeScript 类型
|
||
# - 后端 parent-bff 引用本文件加载为 GraphQLSchema
|
||
# - CI 强制检测 breaking change(与 main 分支对比)
|
||
#
|
||
# 仲裁依据:
|
||
# - U3:GraphQL P2 立即引入(parent-bff P4 直接用 GraphQL,不走 REST 过渡)
|
||
# - C1:错误码前缀 BFF_PARENT_
|
||
# - U4:BFF 豁免 @RequirePermission,仅校验 x-user-id + ChildGuard
|
||
# - 02 §9 #5:depth ≤ 7 / cost ≤ 1000
|
||
#
|
||
# 版本:v2(32 Query + 6 Mutation,对齐 parent-portal 全部 operations)
|
||
|
||
scalar DateTime
|
||
scalar JSON
|
||
|
||
# ============ Legacy Types ============
|
||
|
||
type Parent {
|
||
id: ID!
|
||
email: String!
|
||
name: String!
|
||
avatar: String
|
||
roles: [String!]!
|
||
permissions: [String!]
|
||
dataScope: DataScope!
|
||
schoolId: ID
|
||
}
|
||
|
||
enum DataScope {
|
||
SELF
|
||
CHILDREN
|
||
CLASS
|
||
GRADE
|
||
SCHOOL
|
||
DISTRICT
|
||
ALL
|
||
}
|
||
|
||
type ViewportItem {
|
||
key: String!
|
||
label: String!
|
||
route: String!
|
||
icon: String
|
||
sortOrder: String!
|
||
requiredPermission: String
|
||
}
|
||
|
||
type Child {
|
||
id: ID!
|
||
name: String!
|
||
grade: String!
|
||
class: ClassInfo!
|
||
lastGrade: Grade
|
||
grades(page: Int = 1, pageSize: Int = 20, subject: String): GradePage!
|
||
homework: [Homework!]!
|
||
exams: [Exam!]!
|
||
analytics: ChildAnalytics!
|
||
}
|
||
|
||
type ClassInfo {
|
||
id: ID!
|
||
name: String!
|
||
gradeId: String!
|
||
}
|
||
|
||
type Grade {
|
||
id: ID!
|
||
examId: ID!
|
||
examTitle: String!
|
||
subject: String!
|
||
score: Float!
|
||
rank: Int
|
||
gradedAt: DateTime!
|
||
}
|
||
|
||
type GradePage {
|
||
items: [Grade!]!
|
||
pagination: Pagination!
|
||
}
|
||
|
||
type Pagination {
|
||
page: Int!
|
||
pageSize: Int!
|
||
total: Int!
|
||
}
|
||
|
||
type Homework {
|
||
id: ID!
|
||
title: String!
|
||
classId: ID!
|
||
dueDate: DateTime!
|
||
status: HomeworkStatus!
|
||
submittedAt: DateTime
|
||
}
|
||
|
||
enum HomeworkStatus {
|
||
NOT_SUBMITTED
|
||
SUBMITTED
|
||
GRADED
|
||
OVERDUE
|
||
}
|
||
|
||
type Exam {
|
||
id: ID!
|
||
title: String!
|
||
classId: ID!
|
||
status: ExamStatus!
|
||
examDate: DateTime!
|
||
publishedAt: DateTime
|
||
}
|
||
|
||
enum ExamStatus {
|
||
DRAFT
|
||
PUBLISHED
|
||
IN_PROGRESS
|
||
GRADING
|
||
SCORED
|
||
ARCHIVED
|
||
}
|
||
|
||
type ChildAnalytics {
|
||
childId: ID!
|
||
weakness: [WeaknessTopic!]!
|
||
trend: [TrendPoint!]!
|
||
classRank: Int
|
||
classAverage: Float
|
||
}
|
||
|
||
type WeaknessTopic {
|
||
knowledgePointId: ID!
|
||
name: String!
|
||
subject: String!
|
||
masteryRate: Float!
|
||
}
|
||
|
||
type TrendPoint {
|
||
date: DateTime!
|
||
score: Float!
|
||
subject: String
|
||
}
|
||
|
||
type Notification {
|
||
id: ID!
|
||
type: NotificationType!
|
||
title: String!
|
||
content: String!
|
||
read: Boolean!
|
||
childId: ID
|
||
createdAt: DateTime!
|
||
}
|
||
|
||
enum NotificationType {
|
||
SYSTEM
|
||
EXAM
|
||
HOMEWORK
|
||
GRADE
|
||
ATTENDANCE
|
||
ANNOUNCEMENT
|
||
}
|
||
|
||
type NotificationPreferences {
|
||
channels: [NotificationChannel!]!
|
||
eventTypes: NotificationEventTypes!
|
||
}
|
||
|
||
enum NotificationChannel {
|
||
APP
|
||
SMS
|
||
EMAIL
|
||
WECHAT
|
||
}
|
||
|
||
type NotificationEventTypes {
|
||
gradeReleased: Boolean!
|
||
homeworkGraded: Boolean!
|
||
examPublished: Boolean!
|
||
attendanceAlert: Boolean!
|
||
schoolAnnouncement: Boolean!
|
||
}
|
||
|
||
type DashboardData {
|
||
parent: Parent
|
||
children: [Child!]!
|
||
viewports: [ViewportItem!]!
|
||
unreadNotifications: Int!
|
||
degraded: Boolean!
|
||
}
|
||
|
||
# ============ 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 {
|
||
# Legacy(11 个)
|
||
dashboard: DashboardData!
|
||
viewports: [ViewportItem!]!
|
||
me: Parent!
|
||
children: [Child!]!
|
||
child(childId: ID!): Child
|
||
childGrades(
|
||
childId: ID!
|
||
page: Int = 1
|
||
pageSize: Int = 20
|
||
subject: String
|
||
): GradePage!
|
||
childHomework(childId: ID!): [Homework!]!
|
||
childExams(childId: ID!): [Exam!]!
|
||
childAnalytics(childId: ID!, dateRange: DateRangeInput): ChildAnalytics!
|
||
notifications(
|
||
unreadOnly: Boolean
|
||
page: Int = 1
|
||
pageSize: Int = 20
|
||
): [Notification!]!
|
||
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 {
|
||
start: DateTime!
|
||
end: DateTime!
|
||
}
|
||
|
||
enum TrendPeriod {
|
||
WEEK
|
||
MONTH
|
||
SEMESTER
|
||
YEAR
|
||
}
|
||
|
||
input UpdateNotificationPreferencesInput {
|
||
channels: [NotificationChannel!]!
|
||
eventTypes: NotificationEventTypesInput!
|
||
}
|
||
|
||
input NotificationEventTypesInput {
|
||
gradeReleased: Boolean
|
||
homeworkGraded: Boolean
|
||
examPublished: Boolean
|
||
attendanceAlert: Boolean
|
||
schoolAnnouncement: Boolean
|
||
}
|
||
|
||
input LeaveRequestInput {
|
||
childId: ID!
|
||
type: String!
|
||
startDate: DateTime!
|
||
endDate: DateTime!
|
||
reason: String!
|
||
}
|
||
|
||
type SelectChildResult {
|
||
childId: ID!
|
||
selectedAt: DateTime!
|
||
audited: Boolean!
|
||
}
|