From 3b3067d06734c28970a2ef0f558546884afb87b3 Mon Sep 17 00:00:00 2001 From: dohyeons Date: Mon, 20 Oct 2025 17:24:41 +0900 Subject: [PATCH] =?UTF-8?q?rest=20api=20=EA=B4=80=EB=A6=AC=20=EA=B3=84?= =?UTF-8?q?=ED=9A=8D=20md=ED=8C=8C=EC=9D=BC=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...XTERNAL_CONNECTION_REST_API_ENHANCEMENT.md | 759 ++++++++++++++++++ 1 file changed, 759 insertions(+) create mode 100644 PHASE_EXTERNAL_CONNECTION_REST_API_ENHANCEMENT.md diff --git a/PHASE_EXTERNAL_CONNECTION_REST_API_ENHANCEMENT.md b/PHASE_EXTERNAL_CONNECTION_REST_API_ENHANCEMENT.md new file mode 100644 index 00000000..42145a94 --- /dev/null +++ b/PHASE_EXTERNAL_CONNECTION_REST_API_ENHANCEMENT.md @@ -0,0 +1,759 @@ +# 외부 커넥션 관리 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