fix: code compliance audit and fix across all services
Some checks failed
CI / quality-ts (push) Failing after 48s
CI / quality-go (push) Failing after 4s
CI / quality-proto (push) Failing after 2s
CI / deploy (push) Has been skipped

NestJS (6 services): implement @RequirePermission decorator with
SetMetadata+Reflector, register APP_GUARD globally, fix as assertions
to type guards, add explicit return types, fix import type for express,
fix /metrics implicit any, replace native Error with ApplicationError,
remove typeorm remnants, register LifecycleService.

teacher-bff: add logger, ApplicationError, GlobalErrorFilter, forward
real userId to downstream, log downstream failures, migrate health
controller to shared/health.

Go (2 services): interface to any, doc comments, CORS dev whitelist,
JWT secret fail-fast, push-gateway internal API auth, metrics and
readyz endpoints, remove dead code.

Python (2 services): lifespan return type, dev_mode to bool, data-ana
APIRouter, ai POST body model, ClickHouse async wrapping.
This commit is contained in:
SpecialX
2026-07-09 17:28:27 +08:00
parent b53a486c6e
commit 0a71b02e04
93 changed files with 5775 additions and 608 deletions

View File

@@ -1,55 +1,70 @@
import {
Controller,
Get,
Param,
Req,
UnauthorizedException,
} from "@nestjs/common";
import { Controller, Get, Param, Req } from "@nestjs/common";
import type { Request } from "express";
import { TeacherService } from "./teacher.service.js";
import { UnauthorizedError } from "../shared/errors/application-error.js";
interface SuccessResponse<T> {
success: true;
data: T;
}
@Controller("teacher")
export class TeacherController {
constructor(private readonly service: TeacherService) {}
@Get("dashboard")
async dashboard(@Req() req: Request) {
const userId = req.headers["x-user-id"] as string;
if (!userId) {
throw new UnauthorizedException("Missing x-user-id header");
}
async dashboard(@Req() req: Request): Promise<SuccessResponse<unknown>> {
const userId = this.extractUserId(req);
const data = await this.service.getDashboard(userId);
return { success: true as const, data };
}
@Get("viewports")
async viewports(@Req() req: Request) {
const userId = req.headers["x-user-id"] as string;
if (!userId) {
throw new UnauthorizedException("Missing x-user-id header");
}
async viewports(@Req() req: Request): Promise<SuccessResponse<unknown>> {
const userId = this.extractUserId(req);
const data = await this.service.getViewports(userId);
return { success: true as const, data };
}
// 聚合班级下的考试列表core-edu
@Get("classes/:classId/exams")
async listExamsByClass(@Param("classId") classId: string) {
const data = await this.service.listExamsByClass(classId);
async listExamsByClass(
@Req() req: Request,
@Param("classId") classId: string,
): Promise<SuccessResponse<unknown>> {
const userId = this.extractUserId(req);
const data = await this.service.listExamsByClass(userId, classId);
return { success: true as const, data };
}
// 聚合班级下的作业列表core-edu
@Get("classes/:classId/homework")
async listHomeworkByClass(@Param("classId") classId: string) {
const data = await this.service.listHomeworkByClass(classId);
async listHomeworkByClass(
@Req() req: Request,
@Param("classId") classId: string,
): Promise<SuccessResponse<unknown>> {
const userId = this.extractUserId(req);
const data = await this.service.listHomeworkByClass(userId, classId);
return { success: true as const, data };
}
// 聚合考试下的成绩列表core-edu
@Get("exams/:examId/grades")
async listGradesByExam(@Param("examId") examId: string) {
const data = await this.service.listGradesByExam(examId);
async listGradesByExam(
@Req() req: Request,
@Param("examId") examId: string,
): Promise<SuccessResponse<unknown>> {
const userId = this.extractUserId(req);
const data = await this.service.listGradesByExam(userId, examId);
return { success: true as const, data };
}
private extractUserId(req: Request): string {
const header = req.headers["x-user-id"];
const userId = typeof header === "string" ? header : undefined;
if (!userId) {
throw new UnauthorizedError("Missing x-user-id header");
}
return userId;
}
}

View File

@@ -1,5 +1,7 @@
import { Injectable } from "@nestjs/common";
import { env } from "../config/env.js";
import { logger } from "../shared/observability/logger.js";
import { BadGatewayError } from "../shared/errors/application-error.js";
export interface ViewportItem {
key: string;
@@ -10,13 +12,20 @@ export interface ViewportItem {
requiredPermission: string | null;
}
interface DownstreamEnvelope<T> {
success: boolean;
data?: T;
}
interface DashboardData {
user: unknown;
classes: unknown;
}
@Injectable()
export class TeacherService {
// 聚合 IAM + classes 服务的数据
async getDashboard(userId: string): Promise<{
user: unknown;
classes: unknown;
}> {
async getDashboard(userId: string): Promise<DashboardData> {
const [iamRes, classesRes] = await Promise.allSettled([
fetch(`${env.IamServiceUrl}/iam/me`, {
headers: { "x-user-id": userId },
@@ -26,13 +35,42 @@ export class TeacherService {
}),
]);
return {
user: iamRes.status === "fulfilled" ? await iamRes.value.json() : null,
classes:
classesRes.status === "fulfilled"
? await classesRes.value.json()
: null,
};
let user: unknown = null;
let classes: unknown = null;
if (iamRes.status === "fulfilled") {
if (iamRes.value.ok) {
user = await iamRes.value.json();
} else {
logger.warn(
{ status: iamRes.value.status, url: iamRes.value.url },
"Downstream IAM service call failed",
);
}
} else {
logger.warn(
{ err: iamRes.reason, service: "iam" },
"Downstream IAM service call rejected",
);
}
if (classesRes.status === "fulfilled") {
if (classesRes.value.ok) {
classes = await classesRes.value.json();
} else {
logger.warn(
{ status: classesRes.value.status, url: classesRes.value.url },
"Downstream classes service call failed",
);
}
} else {
logger.warn(
{ err: classesRes.reason, service: "classes" },
"Downstream classes service call rejected",
);
}
return { user, classes };
}
// 聚合 IAM 视口配置L1 导航)
@@ -41,51 +79,80 @@ export class TeacherService {
headers: { "x-user-id": userId },
});
if (!res.ok) {
return [];
logger.warn(
{ status: res.status, url: res.url },
"Downstream IAM service call failed",
);
throw new BadGatewayError(`Downstream service returned ${res.status}`, {
service: "iam",
endpoint: "viewports",
status: res.status,
});
}
const json = (await res.json()) as {
success: boolean;
data?: ViewportItem[];
};
const json = (await res.json()) as DownstreamEnvelope<ViewportItem[]>;
return json.data ?? [];
}
// 聚合班级下的考试列表core-edu
async listExamsByClass(classId: string): Promise<unknown> {
async listExamsByClass(userId: string, classId: string): Promise<unknown> {
const res = await fetch(
`${env.CoreEduServiceUrl}/exams/class/${encodeURIComponent(classId)}`,
{ headers: { "x-user-id": "bff" } },
{ headers: { "x-user-id": userId } },
);
if (!res.ok) {
return [];
logger.warn(
{ status: res.status, url: res.url },
"Downstream core-edu service call failed",
);
throw new BadGatewayError(`Downstream service returned ${res.status}`, {
service: "core-edu",
endpoint: "exams-by-class",
status: res.status,
});
}
const json = (await res.json()) as { success: boolean; data?: unknown };
const json = (await res.json()) as DownstreamEnvelope<unknown>;
return json.data ?? [];
}
// 聚合班级下的作业列表core-edu
async listHomeworkByClass(classId: string): Promise<unknown> {
async listHomeworkByClass(userId: string, classId: string): Promise<unknown> {
const res = await fetch(
`${env.CoreEduServiceUrl}/homework/class/${encodeURIComponent(classId)}`,
{ headers: { "x-user-id": "bff" } },
{ headers: { "x-user-id": userId } },
);
if (!res.ok) {
return [];
logger.warn(
{ status: res.status, url: res.url },
"Downstream core-edu service call failed",
);
throw new BadGatewayError(`Downstream service returned ${res.status}`, {
service: "core-edu",
endpoint: "homework-by-class",
status: res.status,
});
}
const json = (await res.json()) as { success: boolean; data?: unknown };
const json = (await res.json()) as DownstreamEnvelope<unknown>;
return json.data ?? [];
}
// 聚合考试下的成绩列表core-edu
async listGradesByExam(examId: string): Promise<unknown> {
async listGradesByExam(userId: string, examId: string): Promise<unknown> {
const res = await fetch(
`${env.CoreEduServiceUrl}/grades/exam/${encodeURIComponent(examId)}`,
{ headers: { "x-user-id": "bff" } },
{ headers: { "x-user-id": userId } },
);
if (!res.ok) {
return [];
logger.warn(
{ status: res.status, url: res.url },
"Downstream core-edu service call failed",
);
throw new BadGatewayError(`Downstream service returned ${res.status}`, {
service: "core-edu",
endpoint: "grades-by-exam",
status: res.status,
});
}
const json = (await res.json()) as { success: boolean; data?: unknown };
const json = (await res.json()) as DownstreamEnvelope<unknown>;
return json.data ?? [];
}
}