Files
vexplor/backend-node/src/database/RestApiConnector.ts

330 lines
9.2 KiB
TypeScript
Raw Normal View History

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