fix: code compliance audit and fix across all services
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user