- Integrated `TableManagementService` to validate unique constraints before insert, update, and upsert actions in various controllers, including `dataflowExecutionController`, `dynamicFormController`, and `tableManagementController`. - Improved error handling in `errorHandler` to provide detailed messages indicating which field has a unique constraint violation. - Updated the `formatPgError` utility to extract and display specific column labels for unique constraint violations, enhancing user feedback. - Adjusted the table schema retrieval to include company-specific nullable and unique constraints, ensuring accurate representation of database rules. These changes improve data integrity by preventing duplicate entries and enhance user experience through clearer error messages related to unique constraints.
119 lines
3.6 KiB
TypeScript
119 lines
3.6 KiB
TypeScript
import { Request, Response, NextFunction } from "express";
|
|
import { logger } from "../utils/logger";
|
|
|
|
// 커스텀 에러 클래스
|
|
export class AppError extends Error {
|
|
public statusCode: number;
|
|
public isOperational: boolean;
|
|
|
|
constructor(message: string, statusCode: number = 500) {
|
|
super(message);
|
|
this.statusCode = statusCode;
|
|
this.isOperational = true;
|
|
|
|
Error.captureStackTrace(this, this.constructor);
|
|
}
|
|
}
|
|
|
|
// 에러 핸들러 미들웨어
|
|
export const errorHandler = (
|
|
err: Error | AppError,
|
|
req: Request,
|
|
res: Response,
|
|
next: NextFunction
|
|
): void => {
|
|
let error = { ...err };
|
|
error.message = err.message;
|
|
|
|
// PostgreSQL 에러 처리 (pg 라이브러리)
|
|
if ((err as any).code) {
|
|
const pgError = err as any;
|
|
// 원본 에러 메시지 로깅 (디버깅용)
|
|
console.error("🔴 PostgreSQL Error:", {
|
|
code: pgError.code,
|
|
message: pgError.message,
|
|
detail: pgError.detail,
|
|
hint: pgError.hint,
|
|
table: pgError.table,
|
|
column: pgError.column,
|
|
constraint: pgError.constraint,
|
|
});
|
|
// PostgreSQL 에러 코드 참조: https://www.postgresql.org/docs/current/errcodes-appendix.html
|
|
if (pgError.code === "23505") {
|
|
// unique_violation
|
|
const constraint = pgError.constraint || "";
|
|
const tbl = pgError.table || "";
|
|
let col = "";
|
|
if (constraint && tbl) {
|
|
const prefix = `${tbl}_`;
|
|
const suffix = "_key";
|
|
if (constraint.startsWith(prefix) && constraint.endsWith(suffix)) {
|
|
col = constraint.slice(prefix.length, -suffix.length);
|
|
}
|
|
}
|
|
const detail = col ? ` [${col}]` : "";
|
|
error = new AppError(`중복된 데이터가 존재합니다.${detail}`, 400);
|
|
} else if (pgError.code === "23503") {
|
|
// foreign_key_violation
|
|
error = new AppError("참조 무결성 제약 조건 위반입니다.", 400);
|
|
} else if (pgError.code === "23502") {
|
|
// not_null_violation
|
|
const colName = pgError.column || "";
|
|
const tableName = pgError.table || "";
|
|
const detail = colName ? ` [${tableName}.${colName}]` : "";
|
|
error = new AppError(`필수 입력값이 누락되었습니다.${detail}`, 400);
|
|
} else if (pgError.code.startsWith("23")) {
|
|
// 기타 무결성 제약 조건 위반
|
|
error = new AppError("데이터 무결성 제약 조건 위반입니다.", 400);
|
|
} else {
|
|
error = new AppError(`데이터베이스 오류: ${pgError.message}`, 500);
|
|
}
|
|
}
|
|
|
|
// JWT 에러 처리
|
|
if (err.name === "JsonWebTokenError") {
|
|
const message = "유효하지 않은 토큰입니다.";
|
|
error = new AppError(message, 401);
|
|
}
|
|
|
|
if (err.name === "TokenExpiredError") {
|
|
const message = "토큰이 만료되었습니다.";
|
|
error = new AppError(message, 401);
|
|
}
|
|
|
|
// 기본 상태 코드 설정
|
|
const statusCode = (error as AppError).statusCode || 500;
|
|
const message = error.message || "서버 내부 오류가 발생했습니다.";
|
|
|
|
// 에러 로깅
|
|
logger.error({
|
|
message: error.message,
|
|
stack: error.stack,
|
|
url: req.url,
|
|
method: req.method,
|
|
ip: req.ip,
|
|
userAgent: req.get("User-Agent"),
|
|
});
|
|
|
|
// 응답 전송
|
|
res.status(statusCode).json({
|
|
success: false,
|
|
message: message,
|
|
error: {
|
|
message: message,
|
|
...(process.env.NODE_ENV === "development" && { stack: error.stack }),
|
|
},
|
|
});
|
|
};
|
|
|
|
// 404 에러 핸들러
|
|
export const notFoundHandler = (req: Request, res: Response): void => {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: {
|
|
message: "요청한 리소스를 찾을 수 없습니다.",
|
|
path: req.originalUrl,
|
|
},
|
|
});
|
|
};
|