ERP-node/frontend/components/admin/dashboard/data-sources/dataSourceUtils.ts

194 lines
5.2 KiB
TypeScript
Raw Permalink Normal View History

2025-10-14 13:59:54 +09:00
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, string>): 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<string, any>[], 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";
}