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,25 +1,45 @@
export enum CoreEduErrorCode {
VALIDATION_ERROR = 'CORE_EDU_VALIDATION_ERROR',
NOT_FOUND = 'CORE_EDU_NOT_FOUND',
UNAUTHORIZED = 'CORE_EDU_UNAUTHORIZED',
FORBIDDEN = 'CORE_EDU_FORBIDDEN',
CONFLICT = 'CORE_EDU_CONFLICT',
INTERNAL_ERROR = 'CORE_EDU_INTERNAL_ERROR',
EXAM_NOT_FOUND = 'CORE_EDU_EXAM_NOT_FOUND',
HOMEWORK_NOT_FOUND = 'CORE_EDU_HOMEWORK_NOT_FOUND',
GRADE_NOT_FOUND = 'CORE_EDU_GRADE_NOT_FOUND',
OUTBOX_PUBLISH_FAILED = 'CORE_EDU_OUTBOX_PUBLISH_FAILED',
VALIDATION_ERROR = "CORE_EDU_VALIDATION_ERROR",
NOT_FOUND = "CORE_EDU_NOT_FOUND",
UNAUTHORIZED = "CORE_EDU_UNAUTHORIZED",
FORBIDDEN = "CORE_EDU_FORBIDDEN",
CONFLICT = "CORE_EDU_CONFLICT",
INTERNAL_ERROR = "CORE_EDU_INTERNAL_ERROR",
EXAM_NOT_FOUND = "CORE_EDU_EXAM_NOT_FOUND",
HOMEWORK_NOT_FOUND = "CORE_EDU_HOMEWORK_NOT_FOUND",
GRADE_NOT_FOUND = "CORE_EDU_GRADE_NOT_FOUND",
OUTBOX_PUBLISH_FAILED = "CORE_EDU_OUTBOX_PUBLISH_FAILED",
}
export class ApplicationError extends Error {
readonly code: CoreEduErrorCode;
readonly statusCode: number;
readonly details?: unknown;
traceId?: string;
constructor(
public readonly code: CoreEduErrorCode,
code: CoreEduErrorCode,
message: string,
public readonly statusCode: number = 500,
public readonly details?: unknown,
statusCode: number = 500,
details?: unknown,
) {
super(message);
this.name = 'ApplicationError';
this.name = this.constructor.name;
this.code = code;
this.statusCode = statusCode;
this.details = details;
}
toJSON(): Record<string, unknown> {
return {
success: false,
error: {
code: this.code,
message: this.message,
details: this.details,
traceId: this.traceId,
},
};
}
}
@@ -36,17 +56,25 @@ export class NotFoundError extends ApplicationError {
}
export class UnauthorizedError extends ApplicationError {
constructor(message: string = 'Unauthorized') {
constructor(message: string = "Unauthorized") {
super(CoreEduErrorCode.UNAUTHORIZED, message, 401);
}
}
export class ForbiddenError extends ApplicationError {
constructor(message: string = 'Forbidden') {
constructor(message: string = "Forbidden") {
super(CoreEduErrorCode.FORBIDDEN, message, 403);
}
}
export class PermissionDeniedError extends ApplicationError {
constructor(permission: string) {
super(CoreEduErrorCode.FORBIDDEN, `Permission denied: ${permission}`, 403, {
permission,
});
}
}
export class ConflictError extends ApplicationError {
constructor(message: string, details?: unknown) {
super(CoreEduErrorCode.CONFLICT, message, 409, details);
@@ -54,7 +82,7 @@ export class ConflictError extends ApplicationError {
}
export class InternalError extends ApplicationError {
constructor(message: string = 'Internal server error', details?: unknown) {
constructor(message: string = "Internal server error", details?: unknown) {
super(CoreEduErrorCode.INTERNAL_ERROR, message, 500, details);
}
}

View File

@@ -1,63 +1,95 @@
import {
ExceptionFilter,
Catch,
ExceptionFilter,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { ZodError } from 'zod';
import { ApplicationError, CoreEduErrorCode } from './application-error.js';
import { logger } from '../observability/logger.js';
Logger,
} from "@nestjs/common";
import type { Request, Response } from "express";
import { ZodError } from "zod";
import { ApplicationError } from "./application-error.js";
@Catch()
export class GlobalErrorFilter implements ExceptionFilter {
private readonly logger = new Logger(GlobalErrorFilter.name);
catch(exception: unknown, host: ArgumentsHost): void {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
let statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
let code = CoreEduErrorCode.INTERNAL_ERROR;
let message = 'Internal server error';
let details: unknown;
const traceIdHeader = request.headers["x-request-id"];
const traceId =
typeof traceIdHeader === "string" ? traceIdHeader : "unknown";
let statusCode = 500;
let body: Record<string, unknown>;
if (exception instanceof ApplicationError) {
exception.traceId = traceId;
statusCode = exception.statusCode;
code = exception.code;
message = exception.message;
details = exception.details;
body = exception.toJSON();
} else if (exception instanceof ZodError) {
statusCode = HttpStatus.BAD_REQUEST;
code = CoreEduErrorCode.VALIDATION_ERROR;
message = 'Validation failed';
details = exception.flatten().fieldErrors;
statusCode = 400;
body = {
success: false,
error: {
code: "CORE_EDU_VALIDATION_ERROR",
message: "Validation failed",
details: exception.flatten(),
traceId,
},
};
} else if (exception instanceof HttpException) {
statusCode = exception.getStatus();
const resp = exception.getResponse();
message =
typeof resp === 'string'
? resp
: (resp as { message?: string }).message ?? exception.message;
} else if (exception instanceof Error) {
message = exception.message;
const res = exception.getResponse();
const message = this.extractHttpMessage(res, exception);
body = {
success: false,
error: {
code: "HTTP_ERROR",
message,
traceId,
},
};
} else {
this.logger.error(
`Unhandled exception: ${exception}`,
exception instanceof Error ? exception.stack : undefined,
);
body = {
success: false,
error: {
code: "INTERNAL_ERROR",
message: "An unexpected error occurred",
traceId,
},
};
}
logger.error(
this.logger.error(
{
err: exception,
path: request.url,
method: request.method,
code,
},
`Request failed: ${message}`,
`Request failed: ${request.method} ${request.url}`,
);
response.status(statusCode).json({
code,
message,
details,
timestamp: new Date().toISOString(),
path: request.url,
});
response.status(statusCode).json(body);
}
private extractHttpMessage(
res: string | object,
exception: HttpException,
): string {
if (typeof res === "string") {
return res;
}
if (res && typeof res === "object" && "message" in res) {
const msg = (res as { message: unknown }).message;
return typeof msg === "string" ? msg : exception.message;
}
return exception.message;
}
}

View File

@@ -1,12 +1,21 @@
import { Injectable, Logger } from "@nestjs/common";
import {
Injectable,
Logger,
OnApplicationShutdown,
OnModuleInit,
} from "@nestjs/common";
import { closeDb } from "../../config/database.js";
const SERVICE_NAME = "core-edu";
@Injectable()
export class LifecycleService {
export class LifecycleService implements OnModuleInit, OnApplicationShutdown {
private readonly logger = new Logger(LifecycleService.name);
onModuleInit(): void {
this.logger.log(`service ${SERVICE_NAME} module initialized`);
}
async onApplicationShutdown(signal?: string): Promise<void> {
this.logger.log(
`service ${SERVICE_NAME} shutting down (signal=${signal ?? "unknown"})`,