// TeacherService — GraphQL Resolver 的业务逻辑层(B1 裁决:P2 起 GraphQL) // 通过 IamClient 调下游 iam gRPC(B2 裁决:首次实现即 gRPC) // P2: 仅 iam 数据;P3+ 扩展 core-edu / content / data-ana / msg / ai import { Injectable, Inject } from "@nestjs/common"; import { IAM_CLIENT } from "../clients/iam/iam-client.interface.js"; import type { IamClient } from "../clients/iam/iam-client.interface.js"; import type { CallContext } from "../clients/types.js"; import type { UserInfo, ViewportItem, EffectivePermissions, } from "../clients/iam/iam.types.js"; import { logger } from "../shared/observability/logger.js"; /** GraphQL User 类型(聚合 UserInfo + EffectivePermissions) */ export interface GraphQLUser { id: string; email: string; name: string; roles: string[]; permissions: string[]; dataScope: string; } /** GraphQL DashboardData 类型 */ export interface DashboardData { user: GraphQLUser | null; classes: unknown[]; viewports: ViewportItem[]; stats: { totalExams: number; pendingGrading: number; todayHomework: number; } | null; } /** GraphQL Class 类型(P2 mock,P3+ core-edu 真实数据) */ export interface ClassInfo { id: string; name: string; gradeId: string; } /** P2 mock 班级数据(president §3.5:P2 班级列表来自 iam 数据,P3+ core-edu) */ const MOCK_CLASSES: ClassInfo[] = [ { id: "class-001", name: "三年级1班", gradeId: "grade-3" }, { id: "class-002", name: "三年级2班", gradeId: "grade-3" }, { id: "class-003", name: "三年级3班", gradeId: "grade-3" }, ]; @Injectable() export class TeacherService { constructor(@Inject(IAM_CLIENT) private readonly iam: IamClient) {} /** 获取当前用户信息(聚合 iam.GetUserInfo + GetEffectivePermissions) */ async getCurrentUser(ctx: CallContext): Promise { const [userInfo, perms] = await Promise.all([ this.iam.getUserInfo(ctx), this.iam.getEffectivePermissions(ctx), ]); return { id: userInfo.id, email: userInfo.email, name: userInfo.name, roles: userInfo.roles, permissions: perms.permissions, dataScope: perms.dataScope, }; } /** 获取视口配置(iam.GetViewports) */ async getViewports(ctx: CallContext): Promise { return this.iam.getViewports(ctx); } /** * 获取 Dashboard 聚合数据(president §2.8:P2 仅调 iam gRPC) * P2: user + viewports 有数据,classes 返回 mock,stats 返回 null + warning * P3+: classes 来自 core-edu,stats 来自 data-ana.GetTeacherDashboard */ async getDashboard(ctx: CallContext): Promise { const [user, viewports] = await Promise.all([ this.getCurrentUser(ctx), this.iam.getViewports(ctx), ]); // P2: classes 返回 mock 数据(P3+ 替换为 core-edu.GetClassesByTeacher) // P2: stats 返回 null(P4+ 替换为 data-ana.GetTeacherDashboard) logger.warn( { userId: ctx.userId, phase: "P2" }, "Dashboard P2: classes using mock, stats unavailable (field_unavailable_in_p2)", ); return { user, viewports, classes: MOCK_CLASSES, stats: null, }; } /** 获取教师班级列表(P2: mock;P3+: core-edu.GetClassesByTeacher) */ async getClasses(ctx: CallContext): Promise { logger.warn( { userId: ctx.userId, phase: "P2" }, "Classes P2: using mock data (core-edu not ready, field_unavailable_in_p2)", ); return MOCK_CLASSES; } /** 获取单个班级详情(P2: mock;P3+: core-edu) */ async getClass(ctx: CallContext, classId: string): Promise { logger.warn( { userId: ctx.userId, classId, phase: "P2" }, "Class P2: using mock data (core-edu not ready, field_unavailable_in_p2)", ); return MOCK_CLASSES.find((c) => c.id === classId) ?? null; } } /** 导出 EffectivePermissions 类型供外部使用 */ export type { EffectivePermissions, UserInfo };