/** * 다중 커넥션 관리 API 클라이언트 */ import { apiClient } from "./client"; /** * 데이터 타입을 웹 타입으로 매핑하는 함수 * @param dataType - 데이터베이스 데이터 타입 * @returns 웹 타입 문자열 */ const mapDataTypeToWebType = (dataType: string | undefined | null): string => { if (!dataType || typeof dataType !== "string") { console.warn(`⚠️ 잘못된 데이터 타입: ${dataType}, 기본값 'text' 사용`); return "text"; } const lowerType = dataType.toLowerCase(); // 텍스트 타입 if (lowerType.includes("varchar") || lowerType.includes("char") || lowerType.includes("text")) { return "text"; } // 숫자 타입 if (lowerType.includes("int") || lowerType.includes("bigint") || lowerType.includes("smallint")) { return "number"; } if ( lowerType.includes("decimal") || lowerType.includes("numeric") || lowerType.includes("float") || lowerType.includes("double") ) { return "decimal"; } // 날짜/시간 타입 if (lowerType.includes("timestamp") || lowerType.includes("datetime")) { return "datetime"; } if (lowerType.includes("date")) { return "date"; } if (lowerType.includes("time")) { return "time"; } // 불린 타입 if (lowerType.includes("boolean") || lowerType.includes("bit")) { return "boolean"; } // 바이너리/파일 타입 if (lowerType.includes("bytea") || lowerType.includes("blob") || lowerType.includes("binary")) { return "file"; } // JSON 타입 if (lowerType.includes("json")) { return "text"; } // 기본값 console.log(`🔍 알 수 없는 데이터 타입: ${dataType} → text로 매핑`); return "text"; }; /** * 컬럼명으로부터 코드 카테고리를 추론 * 실제 존재하는 카테고리만 반환하도록 개선 */ const inferCodeCategory = (columnName: string): string => { const lowerName = columnName.toLowerCase(); // 실제 데이터베이스에 존재하는 것으로 확인된 카테고리만 반환 if (lowerName.includes("status")) return "STATUS"; // 다른 카테고리들은 실제 존재 여부를 확인한 후 추가 // if (lowerName.includes("type")) return "TYPE"; // if (lowerName.includes("grade")) return "GRADE"; // if (lowerName.includes("level")) return "LEVEL"; // if (lowerName.includes("priority")) return "PRIORITY"; // if (lowerName.includes("category")) return "CATEGORY"; // if (lowerName.includes("role")) return "ROLE"; // 확인되지 않은 컬럼은 일단 STATUS로 매핑 (임시) return "STATUS"; }; export interface MultiConnectionTableInfo { tableName: string; displayName?: string; columnCount: number; connectionId: number; connectionName: string; dbType: string; } export interface ColumnInfo { columnName: string; displayName: string; dataType: string; dbType: string; webType: string; isNullable: boolean; isPrimaryKey: boolean; defaultValue?: string; maxLength?: number; description?: string; } export interface ConnectionInfo { id: number; connection_name: string; description?: string; db_type: string; host: string; port: number; database_name: string; username: string; is_active: string; company_code: string; created_date: Date; updated_date: Date; } export interface ValidationResult { isValid: boolean; error?: string; warnings?: string[]; } /** * 제어관리용 활성 커넥션 목록 조회 (메인 DB 포함) */ export const getActiveConnections = async (): Promise => { const response = await apiClient.get("/external-db-connections/control/active"); return response.data.data || []; }; /** * 특정 커넥션의 테이블 목록 조회 */ export const getTablesFromConnection = async (connectionId: number): Promise => { const response = await apiClient.get(`/multi-connection/connections/${connectionId}/tables`); return response.data.data || []; }; /** * 특정 커넥션의 모든 테이블 정보 배치 조회 (컬럼 수 포함) */ export const getBatchTablesWithColumns = async ( connectionId: number, ): Promise<{ tableName: string; displayName?: string; columnCount: number }[]> => { console.log(`🚀 getBatchTablesWithColumns 호출: connectionId=${connectionId}`); try { const response = await apiClient.get(`/multi-connection/connections/${connectionId}/tables/batch`); console.log("✅ 배치 테이블 정보 조회 성공:", response.data); const result = response.data.data || []; console.log(`📊 배치 조회 결과: ${result.length}개 테이블`, result); return result; } catch (error) { console.error("❌ 배치 테이블 정보 조회 실패:", error); throw error; } }; /** * 특정 커넥션의 테이블 컬럼 정보 조회 */ export const getColumnsFromConnection = async (connectionId: number, tableName: string): Promise => { console.log(`🔍 getColumnsFromConnection 호출: connectionId=${connectionId}, tableName=${tableName}`); try { // 메인 데이터베이스(connectionId = 0)인 경우 기존 API 사용 if (connectionId === 0) { console.log("📡 메인 DB API 호출:", `/table-management/tables/${tableName}/columns`); const response = await apiClient.get(`/table-management/tables/${tableName}/columns`); console.log("✅ 메인 DB 응답:", response.data); const rawResult = response.data.data || []; // 메인 DB는 페이지네이션 구조로 반환됨: {columns: [], total, page, size, totalPages} const columns = rawResult.columns || rawResult; // 메인 DB 컬럼에도 코드 타입 감지 로직 적용 const result = Array.isArray(columns) ? columns.map((col: any) => { const columnName = col.columnName || ""; // 컬럼명으로 코드 타입 감지 const isCodeColumn = columnName.toLowerCase().includes("code") || columnName.toLowerCase().includes("status") || columnName.toLowerCase().includes("type") || columnName.toLowerCase().includes("grade") || columnName.toLowerCase().includes("level"); return { ...col, webType: isCodeColumn ? "code" : col.webType || mapDataTypeToWebType(col.dataType), codeCategory: isCodeColumn ? inferCodeCategory(columnName) : col.codeCategory, }; }) : columns; console.log("📊 메인 DB 최종 결과:", { rawType: typeof rawResult, rawIsArray: Array.isArray(rawResult), hasColumns: rawResult && typeof rawResult === "object" && "columns" in rawResult, finalType: typeof result, finalIsArray: Array.isArray(result), length: Array.isArray(result) ? result.length : "N/A", sample: Array.isArray(result) ? result.slice(0, 1) : result, }); return result; } // 외부 커넥션인 경우 external-db-connections API 사용 console.log("📡 외부 DB API 호출:", `/external-db-connections/${connectionId}/tables/${tableName}/columns`); const response = await apiClient.get(`/external-db-connections/${connectionId}/tables/${tableName}/columns`); console.log("✅ 외부 DB 응답:", response.data); const rawResult = response.data.data || []; // 외부 DB 컬럼 구조를 메인 DB 형식으로 변환 const result = Array.isArray(rawResult) ? rawResult.map((col: any) => { const columnName = col.column_name || col.columnName || ""; const dataType = col.data_type || col.dataType || "unknown"; // 컬럼명이 '_code'로 끝나거나 'status', 'type' 등의 이름을 가진 경우 코드 타입으로 간주 const isCodeColumn = columnName.toLowerCase().includes("code") || columnName.toLowerCase().includes("status") || columnName.toLowerCase().includes("type") || columnName.toLowerCase().includes("grade") || columnName.toLowerCase().includes("level"); return { columnName: columnName, displayName: col.column_comment || col.displayName || columnName, dataType: dataType, dbType: dataType, webType: isCodeColumn ? "code" : mapDataTypeToWebType(dataType), isNullable: col.is_nullable === "YES" || col.isNullable === true, columnDefault: col.column_default || col.columnDefault, description: col.column_comment || col.description, // 코드 타입인 경우 카테고리 추론 codeCategory: isCodeColumn ? inferCodeCategory(columnName) : undefined, }; }) : rawResult; console.log("📊 외부 DB 최종 결과:", { rawType: typeof rawResult, rawIsArray: Array.isArray(rawResult), finalType: typeof result, finalIsArray: Array.isArray(result), length: Array.isArray(result) ? result.length : "N/A", sample: Array.isArray(result) ? result.slice(0, 1) : result, sampleOriginal: Array.isArray(rawResult) ? rawResult.slice(0, 1) : rawResult, }); return result; } catch (error) { console.error("❌ 컬럼 정보 조회 실패:", error); // 개발 환경에서 Mock 데이터 반환 if (process.env.NODE_ENV === "development") { console.warn("🔄 개발 환경: Mock 컬럼 데이터 사용"); const mockResult = getMockColumnsForTable(tableName); console.log("📊 Mock 데이터 반환:", { type: typeof mockResult, isArray: Array.isArray(mockResult), length: mockResult.length, sample: mockResult.slice(0, 1), }); return mockResult; } throw error; } }; /** * Mock 컬럼 데이터 (개발/테스트용) */ const getMockColumnsForTable = (tableName: string): ColumnInfo[] => { const baseColumns: ColumnInfo[] = [ { columnName: "id", displayName: "ID", dataType: "NUMBER", webType: "number", isNullable: false, isPrimaryKey: true, columnComment: "고유 식별자", }, { columnName: "name", displayName: "이름", dataType: "VARCHAR", webType: "text", isNullable: false, isPrimaryKey: false, columnComment: "이름", }, { columnName: "status", displayName: "상태", dataType: "VARCHAR", webType: "code", isNullable: true, isPrimaryKey: false, columnComment: "상태 코드", codeCategory: "STATUS", }, { columnName: "created_date", displayName: "생성일시", dataType: "TIMESTAMP", webType: "datetime", isNullable: true, isPrimaryKey: false, columnComment: "생성일시", }, { columnName: "updated_date", displayName: "수정일시", dataType: "TIMESTAMP", webType: "datetime", isNullable: true, isPrimaryKey: false, columnComment: "수정일시", }, ]; // 테이블명에 따라 추가 컬럼 포함 if (tableName.toLowerCase().includes("user")) { baseColumns.push({ columnName: "email", displayName: "이메일", dataType: "VARCHAR", webType: "email", isNullable: true, isPrimaryKey: false, columnComment: "이메일 주소", }); } if (tableName.toLowerCase().includes("product")) { baseColumns.push({ columnName: "price", displayName: "가격", dataType: "DECIMAL", webType: "decimal", isNullable: true, isPrimaryKey: false, columnComment: "상품 가격", }); } return baseColumns; }; /** * 특정 커넥션에서 데이터 조회 */ export const queryDataFromConnection = async ( connectionId: number, tableName: string, conditions?: Record, ): Promise[]> => { const response = await apiClient.post(`/multi-connection/connections/${connectionId}/query`, { tableName, conditions, }); return response.data.data || []; }; /** * 특정 커넥션에 데이터 삽입 */ export const insertDataToConnection = async ( connectionId: number, tableName: string, data: Record, ): Promise => { const response = await apiClient.post(`/multi-connection/connections/${connectionId}/insert`, { tableName, data, }); return response.data.data || {}; }; /** * 특정 커넥션의 데이터 업데이트 */ export const updateDataInConnection = async ( connectionId: number, tableName: string, data: Record, conditions: Record, ): Promise => { const response = await apiClient.put(`/multi-connection/connections/${connectionId}/update`, { tableName, data, conditions, }); return response.data.data || {}; }; /** * 특정 커넥션에서 데이터 삭제 */ export const deleteDataFromConnection = async ( connectionId: number, tableName: string, conditions: Record, maxDeleteCount?: number, ): Promise => { const response = await apiClient.delete(`/multi-connection/connections/${connectionId}/delete`, { data: { tableName, conditions, maxDeleteCount, }, }); return response.data.data || {}; }; /** * 자기 자신 테이블 작업 검증 */ export const validateSelfTableOperation = async ( tableName: string, operation: "update" | "delete", conditions: any[], ): Promise => { const response = await apiClient.post("/multi-connection/validate-self-operation", { tableName, operation, conditions, }); return response.data.data || {}; };