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', 'X-API-Key': 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() } }; } } async executeQuery(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.executeQuery(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.executeQuery(endpoint + queryString, 'GET'); return result.rows; } async postData(endpoint: string, data: any): Promise { const result = await this.executeQuery(endpoint, 'POST', data); return result.rows[0]; } async putData(endpoint: string, data: any): Promise { const result = await this.executeQuery(endpoint, 'PUT', data); return result.rows[0]; } async deleteData(endpoint: string): Promise { const result = await this.executeQuery(endpoint, 'DELETE'); return result.rows[0]; } }