rest api 관리 계획 md파일 생성
This commit is contained in:
parent
72045df25a
commit
3b3067d067
|
|
@ -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<string, string>;
|
||||
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<string, string>;
|
||||
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<ExternalRestApiConnection>
|
||||
);
|
||||
static async deleteConnection(id: number);
|
||||
|
||||
// 테스트 메서드
|
||||
static async testConnection(
|
||||
testRequest: RestApiTestRequest
|
||||
): Promise<RestApiTestResult>;
|
||||
static async testConnectionById(
|
||||
id: number,
|
||||
endpoint?: string
|
||||
): Promise<RestApiTestResult>;
|
||||
|
||||
// 헬퍼 메서드
|
||||
private static buildHeaders(
|
||||
connection: ExternalRestApiConnection
|
||||
): Record<string, string>;
|
||||
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<RestApiTestResult> {
|
||||
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<ConnectionTabType>("database");
|
||||
```
|
||||
|
||||
### 2. 메인 페이지 구조 개선
|
||||
|
||||
```tsx
|
||||
// 탭 헤더
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(value) => setActiveTab(value as ConnectionTabType)}
|
||||
>
|
||||
<TabsList className="grid w-[400px] grid-cols-2">
|
||||
<TabsTrigger value="database" className="flex items-center gap-2">
|
||||
<Database className="h-4 w-4" />
|
||||
데이터베이스 연결
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="rest-api" className="flex items-center gap-2">
|
||||
<Globe className="h-4 w-4" />
|
||||
REST API 연결
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* 데이터베이스 연결 탭 */}
|
||||
<TabsContent value="database">
|
||||
<DatabaseConnectionList />
|
||||
</TabsContent>
|
||||
|
||||
{/* REST API 연결 탭 */}
|
||||
<TabsContent value="rest-api">
|
||||
<RestApiConnectionList />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
### 3. REST API 연결 목록 컴포넌트
|
||||
|
||||
```typescript
|
||||
// frontend/components/admin/RestApiConnectionList.tsx
|
||||
|
||||
export function RestApiConnectionList() {
|
||||
const [connections, setConnections] = useState<ExternalRestApiConnection[]>(
|
||||
[]
|
||||
);
|
||||
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<string, string>;
|
||||
onChange: (headers: Record<string, string>) => 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<string, string>);
|
||||
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<ExternalRestApiConnection>
|
||||
) {
|
||||
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<RestApiTestResult> {
|
||||
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<RestApiTestResult> {
|
||||
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
|
||||
Loading…
Reference in New Issue