import { QueryResult } from "../types"; /** * JSON Path를 사용하여 객체에서 데이터 추출 * @param obj JSON 객체 * @param path 경로 (예: "data.results", "items") * @returns 추출된 데이터 */ export function extractDataFromJsonPath(obj: any, path: string): any { if (!path || path.trim() === "") { return obj; } const keys = path.split("."); let result = obj; for (const key of keys) { if (result === null || result === undefined) { return null; } result = result[key]; } return result; } /** * API 응답을 QueryResult 형식으로 변환 * @param data API 응답 데이터 * @param jsonPath JSON Path (선택) * @returns QueryResult */ export function transformApiResponseToQueryResult(data: any, jsonPath?: string): QueryResult { try { // JSON Path가 있으면 데이터 추출 let extractedData = jsonPath ? extractDataFromJsonPath(data, jsonPath) : data; // 배열이 아니면 배열로 변환 if (!Array.isArray(extractedData)) { // 객체인 경우 키-값 쌍을 배열로 변환 if (typeof extractedData === "object" && extractedData !== null) { extractedData = Object.entries(extractedData).map(([key, value]) => ({ key, value, })); } else { throw new Error("데이터가 배열 또는 객체 형식이 아닙니다"); } } if (extractedData.length === 0) { return { columns: [], rows: [], totalRows: 0, executionTime: 0, }; } // 첫 번째 행에서 컬럼 추출 const firstRow = extractedData[0]; const columns = Object.keys(firstRow); return { columns, rows: extractedData, totalRows: extractedData.length, executionTime: 0, }; } catch (error) { throw new Error(`API 응답 변환 실패: ${error instanceof Error ? error.message : "알 수 없는 오류"}`); } } /** * 데이터 소스가 유효한지 검증 * @param type 데이터 소스 타입 * @param connectionType 커넥션 타입 (DB일 때) * @param externalConnectionId 외부 커넥션 ID (외부 DB일 때) * @param query SQL 쿼리 (DB일 때) * @param endpoint API URL (API일 때) * @returns 유효성 검증 결과 */ export function validateDataSource( type: "database" | "api", connectionType?: "current" | "external", externalConnectionId?: string, query?: string, endpoint?: string, ): { valid: boolean; message?: string } { if (type === "database") { // DB 검증 if (!connectionType) { return { valid: false, message: "데이터베이스 타입을 선택하세요" }; } if (connectionType === "external" && !externalConnectionId) { return { valid: false, message: "외부 커넥션을 선택하세요" }; } if (!query || query.trim() === "") { return { valid: false, message: "SQL 쿼리를 입력하세요" }; } // SELECT 쿼리인지 검증 (간단한 검증) const trimmedQuery = query.trim().toLowerCase(); if (!trimmedQuery.startsWith("select")) { return { valid: false, message: "SELECT 쿼리만 허용됩니다" }; } // 위험한 키워드 체크 const dangerousKeywords = ["drop", "delete", "insert", "update", "truncate", "alter", "create", "exec", "execute"]; for (const keyword of dangerousKeywords) { if (trimmedQuery.includes(keyword)) { return { valid: false, message: `보안상 ${keyword.toUpperCase()} 명령은 사용할 수 없습니다`, }; } } return { valid: true }; } else if (type === "api") { // API 검증 if (!endpoint || endpoint.trim() === "") { return { valid: false, message: "API URL을 입력하세요" }; } // URL 형식 검증 try { new URL(endpoint); } catch { return { valid: false, message: "올바른 URL 형식이 아닙니다" }; } return { valid: true }; } return { valid: false, message: "알 수 없는 데이터 소스 타입입니다" }; } /** * 쿼리 파라미터를 URL에 추가 * @param baseUrl 기본 URL * @param params 쿼리 파라미터 객체 * @returns 쿼리 파라미터가 추가된 URL */ export function buildUrlWithParams(baseUrl: string, params?: Record): string { if (!params || Object.keys(params).length === 0) { return baseUrl; } const url = new URL(baseUrl); Object.entries(params).forEach(([key, value]) => { if (key && value) { url.searchParams.append(key, value); } }); return url.toString(); } /** * 컬럼 데이터 타입 추론 * @param rows 데이터 행 * @param columnName 컬럼명 * @returns 데이터 타입 ('string' | 'number' | 'date' | 'boolean') */ export function inferColumnType(rows: Record[], columnName: string): string { if (rows.length === 0) { return "string"; } const sampleValue = rows[0][columnName]; if (typeof sampleValue === "number") { return "number"; } if (typeof sampleValue === "boolean") { return "boolean"; } if (typeof sampleValue === "string") { // 날짜 형식인지 확인 if (!isNaN(Date.parse(sampleValue))) { return "date"; } return "string"; } return "string"; }