import axios, { AxiosInstance, AxiosResponse } from "axios"; import { DatabaseConnector, ConnectionConfig, QueryResult, } from "../interfaces/DatabaseConnector"; import { ConnectionTestResult, TableInfo } from "../types/externalDbTypes"; export interface RestApiConfig { baseUrl: string; apiKey: string; timeout?: number; // ConnectionConfig 호환성을 위한 더미 필드들 (사용하지 않음) host?: string; port?: number; database?: string; user?: string; password?: string; } export class RestApiConnector implements DatabaseConnector { private httpClient: AxiosInstance; private config: RestApiConfig; constructor(config: RestApiConfig) { this.config = config; // Axios 인스턴스 생성 this.httpClient = axios.create({ baseURL: config.baseUrl, timeout: config.timeout || 30000, headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}`, Accept: "application/json", }, }); // 요청/응답 인터셉터 설정 this.setupInterceptors(); } private setupInterceptors() { // 요청 인터셉터 this.httpClient.interceptors.request.use( (config) => { console.log( `[RestApiConnector] 요청: ${config.method?.toUpperCase()} ${config.url}` ); return config; }, (error) => { console.error("[RestApiConnector] 요청 오류:", error); return Promise.reject(error); } ); // 응답 인터셉터 this.httpClient.interceptors.response.use( (response) => { console.log( `[RestApiConnector] 응답: ${response.status} ${response.statusText}` ); return response; }, (error) => { console.error( "[RestApiConnector] 응답 오류:", error.response?.status, error.response?.statusText ); return Promise.reject(error); } ); } async connect(): Promise { try { // 연결 테스트 - 기본 엔드포인트 호출 await this.httpClient.get("/health", { timeout: 5000 }); console.log(`[RestApiConnector] 연결 성공: ${this.config.baseUrl}`); } catch (error) { // health 엔드포인트가 없을 수 있으므로 404는 정상으로 처리 if (axios.isAxiosError(error) && error.response?.status === 404) { console.log( `[RestApiConnector] 연결 성공 (health 엔드포인트 없음): ${this.config.baseUrl}` ); return; } console.error( `[RestApiConnector] 연결 실패: ${this.config.baseUrl}`, error ); throw new Error( `REST API 연결 실패: ${error instanceof Error ? error.message : "알 수 없는 오류"}` ); } } async disconnect(): Promise { // REST API는 연결 해제가 필요 없음 console.log(`[RestApiConnector] 연결 해제: ${this.config.baseUrl}`); } async testConnection(): Promise { try { await this.connect(); return { success: true, message: "REST API 연결이 성공했습니다.", details: { response_time: Date.now(), }, }; } catch (error) { return { success: false, message: error instanceof Error ? error.message : "REST API 연결에 실패했습니다.", details: { response_time: Date.now(), }, }; } } // 🔥 DatabaseConnector 인터페이스 호환용 executeQuery (사용하지 않음) async executeQuery(query: string, params?: any[]): Promise { // REST API는 executeRequest를 사용해야 함 throw new Error( "REST API Connector는 executeQuery를 지원하지 않습니다. executeRequest를 사용하세요." ); } // 🔥 실제 REST API 요청을 위한 메서드 async executeRequest( endpoint: string, method: "GET" | "POST" | "PUT" | "DELETE" = "GET", data?: any ): Promise { try { const startTime = Date.now(); let response: AxiosResponse; // HTTP 메서드에 따른 요청 실행 switch (method.toUpperCase()) { case "GET": response = await this.httpClient.get(endpoint); break; case "POST": response = await this.httpClient.post(endpoint, data); break; case "PUT": response = await this.httpClient.put(endpoint, data); break; case "DELETE": response = await this.httpClient.delete(endpoint); break; default: throw new Error(`지원하지 않는 HTTP 메서드: ${method}`); } const executionTime = Date.now() - startTime; const responseData = response.data; console.log(`[RestApiConnector] 원본 응답 데이터:`, { type: typeof responseData, isArray: Array.isArray(responseData), keys: typeof responseData === "object" ? Object.keys(responseData) : "not object", responseData: responseData, }); // 응답 데이터 처리 let rows: any[]; if (Array.isArray(responseData)) { rows = responseData; } else if ( responseData && responseData.data && Array.isArray(responseData.data) ) { // API 응답이 {success: true, data: [...]} 형태인 경우 rows = responseData.data; } else if ( responseData && responseData.data && typeof responseData.data === "object" ) { // API 응답이 {success: true, data: {...}} 형태인 경우 (단일 객체) rows = [responseData.data]; } else if ( responseData && typeof responseData === "object" && !Array.isArray(responseData) ) { // 단일 객체 응답인 경우 rows = [responseData]; } else { rows = []; } console.log(`[RestApiConnector] 처리된 rows:`, { rowsLength: rows.length, firstRow: rows.length > 0 ? rows[0] : "no data", allRows: rows, }); console.log(`[RestApiConnector] API 호출 결과:`, { endpoint, method, status: response.status, rowCount: rows.length, executionTime: `${executionTime}ms`, }); return { rows: rows, rowCount: rows.length, fields: rows.length > 0 ? Object.keys(rows[0]).map((key) => ({ name: key, type: "string" })) : [], }; } catch (error) { console.error( `[RestApiConnector] API 호출 오류 (${method} ${endpoint}):`, error ); if (axios.isAxiosError(error)) { throw new Error( `REST API 호출 실패: ${error.response?.status} ${error.response?.statusText}` ); } throw new Error( `REST API 호출 실패: ${error instanceof Error ? error.message : "알 수 없는 오류"}` ); } } async getTables(): Promise { // REST API의 경우 "테이블"은 사용 가능한 엔드포인트를 의미 // 일반적인 REST API 엔드포인트들을 반환 return [ { table_name: "/api/users", columns: [], description: "사용자 정보 API", }, { table_name: "/api/data", columns: [], description: "기본 데이터 API", }, { table_name: "/api/custom", columns: [], description: "사용자 정의 엔드포인트", }, ]; } async getTableList(): Promise { return this.getTables(); } async getColumns(endpoint: string): Promise { try { // GET 요청으로 샘플 데이터를 가져와서 필드 구조 파악 const result = await this.executeRequest(endpoint, "GET"); if (result.rows.length > 0) { const sampleRow = result.rows[0]; return Object.keys(sampleRow).map((key) => ({ column_name: key, data_type: typeof sampleRow[key], is_nullable: "YES", column_default: null, description: `${key} 필드`, })); } return []; } catch (error) { console.error( `[RestApiConnector] 컬럼 정보 조회 오류 (${endpoint}):`, error ); return []; } } async getTableColumns(endpoint: string): Promise { return this.getColumns(endpoint); } // REST API 전용 메서드들 async getData( endpoint: string, params?: Record ): Promise { const queryString = params ? "?" + new URLSearchParams(params).toString() : ""; const result = await this.executeRequest(endpoint + queryString, "GET"); return result.rows; } async postData(endpoint: string, data: any): Promise { const result = await this.executeRequest(endpoint, "POST", data); return result.rows[0]; } async putData(endpoint: string, data: any): Promise { const result = await this.executeRequest(endpoint, "PUT", data); return result.rows[0]; } async deleteData(endpoint: string): Promise { const result = await this.executeRequest(endpoint, "DELETE"); return result.rows[0]; } }