# 외부 커넥션 관리 REST API 지원 확장 계획서 ## 📋 프로젝트 개요 ### 목적 현재 외부 데이터베이스 연결만 관리하는 `/admin/external-connections` 페이지에 REST API 연결 관리 기능을 추가하여, DB와 REST API 커넥션을 통합 관리할 수 있도록 확장합니다. ### 현재 상황 - **기존 기능**: 외부 데이터베이스 연결 정보만 관리 (MySQL, PostgreSQL, Oracle, SQL Server, SQLite) - **기존 테이블**: `external_db_connections` - DB 연결 정보 저장 - **기존 UI**: 단일 화면에서 DB 연결 목록 표시 및 CRUD 작업 ### 요구사항 1. **탭 전환**: DB 연결 관리 ↔ REST API 연결 관리 간 탭 전환 UI 2. **REST API 관리**: 요청 주소별 헤더(키-값 쌍) 관리 3. **연결 테스트**: REST API 호출이 정상 작동하는지 테스트 기능 --- ## 🗄️ 데이터베이스 설계 ### 신규 테이블: `external_rest_api_connections` ```sql CREATE TABLE external_rest_api_connections ( id SERIAL PRIMARY KEY, -- 기본 정보 connection_name VARCHAR(100) NOT NULL UNIQUE, description TEXT, -- REST API 연결 정보 base_url VARCHAR(500) NOT NULL, -- 기본 URL (예: https://api.example.com) default_headers JSONB DEFAULT '{}', -- 기본 헤더 정보 (키-값 쌍) -- 인증 설정 auth_type VARCHAR(20) DEFAULT 'none', -- none, api-key, bearer, basic, oauth2 auth_config JSONB, -- 인증 관련 설정 -- 고급 설정 timeout INTEGER DEFAULT 30000, -- 요청 타임아웃 (ms) retry_count INTEGER DEFAULT 0, -- 재시도 횟수 retry_delay INTEGER DEFAULT 1000, -- 재시도 간격 (ms) -- 관리 정보 company_code VARCHAR(20) DEFAULT '*', is_active CHAR(1) DEFAULT 'Y', created_date TIMESTAMP DEFAULT NOW(), created_by VARCHAR(50), updated_date TIMESTAMP DEFAULT NOW(), updated_by VARCHAR(50), -- 테스트 정보 last_test_date TIMESTAMP, last_test_result CHAR(1), -- Y: 성공, N: 실패 last_test_message TEXT ); -- 인덱스 CREATE INDEX idx_rest_api_connections_company ON external_rest_api_connections(company_code); CREATE INDEX idx_rest_api_connections_active ON external_rest_api_connections(is_active); CREATE INDEX idx_rest_api_connections_name ON external_rest_api_connections(connection_name); ``` ### 샘플 데이터 ```sql INSERT INTO external_rest_api_connections ( connection_name, description, base_url, default_headers, auth_type, auth_config ) VALUES ( '기상청 API', '기상청 공공데이터 API', 'https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0', '{"Content-Type": "application/json", "Accept": "application/json"}', 'api-key', '{"keyLocation": "query", "keyName": "serviceKey", "keyValue": "your-api-key-here"}' ), ( '사내 인사 시스템 API', '인사정보 조회용 내부 API', 'https://hr.company.com/api/v1', '{"Content-Type": "application/json"}', 'bearer', '{"token": "your-bearer-token-here"}' ); ``` --- ## 🔧 백엔드 구현 ### 1. 타입 정의 ```typescript // backend-node/src/types/externalRestApiTypes.ts export type AuthType = "none" | "api-key" | "bearer" | "basic" | "oauth2"; export interface ExternalRestApiConnection { id?: number; connection_name: string; description?: string; base_url: string; default_headers: Record; auth_type: AuthType; auth_config?: { // API Key keyLocation?: "header" | "query"; keyName?: string; keyValue?: string; // Bearer Token token?: string; // Basic Auth username?: string; password?: string; // OAuth2 clientId?: string; clientSecret?: string; tokenUrl?: string; accessToken?: string; }; timeout?: number; retry_count?: number; retry_delay?: number; company_code: string; is_active: string; created_date?: Date; created_by?: string; updated_date?: Date; updated_by?: string; last_test_date?: Date; last_test_result?: string; last_test_message?: string; } export interface ExternalRestApiConnectionFilter { auth_type?: string; is_active?: string; company_code?: string; search?: string; } export interface RestApiTestRequest { id?: number; base_url: string; endpoint?: string; // 테스트할 엔드포인트 (선택) method?: "GET" | "POST" | "PUT" | "DELETE"; headers?: Record; auth_type?: AuthType; auth_config?: any; timeout?: number; } export interface RestApiTestResult { success: boolean; message: string; response_time?: number; status_code?: number; response_data?: any; error_details?: string; } ``` ### 2. 서비스 계층 ```typescript // backend-node/src/services/externalRestApiConnectionService.ts export class ExternalRestApiConnectionService { // CRUD 메서드 static async getConnections(filter: ExternalRestApiConnectionFilter); static async getConnectionById(id: number); static async createConnection(data: ExternalRestApiConnection); static async updateConnection( id: number, data: Partial ); static async deleteConnection(id: number); // 테스트 메서드 static async testConnection( testRequest: RestApiTestRequest ): Promise; static async testConnectionById( id: number, endpoint?: string ): Promise; // 헬퍼 메서드 private static buildHeaders( connection: ExternalRestApiConnection ): Record; private static validateConnectionData(data: ExternalRestApiConnection): void; private static encryptSensitiveData(authConfig: any): any; private static decryptSensitiveData(authConfig: any): any; } ``` ### 3. API 라우트 ```typescript // backend-node/src/routes/externalRestApiConnectionRoutes.ts // GET /api/external-rest-api-connections - 목록 조회 // GET /api/external-rest-api-connections/:id - 상세 조회 // POST /api/external-rest-api-connections - 새 연결 생성 // PUT /api/external-rest-api-connections/:id - 연결 수정 // DELETE /api/external-rest-api-connections/:id - 연결 삭제 // POST /api/external-rest-api-connections/test - 연결 테스트 (신규) // POST /api/external-rest-api-connections/:id/test - ID로 테스트 (기존 연결) ``` ### 4. 연결 테스트 구현 ```typescript // REST API 연결 테스트 로직 static async testConnection(testRequest: RestApiTestRequest): Promise { const startTime = Date.now(); try { // 헤더 구성 const headers = { ...testRequest.headers }; // 인증 헤더 추가 if (testRequest.auth_type === 'bearer' && testRequest.auth_config?.token) { headers['Authorization'] = `Bearer ${testRequest.auth_config.token}`; } else if (testRequest.auth_type === 'basic') { const credentials = Buffer.from( `${testRequest.auth_config.username}:${testRequest.auth_config.password}` ).toString('base64'); headers['Authorization'] = `Basic ${credentials}`; } else if (testRequest.auth_type === 'api-key') { if (testRequest.auth_config.keyLocation === 'header') { headers[testRequest.auth_config.keyName] = testRequest.auth_config.keyValue; } } // URL 구성 let url = testRequest.base_url; if (testRequest.endpoint) { url = `${testRequest.base_url}${testRequest.endpoint}`; } // API Key가 쿼리에 있는 경우 if (testRequest.auth_type === 'api-key' && testRequest.auth_config.keyLocation === 'query') { const separator = url.includes('?') ? '&' : '?'; url = `${url}${separator}${testRequest.auth_config.keyName}=${testRequest.auth_config.keyValue}`; } // HTTP 요청 실행 const response = await fetch(url, { method: testRequest.method || 'GET', headers, signal: AbortSignal.timeout(testRequest.timeout || 30000), }); const responseTime = Date.now() - startTime; const responseData = await response.json().catch(() => null); return { success: response.ok, message: response.ok ? '연결 성공' : `연결 실패 (${response.status})`, response_time: responseTime, status_code: response.status, response_data: responseData, }; } catch (error) { return { success: false, message: '연결 실패', error_details: error instanceof Error ? error.message : '알 수 없는 오류', }; } } ``` --- ## 🎨 프론트엔드 구현 ### 1. 탭 구조 설계 ```typescript // frontend/app/(main)/admin/external-connections/page.tsx type ConnectionTabType = "database" | "rest-api"; const [activeTab, setActiveTab] = useState("database"); ``` ### 2. 메인 페이지 구조 개선 ```tsx // 탭 헤더 setActiveTab(value as ConnectionTabType)} > 데이터베이스 연결 REST API 연결 {/* 데이터베이스 연결 탭 */} {/* REST API 연결 탭 */} ``` ### 3. REST API 연결 목록 컴포넌트 ```typescript // frontend/components/admin/RestApiConnectionList.tsx export function RestApiConnectionList() { const [connections, setConnections] = useState( [] ); const [searchTerm, setSearchTerm] = useState(""); const [authTypeFilter, setAuthTypeFilter] = useState("ALL"); const [isModalOpen, setIsModalOpen] = useState(false); const [editingConnection, setEditingConnection] = useState< ExternalRestApiConnection | undefined >(); // 테이블 컬럼: // - 연결명 // - 기본 URL // - 인증 타입 // - 헤더 수 (default_headers 개수) // - 상태 (활성/비활성) // - 마지막 테스트 (날짜 + 결과) // - 작업 (테스트/편집/삭제) } ``` ### 4. REST API 연결 설정 모달 ```typescript // frontend/components/admin/RestApiConnectionModal.tsx export function RestApiConnectionModal({ isOpen, onClose, onSave, connection, }: RestApiConnectionModalProps) { // 섹션 구성: // 1. 기본 정보 // - 연결명 (필수) // - 설명 // - 기본 URL (필수) // 2. 헤더 관리 (키-값 추가/삭제) // - 동적 입력 필드 // - + 버튼으로 추가 // - 각 행에 삭제 버튼 // 3. 인증 설정 // - 인증 타입 선택 (none/api-key/bearer/basic/oauth2) // - 선택된 타입별 설정 필드 표시 // 4. 고급 설정 (접기/펼치기) // - 타임아웃 // - 재시도 설정 // 5. 테스트 섹션 // - 테스트 엔드포인트 입력 (선택) // - 테스트 실행 버튼 // - 테스트 결과 표시 } ``` ### 5. 헤더 관리 컴포넌트 ```typescript // frontend/components/admin/HeadersManager.tsx interface HeadersManagerProps { headers: Record; onChange: (headers: Record) => void; } export function HeadersManager({ headers, onChange }: HeadersManagerProps) { const [headersList, setHeadersList] = useState< Array<{ key: string; value: string }> >(Object.entries(headers).map(([key, value]) => ({ key, value }))); const addHeader = () => { setHeadersList([...headersList, { key: "", value: "" }]); }; const removeHeader = (index: number) => { const newList = headersList.filter((_, i) => i !== index); setHeadersList(newList); updateParent(newList); }; const updateHeader = ( index: number, field: "key" | "value", value: string ) => { const newList = [...headersList]; newList[index][field] = value; setHeadersList(newList); updateParent(newList); }; const updateParent = (list: Array<{ key: string; value: string }>) => { const headersObject = list.reduce((acc, { key, value }) => { if (key.trim()) acc[key] = value; return acc; }, {} as Record); onChange(headersObject); }; // UI: 테이블 형태로 키-값 입력 필드 표시 // 각 행: [키 입력] [값 입력] [삭제 버튼] // 하단: [+ 헤더 추가] 버튼 } ``` ### 6. 인증 설정 컴포넌트 ```typescript // frontend/components/admin/AuthenticationConfig.tsx export function AuthenticationConfig({ authType, authConfig, onChange, }: AuthenticationConfigProps) { // authType에 따라 다른 입력 필드 표시 // none: 추가 필드 없음 // api-key: // - 키 위치 (header/query) // - 키 이름 // - 키 값 // bearer: // - 토큰 값 // basic: // - 사용자명 // - 비밀번호 // oauth2: // - Client ID // - Client Secret // - Token URL // - Access Token (읽기전용, 자동 갱신) } ``` ### 7. API 클라이언트 ```typescript // frontend/lib/api/externalRestApiConnection.ts export class ExternalRestApiConnectionAPI { private static readonly BASE_URL = "/api/external-rest-api-connections"; static async getConnections(filter?: ExternalRestApiConnectionFilter) { const params = new URLSearchParams(); if (filter?.search) params.append("search", filter.search); if (filter?.auth_type && filter.auth_type !== "ALL") { params.append("auth_type", filter.auth_type); } if (filter?.is_active && filter.is_active !== "ALL") { params.append("is_active", filter.is_active); } const response = await fetch(`${this.BASE_URL}?${params}`); return this.handleResponse(response); } static async getConnectionById(id: number) { const response = await fetch(`${this.BASE_URL}/${id}`); return this.handleResponse(response); } static async createConnection(data: ExternalRestApiConnection) { const response = await fetch(this.BASE_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); return this.handleResponse(response); } static async updateConnection( id: number, data: Partial ) { const response = await fetch(`${this.BASE_URL}/${id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); return this.handleResponse(response); } static async deleteConnection(id: number) { const response = await fetch(`${this.BASE_URL}/${id}`, { method: "DELETE", }); return this.handleResponse(response); } static async testConnection( testRequest: RestApiTestRequest ): Promise { const response = await fetch(`${this.BASE_URL}/test`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(testRequest), }); return this.handleResponse(response); } static async testConnectionById( id: number, endpoint?: string ): Promise { const response = await fetch(`${this.BASE_URL}/${id}/test`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ endpoint }), }); return this.handleResponse(response); } private static async handleResponse(response: Response) { if (!response.ok) { const error = await response.json().catch(() => ({})); throw new Error(error.message || "요청 실패"); } return response.json(); } } ``` --- ## 📋 구현 순서 ### Phase 1: 데이터베이스 및 백엔드 기본 구조 (1일) - [x] 데이터베이스 테이블 생성 (`external_rest_api_connections`) - [ ] 타입 정의 작성 (`externalRestApiTypes.ts`) - [ ] 서비스 계층 기본 CRUD 구현 - [ ] API 라우트 기본 구현 ### Phase 2: 연결 테스트 기능 (1일) - [ ] 연결 테스트 로직 구현 - [ ] 인증 타입별 헤더 구성 로직 - [ ] 에러 처리 및 타임아웃 관리 - [ ] 테스트 결과 저장 (last_test_date, last_test_result) ### Phase 3: 프론트엔드 기본 UI (1-2일) - [ ] 탭 구조 추가 (Database / REST API) - [ ] REST API 연결 목록 컴포넌트 - [ ] API 클라이언트 작성 - [ ] 기본 CRUD UI 구현 ### Phase 4: 모달 및 상세 기능 (1-2일) - [ ] REST API 연결 설정 모달 - [ ] 헤더 관리 컴포넌트 (키-값 동적 추가/삭제) - [ ] 인증 설정 컴포넌트 (타입별 입력 필드) - [ ] 고급 설정 섹션 ### Phase 5: 테스트 및 통합 (1일) - [ ] 연결 테스트 UI - [ ] 테스트 결과 표시 - [ ] 에러 처리 및 사용자 피드백 - [ ] 전체 기능 통합 테스트 ### Phase 6: 최적화 및 마무리 (0.5일) - [ ] 민감 정보 암호화 (API 키, 토큰, 비밀번호) - [ ] UI/UX 개선 - [ ] 문서화 --- ## 🧪 테스트 시나리오 ### 1. REST API 연결 등록 테스트 - [ ] 기본 정보 입력 (연결명, URL) - [ ] 헤더 추가/삭제 - [ ] 각 인증 타입별 설정 - [ ] 유효성 검증 (필수 필드, URL 형식) ### 2. 연결 테스트 - [ ] 인증 없는 API 테스트 - [ ] API Key (header/query) 테스트 - [ ] Bearer Token 테스트 - [ ] Basic Auth 테스트 - [ ] 타임아웃 시나리오 - [ ] 네트워크 오류 시나리오 ### 3. 데이터 관리 - [ ] 목록 조회 및 필터링 - [ ] 연결 수정 - [ ] 연결 삭제 - [ ] 활성/비활성 전환 ### 4. 통합 시나리오 - [ ] DB 연결 탭 ↔ REST API 탭 전환 - [ ] 여러 연결 등록 및 관리 - [ ] 동시 테스트 실행 --- ## 🔒 보안 고려사항 ### 1. 민감 정보 암호화 ```typescript // API 키, 토큰, 비밀번호 암호화 private static encryptSensitiveData(authConfig: any): any { if (!authConfig) return null; const encrypted = { ...authConfig }; // 암호화 대상 필드 if (encrypted.keyValue) { encrypted.keyValue = encrypt(encrypted.keyValue); } if (encrypted.token) { encrypted.token = encrypt(encrypted.token); } if (encrypted.password) { encrypted.password = encrypt(encrypted.password); } if (encrypted.clientSecret) { encrypted.clientSecret = encrypt(encrypted.clientSecret); } return encrypted; } ``` ### 2. 접근 권한 제어 - 관리자 권한만 접근 - 회사별 데이터 분리 - API 호출 시 인증 토큰 검증 ### 3. 테스트 요청 제한 - Rate Limiting (1분에 최대 10회) - 타임아웃 설정 (최대 30초) - 동시 테스트 제한 --- ## 📊 성능 최적화 ### 1. 헤더 데이터 구조 ```typescript // JSONB 필드 인덱싱 (PostgreSQL) CREATE INDEX idx_rest_api_headers ON external_rest_api_connections USING GIN (default_headers); CREATE INDEX idx_rest_api_auth_config ON external_rest_api_connections USING GIN (auth_config); ``` ### 2. 캐싱 전략 - 자주 사용되는 연결 정보 캐싱 - 테스트 결과 임시 캐싱 (5분) --- ## 📚 향후 확장 가능성 ### 1. 엔드포인트 관리 각 REST API 연결에 대해 자주 사용하는 엔드포인트를 사전 등록하여 빠른 호출 가능 ### 2. 요청 템플릿 HTTP 메서드별 요청 바디 템플릿 관리 ### 3. 응답 매핑 REST API 응답을 내부 데이터 구조로 변환하는 매핑 룰 설정 ### 4. 로그 및 모니터링 - API 호출 이력 기록 - 응답 시간 모니터링 - 오류율 추적 --- ## ✅ 완료 체크리스트 ### 백엔드 - [ ] 데이터베이스 테이블 생성 - [ ] 타입 정의 - [ ] 서비스 계층 CRUD - [ ] 연결 테스트 로직 - [ ] API 라우트 - [ ] 민감 정보 암호화 ### 프론트엔드 - [ ] 탭 구조 - [ ] REST API 연결 목록 - [ ] 연결 설정 모달 - [ ] 헤더 관리 컴포넌트 - [ ] 인증 설정 컴포넌트 - [ ] API 클라이언트 - [ ] 연결 테스트 UI ### 테스트 - [ ] 단위 테스트 - [ ] 통합 테스트 - [ ] 사용자 시나리오 테스트 ### 문서 - [ ] API 문서 - [ ] 사용자 가이드 - [ ] 배포 가이드 --- **작성일**: 2025-10-20 **버전**: 1.0 **담당**: AI Assistant