329 lines
8.7 KiB
TypeScript
329 lines
8.7 KiB
TypeScript
/**
|
|
* 🔥 데이터플로우 실행 컨트롤러
|
|
*
|
|
* 버튼 제어에서 관계 실행 시 사용되는 컨트롤러
|
|
*/
|
|
|
|
import { Request, Response } from "express";
|
|
import { AuthenticatedRequest } from "../types/auth";
|
|
import { query } from "../database/db";
|
|
import logger from "../utils/logger";
|
|
|
|
/**
|
|
* 데이터 액션 실행
|
|
*/
|
|
export async function executeDataAction(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const { tableName, data, actionType, connection } = req.body;
|
|
const companyCode = req.user?.companyCode || "*";
|
|
|
|
logger.info(`데이터 액션 실행 시작: ${actionType} on ${tableName}`, {
|
|
tableName,
|
|
actionType,
|
|
dataKeys: Object.keys(data),
|
|
connection: connection?.name,
|
|
});
|
|
|
|
// 연결 정보에 따라 다른 데이터베이스에 저장
|
|
let result;
|
|
|
|
if (connection && connection.id !== 0) {
|
|
// 외부 데이터베이스 연결
|
|
result = await executeExternalDatabaseAction(
|
|
tableName,
|
|
data,
|
|
actionType,
|
|
connection
|
|
);
|
|
} else {
|
|
// 메인 데이터베이스 (현재 시스템)
|
|
result = await executeMainDatabaseAction(
|
|
tableName,
|
|
data,
|
|
actionType,
|
|
companyCode
|
|
);
|
|
}
|
|
|
|
logger.info(`데이터 액션 실행 완료: ${actionType} on ${tableName}`, result);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: `데이터 액션 실행 완료: ${actionType}`,
|
|
data: result,
|
|
});
|
|
} catch (error: any) {
|
|
logger.error("데이터 액션 실행 실패:", error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: `데이터 액션 실행 실패: ${error.message}`,
|
|
errorCode: "DATA_ACTION_EXECUTION_ERROR",
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 메인 데이터베이스에서 데이터 액션 실행
|
|
*/
|
|
async function executeMainDatabaseAction(
|
|
tableName: string,
|
|
data: Record<string, any>,
|
|
actionType: string,
|
|
companyCode: string
|
|
): Promise<any> {
|
|
try {
|
|
// 회사 코드 추가
|
|
const dataWithCompany = {
|
|
...data,
|
|
company_code: companyCode,
|
|
};
|
|
|
|
switch (actionType.toLowerCase()) {
|
|
case "insert":
|
|
return await executeInsert(tableName, dataWithCompany);
|
|
case "update":
|
|
return await executeUpdate(tableName, dataWithCompany);
|
|
case "upsert":
|
|
return await executeUpsert(tableName, dataWithCompany);
|
|
case "delete":
|
|
return await executeDelete(tableName, dataWithCompany);
|
|
default:
|
|
throw new Error(`지원하지 않는 액션 타입: ${actionType}`);
|
|
}
|
|
} catch (error) {
|
|
logger.error(`메인 DB 액션 실행 오류 (${actionType}):`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 외부 데이터베이스에서 데이터 액션 실행
|
|
*/
|
|
async function executeExternalDatabaseAction(
|
|
tableName: string,
|
|
data: Record<string, any>,
|
|
actionType: string,
|
|
connection: any
|
|
): Promise<any> {
|
|
try {
|
|
logger.info(
|
|
`외부 DB 액션 실행: ${connection.name} (${connection.host}:${connection.port})`
|
|
);
|
|
logger.info(`테이블: ${tableName}, 액션: ${actionType}`, data);
|
|
|
|
// 🔥 실제 외부 DB 연결 및 실행 로직 구현
|
|
const { MultiConnectionQueryService } = await import(
|
|
"../services/multiConnectionQueryService"
|
|
);
|
|
const queryService = new MultiConnectionQueryService();
|
|
|
|
let result;
|
|
switch (actionType.toLowerCase()) {
|
|
case "insert":
|
|
result = await queryService.insertDataToConnection(
|
|
connection.id,
|
|
tableName,
|
|
data
|
|
);
|
|
logger.info(`외부 DB INSERT 성공:`, result);
|
|
break;
|
|
case "update":
|
|
// TODO: UPDATE 로직 구현 (조건 필요)
|
|
throw new Error(
|
|
"UPDATE 액션은 아직 지원되지 않습니다. 조건 설정이 필요합니다."
|
|
);
|
|
case "delete":
|
|
// TODO: DELETE 로직 구현 (조건 필요)
|
|
throw new Error(
|
|
"DELETE 액션은 아직 지원되지 않습니다. 조건 설정이 필요합니다."
|
|
);
|
|
default:
|
|
throw new Error(`지원하지 않는 액션 타입: ${actionType}`);
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
message: `외부 DB 액션 실행 완료: ${actionType} on ${tableName}`,
|
|
connection: connection.name,
|
|
data: result,
|
|
affectedRows: 1,
|
|
};
|
|
} catch (error) {
|
|
logger.error(`외부 DB 액션 실행 오류 (${actionType}):`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* INSERT 실행
|
|
*/
|
|
async function executeInsert(
|
|
tableName: string,
|
|
data: Record<string, any>
|
|
): Promise<any> {
|
|
try {
|
|
// 동적 테이블 접근을 위한 raw query 사용
|
|
const columns = Object.keys(data).join(", ");
|
|
const values = Object.values(data);
|
|
const placeholders = values.map((_, index) => `$${index + 1}`).join(", ");
|
|
|
|
const insertQuery = `INSERT INTO ${tableName} (${columns}) VALUES (${placeholders}) RETURNING *`;
|
|
|
|
logger.info(`INSERT 쿼리 실행:`, { query: insertQuery, values });
|
|
|
|
const result = await query<any>(insertQuery, values);
|
|
|
|
return {
|
|
success: true,
|
|
action: "insert",
|
|
tableName,
|
|
data: result,
|
|
affectedRows: result.length,
|
|
};
|
|
} catch (error) {
|
|
logger.error(`INSERT 실행 오류:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* UPDATE 실행
|
|
*/
|
|
async function executeUpdate(
|
|
tableName: string,
|
|
data: Record<string, any>
|
|
): Promise<any> {
|
|
try {
|
|
logger.info(`UPDATE 액션 시작:`, { tableName, receivedData: data });
|
|
|
|
// 1. 테이블의 실제 기본키 조회
|
|
const primaryKeyQuery = `
|
|
SELECT a.attname as column_name
|
|
FROM pg_index i
|
|
JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey)
|
|
WHERE i.indrelid = $1::regclass AND i.indisprimary
|
|
`;
|
|
|
|
const pkResult = await query<{ column_name: string }>(primaryKeyQuery, [
|
|
tableName,
|
|
]);
|
|
|
|
if (!pkResult || pkResult.length === 0) {
|
|
throw new Error(`테이블 ${tableName}의 기본키를 찾을 수 없습니다`);
|
|
}
|
|
|
|
const primaryKeyColumn = pkResult[0].column_name;
|
|
logger.info(`테이블 ${tableName}의 기본키:`, primaryKeyColumn);
|
|
|
|
// 2. 기본키 값 추출
|
|
const primaryKeyValue = data[primaryKeyColumn];
|
|
|
|
if (!primaryKeyValue && primaryKeyValue !== 0) {
|
|
logger.error(`UPDATE 실패: 기본키 값이 없음`, {
|
|
primaryKeyColumn,
|
|
receivedData: data,
|
|
availableKeys: Object.keys(data),
|
|
});
|
|
throw new Error(
|
|
`UPDATE를 위한 기본키 값이 필요합니다 (${primaryKeyColumn})`
|
|
);
|
|
}
|
|
|
|
// 3. 업데이트할 데이터에서 기본키 제외
|
|
const updateData = { ...data };
|
|
delete updateData[primaryKeyColumn];
|
|
|
|
logger.info(`UPDATE 데이터 준비:`, {
|
|
primaryKeyColumn,
|
|
primaryKeyValue,
|
|
updateFields: Object.keys(updateData),
|
|
});
|
|
|
|
// 4. 동적 UPDATE 쿼리 생성
|
|
const setClause = Object.keys(updateData)
|
|
.map((key, index) => `${key} = $${index + 1}`)
|
|
.join(", ");
|
|
|
|
const values = Object.values(updateData);
|
|
const updateQuery = `UPDATE ${tableName} SET ${setClause} WHERE ${primaryKeyColumn} = $${values.length + 1} RETURNING *`;
|
|
|
|
logger.info(`UPDATE 쿼리 실행:`, {
|
|
query: updateQuery,
|
|
values: [...values, primaryKeyValue],
|
|
});
|
|
|
|
const result = await query<any>(updateQuery, [...values, primaryKeyValue]);
|
|
|
|
logger.info(`UPDATE 성공:`, { affectedRows: result.length });
|
|
|
|
return {
|
|
success: true,
|
|
action: "update",
|
|
tableName,
|
|
data: result,
|
|
affectedRows: result.length,
|
|
};
|
|
} catch (error) {
|
|
logger.error(`UPDATE 실행 오류:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* UPSERT 실행
|
|
*/
|
|
async function executeUpsert(
|
|
tableName: string,
|
|
data: Record<string, any>
|
|
): Promise<any> {
|
|
try {
|
|
// 먼저 INSERT를 시도하고, 실패하면 UPDATE
|
|
try {
|
|
return await executeInsert(tableName, data);
|
|
} catch (insertError) {
|
|
// INSERT 실패 시 UPDATE 시도
|
|
logger.info(`INSERT 실패, UPDATE 시도:`, insertError);
|
|
return await executeUpdate(tableName, data);
|
|
}
|
|
} catch (error) {
|
|
logger.error(`UPSERT 실행 오류:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DELETE 실행
|
|
*/
|
|
async function executeDelete(
|
|
tableName: string,
|
|
data: Record<string, any>
|
|
): Promise<any> {
|
|
try {
|
|
const { id } = data;
|
|
|
|
if (!id) {
|
|
throw new Error("DELETE를 위한 ID가 필요합니다");
|
|
}
|
|
|
|
const deleteQuery = `DELETE FROM ${tableName} WHERE id = $1 RETURNING *`;
|
|
|
|
logger.info(`DELETE 쿼리 실행:`, { query: deleteQuery, values: [id] });
|
|
|
|
const result = await query<any>(deleteQuery, [id]);
|
|
|
|
return {
|
|
success: true,
|
|
action: "delete",
|
|
tableName,
|
|
data: result,
|
|
affectedRows: result.length,
|
|
};
|
|
} catch (error) {
|
|
logger.error(`DELETE 실행 오류:`, error);
|
|
throw error;
|
|
}
|
|
}
|