테이블 변경 이력 로그 시스템 구현
This commit is contained in:
@@ -1048,3 +1048,268 @@ export async function updateColumnWebType(
|
||||
res.status(500).json(response);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 🎯 테이블 로그 시스템 API
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* 로그 테이블 생성
|
||||
*/
|
||||
export async function createLogTable(
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { tableName } = req.params;
|
||||
const { pkColumn } = req.body;
|
||||
const userId = req.user?.userId;
|
||||
|
||||
logger.info(`=== 로그 테이블 생성 시작: ${tableName} ===`);
|
||||
|
||||
if (!tableName) {
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
message: "테이블명이 필요합니다.",
|
||||
error: {
|
||||
code: "MISSING_TABLE_NAME",
|
||||
details: "테이블명 파라미터가 누락되었습니다.",
|
||||
},
|
||||
};
|
||||
res.status(400).json(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pkColumn || !pkColumn.columnName || !pkColumn.dataType) {
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
message: "PK 컬럼 정보가 필요합니다.",
|
||||
error: {
|
||||
code: "MISSING_PK_COLUMN",
|
||||
details: "PK 컬럼명과 데이터 타입이 필요합니다.",
|
||||
},
|
||||
};
|
||||
res.status(400).json(response);
|
||||
return;
|
||||
}
|
||||
|
||||
const tableManagementService = new TableManagementService();
|
||||
await tableManagementService.createLogTable(tableName, pkColumn, userId);
|
||||
|
||||
logger.info(`로그 테이블 생성 완료: ${tableName}_log`);
|
||||
|
||||
const response: ApiResponse<null> = {
|
||||
success: true,
|
||||
message: "로그 테이블이 성공적으로 생성되었습니다.",
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("로그 테이블 생성 중 오류 발생:", error);
|
||||
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
message: "로그 테이블 생성 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "LOG_TABLE_CREATE_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
};
|
||||
|
||||
res.status(500).json(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그 설정 조회
|
||||
*/
|
||||
export async function getLogConfig(
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { tableName } = req.params;
|
||||
|
||||
logger.info(`=== 로그 설정 조회: ${tableName} ===`);
|
||||
|
||||
if (!tableName) {
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
message: "테이블명이 필요합니다.",
|
||||
error: {
|
||||
code: "MISSING_TABLE_NAME",
|
||||
details: "테이블명 파라미터가 누락되었습니다.",
|
||||
},
|
||||
};
|
||||
res.status(400).json(response);
|
||||
return;
|
||||
}
|
||||
|
||||
const tableManagementService = new TableManagementService();
|
||||
const logConfig = await tableManagementService.getLogConfig(tableName);
|
||||
|
||||
const response: ApiResponse<typeof logConfig> = {
|
||||
success: true,
|
||||
message: "로그 설정을 조회했습니다.",
|
||||
data: logConfig,
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("로그 설정 조회 중 오류 발생:", error);
|
||||
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
message: "로그 설정 조회 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "LOG_CONFIG_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
};
|
||||
|
||||
res.status(500).json(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그 데이터 조회
|
||||
*/
|
||||
export async function getLogData(
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { tableName } = req.params;
|
||||
const {
|
||||
page = 1,
|
||||
size = 20,
|
||||
operationType,
|
||||
startDate,
|
||||
endDate,
|
||||
changedBy,
|
||||
originalId,
|
||||
} = req.query;
|
||||
|
||||
logger.info(`=== 로그 데이터 조회: ${tableName} ===`);
|
||||
|
||||
if (!tableName) {
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
message: "테이블명이 필요합니다.",
|
||||
error: {
|
||||
code: "MISSING_TABLE_NAME",
|
||||
details: "테이블명 파라미터가 누락되었습니다.",
|
||||
},
|
||||
};
|
||||
res.status(400).json(response);
|
||||
return;
|
||||
}
|
||||
|
||||
const tableManagementService = new TableManagementService();
|
||||
const result = await tableManagementService.getLogData(tableName, {
|
||||
page: parseInt(page as string),
|
||||
size: parseInt(size as string),
|
||||
operationType: operationType as string,
|
||||
startDate: startDate as string,
|
||||
endDate: endDate as string,
|
||||
changedBy: changedBy as string,
|
||||
originalId: originalId as string,
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`로그 데이터 조회 완료: ${tableName}_log, ${result.total}건`
|
||||
);
|
||||
|
||||
const response: ApiResponse<typeof result> = {
|
||||
success: true,
|
||||
message: "로그 데이터를 조회했습니다.",
|
||||
data: result,
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("로그 데이터 조회 중 오류 발생:", error);
|
||||
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
message: "로그 데이터 조회 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "LOG_DATA_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
};
|
||||
|
||||
res.status(500).json(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그 테이블 활성화/비활성화
|
||||
*/
|
||||
export async function toggleLogTable(
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { tableName } = req.params;
|
||||
const { isActive } = req.body;
|
||||
|
||||
logger.info(`=== 로그 테이블 토글: ${tableName}, isActive: ${isActive} ===`);
|
||||
|
||||
if (!tableName) {
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
message: "테이블명이 필요합니다.",
|
||||
error: {
|
||||
code: "MISSING_TABLE_NAME",
|
||||
details: "테이블명 파라미터가 누락되었습니다.",
|
||||
},
|
||||
};
|
||||
res.status(400).json(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isActive === undefined || isActive === null) {
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
message: "isActive 값이 필요합니다.",
|
||||
error: {
|
||||
code: "MISSING_IS_ACTIVE",
|
||||
details: "isActive 파라미터가 누락되었습니다.",
|
||||
},
|
||||
};
|
||||
res.status(400).json(response);
|
||||
return;
|
||||
}
|
||||
|
||||
const tableManagementService = new TableManagementService();
|
||||
await tableManagementService.toggleLogTable(
|
||||
tableName,
|
||||
isActive === "Y" || isActive === true
|
||||
);
|
||||
|
||||
logger.info(
|
||||
`로그 테이블 토글 완료: ${tableName}, isActive: ${isActive}`
|
||||
);
|
||||
|
||||
const response: ApiResponse<null> = {
|
||||
success: true,
|
||||
message: `로그 기능이 ${isActive ? "활성화" : "비활성화"}되었습니다.`,
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("로그 테이블 토글 중 오류 발생:", error);
|
||||
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
message: "로그 테이블 토글 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "LOG_TOGGLE_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
};
|
||||
|
||||
res.status(500).json(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ import {
|
||||
checkTableExists,
|
||||
getColumnWebTypes,
|
||||
checkDatabaseConnection,
|
||||
createLogTable,
|
||||
getLogConfig,
|
||||
getLogData,
|
||||
toggleLogTable,
|
||||
} from "../controllers/tableManagementController";
|
||||
|
||||
const router = express.Router();
|
||||
@@ -148,4 +152,32 @@ router.put("/tables/:tableName/edit", editTableData);
|
||||
*/
|
||||
router.delete("/tables/:tableName/delete", deleteTableData);
|
||||
|
||||
// ========================================
|
||||
// 테이블 로그 시스템 API
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* 로그 테이블 생성
|
||||
* POST /api/table-management/tables/:tableName/log
|
||||
*/
|
||||
router.post("/tables/:tableName/log", createLogTable);
|
||||
|
||||
/**
|
||||
* 로그 설정 조회
|
||||
* GET /api/table-management/tables/:tableName/log/config
|
||||
*/
|
||||
router.get("/tables/:tableName/log/config", getLogConfig);
|
||||
|
||||
/**
|
||||
* 로그 데이터 조회
|
||||
* GET /api/table-management/tables/:tableName/log
|
||||
*/
|
||||
router.get("/tables/:tableName/log", getLogData);
|
||||
|
||||
/**
|
||||
* 로그 테이블 활성화/비활성화
|
||||
* POST /api/table-management/tables/:tableName/log/toggle
|
||||
*/
|
||||
router.post("/tables/:tableName/log/toggle", toggleLogTable);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -3118,4 +3118,410 @@ export class TableManagementService {
|
||||
// 기본값
|
||||
return "text";
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 🎯 테이블 로그 시스템
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* 로그 테이블 생성
|
||||
*/
|
||||
async createLogTable(
|
||||
tableName: string,
|
||||
pkColumn: { columnName: string; dataType: string },
|
||||
userId?: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
const logTableName = `${tableName}_log`;
|
||||
const triggerFuncName = `${tableName}_log_trigger_func`;
|
||||
const triggerName = `${tableName}_audit_trigger`;
|
||||
|
||||
logger.info(`로그 테이블 생성 시작: ${logTableName}`);
|
||||
|
||||
// 로그 테이블 DDL 생성
|
||||
const logTableDDL = this.generateLogTableDDL(
|
||||
logTableName,
|
||||
tableName,
|
||||
pkColumn.columnName,
|
||||
pkColumn.dataType
|
||||
);
|
||||
|
||||
// 트리거 함수 DDL 생성
|
||||
const triggerFuncDDL = this.generateTriggerFunctionDDL(
|
||||
triggerFuncName,
|
||||
logTableName,
|
||||
tableName,
|
||||
pkColumn.columnName
|
||||
);
|
||||
|
||||
// 트리거 DDL 생성
|
||||
const triggerDDL = this.generateTriggerDDL(
|
||||
triggerName,
|
||||
tableName,
|
||||
triggerFuncName
|
||||
);
|
||||
|
||||
// 트랜잭션으로 실행
|
||||
await transaction(async (client) => {
|
||||
// 1. 로그 테이블 생성
|
||||
await client.query(logTableDDL);
|
||||
logger.info(`로그 테이블 생성 완료: ${logTableName}`);
|
||||
|
||||
// 2. 트리거 함수 생성
|
||||
await client.query(triggerFuncDDL);
|
||||
logger.info(`트리거 함수 생성 완료: ${triggerFuncName}`);
|
||||
|
||||
// 3. 트리거 생성
|
||||
await client.query(triggerDDL);
|
||||
logger.info(`트리거 생성 완료: ${triggerName}`);
|
||||
|
||||
// 4. 로그 설정 저장
|
||||
await client.query(
|
||||
`INSERT INTO table_log_config (
|
||||
original_table_name, log_table_name, trigger_name,
|
||||
trigger_function_name, created_by
|
||||
) VALUES ($1, $2, $3, $4, $5)`,
|
||||
[tableName, logTableName, triggerName, triggerFuncName, userId]
|
||||
);
|
||||
logger.info(`로그 설정 저장 완료: ${tableName}`);
|
||||
});
|
||||
|
||||
logger.info(`로그 테이블 생성 완료: ${logTableName}`);
|
||||
} catch (error) {
|
||||
logger.error(`로그 테이블 생성 실패: ${tableName}`, error);
|
||||
throw new Error(
|
||||
`로그 테이블 생성 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그 테이블 DDL 생성
|
||||
*/
|
||||
private generateLogTableDDL(
|
||||
logTableName: string,
|
||||
originalTableName: string,
|
||||
pkColumnName: string,
|
||||
pkDataType: string
|
||||
): string {
|
||||
return `
|
||||
CREATE TABLE ${logTableName} (
|
||||
log_id SERIAL PRIMARY KEY,
|
||||
operation_type VARCHAR(10) NOT NULL,
|
||||
original_id VARCHAR(100),
|
||||
changed_column VARCHAR(100),
|
||||
old_value TEXT,
|
||||
new_value TEXT,
|
||||
changed_by VARCHAR(50),
|
||||
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
ip_address VARCHAR(50),
|
||||
user_agent TEXT,
|
||||
full_row_before JSONB,
|
||||
full_row_after JSONB
|
||||
);
|
||||
|
||||
CREATE INDEX idx_${logTableName}_original_id ON ${logTableName}(original_id);
|
||||
CREATE INDEX idx_${logTableName}_changed_at ON ${logTableName}(changed_at);
|
||||
CREATE INDEX idx_${logTableName}_operation ON ${logTableName}(operation_type);
|
||||
|
||||
COMMENT ON TABLE ${logTableName} IS '${originalTableName} 테이블 변경 이력';
|
||||
COMMENT ON COLUMN ${logTableName}.operation_type IS '작업 유형 (INSERT/UPDATE/DELETE)';
|
||||
COMMENT ON COLUMN ${logTableName}.original_id IS '원본 테이블 PK 값';
|
||||
COMMENT ON COLUMN ${logTableName}.changed_column IS '변경된 컬럼명';
|
||||
COMMENT ON COLUMN ${logTableName}.old_value IS '변경 전 값';
|
||||
COMMENT ON COLUMN ${logTableName}.new_value IS '변경 후 값';
|
||||
COMMENT ON COLUMN ${logTableName}.changed_by IS '변경자 ID';
|
||||
COMMENT ON COLUMN ${logTableName}.changed_at IS '변경 시각';
|
||||
COMMENT ON COLUMN ${logTableName}.ip_address IS '변경 요청 IP';
|
||||
COMMENT ON COLUMN ${logTableName}.full_row_before IS '변경 전 전체 행 (JSON)';
|
||||
COMMENT ON COLUMN ${logTableName}.full_row_after IS '변경 후 전체 행 (JSON)';
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 트리거 함수 DDL 생성
|
||||
*/
|
||||
private generateTriggerFunctionDDL(
|
||||
funcName: string,
|
||||
logTableName: string,
|
||||
originalTableName: string,
|
||||
pkColumnName: string
|
||||
): string {
|
||||
return `
|
||||
CREATE OR REPLACE FUNCTION ${funcName}()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
v_column_name TEXT;
|
||||
v_old_value TEXT;
|
||||
v_new_value TEXT;
|
||||
v_user_id VARCHAR(50);
|
||||
v_ip_address VARCHAR(50);
|
||||
BEGIN
|
||||
v_user_id := current_setting('app.user_id', TRUE);
|
||||
v_ip_address := current_setting('app.ip_address', TRUE);
|
||||
|
||||
IF (TG_OP = 'INSERT') THEN
|
||||
EXECUTE format(
|
||||
'INSERT INTO ${logTableName} (operation_type, original_id, changed_by, ip_address, full_row_after)
|
||||
VALUES ($1, ($2).%I, $3, $4, $5)',
|
||||
'${pkColumnName}'
|
||||
)
|
||||
USING 'INSERT', NEW, v_user_id, v_ip_address, row_to_json(NEW)::jsonb;
|
||||
RETURN NEW;
|
||||
|
||||
ELSIF (TG_OP = 'UPDATE') THEN
|
||||
FOR v_column_name IN
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = '${originalTableName}'
|
||||
AND table_schema = 'public'
|
||||
LOOP
|
||||
EXECUTE format('SELECT ($1).%I::TEXT, ($2).%I::TEXT', v_column_name, v_column_name)
|
||||
INTO v_old_value, v_new_value
|
||||
USING OLD, NEW;
|
||||
|
||||
IF v_old_value IS DISTINCT FROM v_new_value THEN
|
||||
EXECUTE format(
|
||||
'INSERT INTO ${logTableName} (operation_type, original_id, changed_column, old_value, new_value, changed_by, ip_address, full_row_before, full_row_after)
|
||||
VALUES ($1, ($2).%I, $3, $4, $5, $6, $7, $8, $9)',
|
||||
'${pkColumnName}'
|
||||
)
|
||||
USING 'UPDATE', NEW, v_column_name, v_old_value, v_new_value, v_user_id, v_ip_address, row_to_json(OLD)::jsonb, row_to_json(NEW)::jsonb;
|
||||
END IF;
|
||||
END LOOP;
|
||||
RETURN NEW;
|
||||
|
||||
ELSIF (TG_OP = 'DELETE') THEN
|
||||
EXECUTE format(
|
||||
'INSERT INTO ${logTableName} (operation_type, original_id, changed_by, ip_address, full_row_before)
|
||||
VALUES ($1, ($2).%I, $3, $4, $5)',
|
||||
'${pkColumnName}'
|
||||
)
|
||||
USING 'DELETE', OLD, v_user_id, v_ip_address, row_to_json(OLD)::jsonb;
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 트리거 DDL 생성
|
||||
*/
|
||||
private generateTriggerDDL(
|
||||
triggerName: string,
|
||||
tableName: string,
|
||||
funcName: string
|
||||
): string {
|
||||
return `
|
||||
CREATE TRIGGER ${triggerName}
|
||||
AFTER INSERT OR UPDATE OR DELETE ON ${tableName}
|
||||
FOR EACH ROW EXECUTE FUNCTION ${funcName}();
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그 설정 조회
|
||||
*/
|
||||
async getLogConfig(tableName: string): Promise<{
|
||||
originalTableName: string;
|
||||
logTableName: string;
|
||||
triggerName: string;
|
||||
triggerFunctionName: string;
|
||||
isActive: string;
|
||||
createdAt: Date;
|
||||
createdBy: string;
|
||||
} | null> {
|
||||
try {
|
||||
logger.info(`로그 설정 조회: ${tableName}`);
|
||||
|
||||
const result = await queryOne<{
|
||||
original_table_name: string;
|
||||
log_table_name: string;
|
||||
trigger_name: string;
|
||||
trigger_function_name: string;
|
||||
is_active: string;
|
||||
created_at: Date;
|
||||
created_by: string;
|
||||
}>(
|
||||
`SELECT
|
||||
original_table_name, log_table_name, trigger_name,
|
||||
trigger_function_name, is_active, created_at, created_by
|
||||
FROM table_log_config
|
||||
WHERE original_table_name = $1`,
|
||||
[tableName]
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
originalTableName: result.original_table_name,
|
||||
logTableName: result.log_table_name,
|
||||
triggerName: result.trigger_name,
|
||||
triggerFunctionName: result.trigger_function_name,
|
||||
isActive: result.is_active,
|
||||
createdAt: result.created_at,
|
||||
createdBy: result.created_by,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`로그 설정 조회 실패: ${tableName}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그 데이터 조회
|
||||
*/
|
||||
async getLogData(
|
||||
tableName: string,
|
||||
options: {
|
||||
page: number;
|
||||
size: number;
|
||||
operationType?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
changedBy?: string;
|
||||
originalId?: string;
|
||||
}
|
||||
): Promise<{
|
||||
data: any[];
|
||||
total: number;
|
||||
page: number;
|
||||
size: number;
|
||||
totalPages: number;
|
||||
}> {
|
||||
try {
|
||||
const logTableName = `${tableName}_log`;
|
||||
const offset = (options.page - 1) * options.size;
|
||||
|
||||
logger.info(`로그 데이터 조회: ${logTableName}`, options);
|
||||
|
||||
// WHERE 조건 구성
|
||||
const whereConditions: string[] = [];
|
||||
const values: any[] = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
if (options.operationType) {
|
||||
whereConditions.push(`operation_type = $${paramIndex}`);
|
||||
values.push(options.operationType);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
if (options.startDate) {
|
||||
whereConditions.push(`changed_at >= $${paramIndex}::timestamp`);
|
||||
values.push(options.startDate);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
if (options.endDate) {
|
||||
whereConditions.push(`changed_at <= $${paramIndex}::timestamp`);
|
||||
values.push(options.endDate);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
if (options.changedBy) {
|
||||
whereConditions.push(`changed_by = $${paramIndex}`);
|
||||
values.push(options.changedBy);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
if (options.originalId) {
|
||||
whereConditions.push(`original_id::text = $${paramIndex}`);
|
||||
values.push(options.originalId);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
const whereClause =
|
||||
whereConditions.length > 0
|
||||
? `WHERE ${whereConditions.join(" AND ")}`
|
||||
: "";
|
||||
|
||||
// 전체 개수 조회
|
||||
const countQuery = `SELECT COUNT(*) as count FROM ${logTableName} ${whereClause}`;
|
||||
const countResult = await query<any>(countQuery, values);
|
||||
const total = parseInt(countResult[0].count);
|
||||
|
||||
// 데이터 조회
|
||||
const dataQuery = `
|
||||
SELECT * FROM ${logTableName}
|
||||
${whereClause}
|
||||
ORDER BY changed_at DESC
|
||||
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
|
||||
`;
|
||||
|
||||
const data = await query<any>(dataQuery, [
|
||||
...values,
|
||||
options.size,
|
||||
offset,
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / options.size);
|
||||
|
||||
logger.info(
|
||||
`로그 데이터 조회 완료: ${logTableName}, 총 ${total}건, ${data.length}개 반환`
|
||||
);
|
||||
|
||||
return {
|
||||
data,
|
||||
total,
|
||||
page: options.page,
|
||||
size: options.size,
|
||||
totalPages,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`로그 데이터 조회 실패: ${tableName}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그 테이블 활성화/비활성화
|
||||
*/
|
||||
async toggleLogTable(tableName: string, isActive: boolean): Promise<void> {
|
||||
try {
|
||||
const logConfig = await this.getLogConfig(tableName);
|
||||
if (!logConfig) {
|
||||
throw new Error(`로그 설정을 찾을 수 없습니다: ${tableName}`);
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`로그 테이블 ${isActive ? "활성화" : "비활성화"}: ${tableName}`
|
||||
);
|
||||
|
||||
await transaction(async (client) => {
|
||||
// 트리거 활성화/비활성화
|
||||
if (isActive) {
|
||||
await client.query(
|
||||
`ALTER TABLE ${tableName} ENABLE TRIGGER ${logConfig.triggerName}`
|
||||
);
|
||||
} else {
|
||||
await client.query(
|
||||
`ALTER TABLE ${tableName} DISABLE TRIGGER ${logConfig.triggerName}`
|
||||
);
|
||||
}
|
||||
|
||||
// 설정 업데이트
|
||||
await client.query(
|
||||
`UPDATE table_log_config
|
||||
SET is_active = $1, updated_at = NOW()
|
||||
WHERE original_table_name = $2`,
|
||||
[isActive ? "Y" : "N", tableName]
|
||||
);
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`로그 테이블 ${isActive ? "활성화" : "비활성화"} 완료: ${tableName}`
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`로그 테이블 ${isActive ? "활성화" : "비활성화"} 실패: ${tableName}`,
|
||||
error
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user