ERP-node/PHASE3.12_EXTERNAL_CALL_CON...

306 lines
7.8 KiB
Markdown
Raw Normal View History

# 📋 Phase 3.12: ExternalCallConfigService Raw Query 전환 계획
## 📋 개요
ExternalCallConfigService는 **8개의 Prisma 호출**이 있으며, 외부 API 호출 설정 관리를 담당하는 서비스입니다.
### 📊 기본 정보
| 항목 | 내용 |
| --------------- | --------------------------------------------------------- |
| 파일 위치 | `backend-node/src/services/externalCallConfigService.ts` |
| 파일 크기 | 581 라인 |
| Prisma 호출 | 8개 |
| **현재 진행률** | **0/8 (0%)** 🔄 **진행 예정** |
| 복잡도 | 중간 (JSON 필드, 복잡한 CRUD) |
| 우선순위 | 🟡 중간 (Phase 3.12) |
| **상태** | ⏳ **대기 중** |
### 🎯 전환 목표
-**8개 모든 Prisma 호출을 `db.ts`의 `query()`, `queryOne()` 함수로 교체**
- ⏳ 외부 호출 설정 CRUD 기능 정상 동작
- ⏳ JSON 필드 처리 (headers, params, auth_config)
- ⏳ 동적 WHERE 조건 생성
- ⏳ 민감 정보 암호화/복호화 유지
- ⏳ TypeScript 컴파일 성공
-**Prisma import 완전 제거**
---
## 🔍 예상 Prisma 사용 패턴
### 주요 기능 (8개 예상)
#### 1. **외부 호출 설정 목록 조회**
- findMany with filters
- 페이징, 정렬
- 동적 WHERE 조건 (is_active, company_code, search)
#### 2. **외부 호출 설정 단건 조회**
- findUnique or findFirst
- config_id 기준
#### 3. **외부 호출 설정 생성**
- create
- JSON 필드 처리 (headers, params, auth_config)
- 민감 정보 암호화
#### 4. **외부 호출 설정 수정**
- update
- 동적 UPDATE 쿼리
- JSON 필드 업데이트
#### 5. **외부 호출 설정 삭제**
- delete or soft delete
#### 6. **외부 호출 설정 복제**
- findUnique + create
#### 7. **외부 호출 설정 테스트**
- findUnique
- 실제 HTTP 호출
#### 8. **외부 호출 이력 조회**
- findMany with 관계 조인
- 통계 쿼리
---
## 💡 전환 전략
### 1단계: 기본 CRUD 전환 (5개)
- getExternalCallConfigs() - 목록 조회
- getExternalCallConfig() - 단건 조회
- createExternalCallConfig() - 생성
- updateExternalCallConfig() - 수정
- deleteExternalCallConfig() - 삭제
### 2단계: 추가 기능 전환 (3개)
- duplicateExternalCallConfig() - 복제
- testExternalCallConfig() - 테스트
- getExternalCallHistory() - 이력 조회
---
## 💻 전환 예시
### 예시 1: 목록 조회 (동적 WHERE + JSON)
**변경 전**:
```typescript
const configs = await prisma.external_call_configs.findMany({
where: {
company_code: companyCode,
is_active: isActive,
OR: [
{ config_name: { contains: search, mode: "insensitive" } },
{ endpoint_url: { contains: search, mode: "insensitive" } },
],
},
orderBy: { created_at: "desc" },
skip,
take: limit,
});
```
**변경 후**:
```typescript
const conditions: string[] = ["company_code = $1"];
const params: any[] = [companyCode];
let paramIndex = 2;
if (isActive !== undefined) {
conditions.push(`is_active = $${paramIndex++}`);
params.push(isActive);
}
if (search) {
conditions.push(
`(config_name ILIKE $${paramIndex} OR endpoint_url ILIKE $${paramIndex})`
);
params.push(`%${search}%`);
paramIndex++;
}
const configs = await query<any>(
`SELECT * FROM external_call_configs
WHERE ${conditions.join(" AND ")}
ORDER BY created_at DESC
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
[...params, limit, skip]
);
```
### 예시 2: JSON 필드 생성
**변경 전**:
```typescript
const config = await prisma.external_call_configs.create({
data: {
config_name: data.config_name,
endpoint_url: data.endpoint_url,
http_method: data.http_method,
headers: data.headers, // JSON
params: data.params, // JSON
auth_config: encryptedAuthConfig, // JSON (암호화됨)
company_code: companyCode,
},
});
```
**변경 후**:
```typescript
const config = await queryOne<any>(
`INSERT INTO external_call_configs
(config_name, endpoint_url, http_method, headers, params,
auth_config, company_code, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())
RETURNING *`,
[
data.config_name,
data.endpoint_url,
data.http_method,
JSON.stringify(data.headers),
JSON.stringify(data.params),
JSON.stringify(encryptedAuthConfig),
companyCode,
]
);
```
### 예시 3: 동적 UPDATE (JSON 포함)
**변경 전**:
```typescript
const updateData: any = {};
if (data.headers) updateData.headers = data.headers;
if (data.params) updateData.params = data.params;
const config = await prisma.external_call_configs.update({
where: { config_id: configId },
data: updateData,
});
```
**변경 후**:
```typescript
const updateFields: string[] = ["updated_at = NOW()"];
const values: any[] = [];
let paramIndex = 1;
if (data.headers !== undefined) {
updateFields.push(`headers = $${paramIndex++}`);
values.push(JSON.stringify(data.headers));
}
if (data.params !== undefined) {
updateFields.push(`params = $${paramIndex++}`);
values.push(JSON.stringify(data.params));
}
const config = await queryOne<any>(
`UPDATE external_call_configs
SET ${updateFields.join(", ")}
WHERE config_id = $${paramIndex}
RETURNING *`,
[...values, configId]
);
```
---
## 🔧 기술적 고려사항
### 1. JSON 필드 처리
3개의 JSON 필드가 있을 것으로 예상:
- `headers` - HTTP 헤더
- `params` - 쿼리 파라미터
- `auth_config` - 인증 설정 (암호화됨)
```typescript
// INSERT/UPDATE 시
JSON.stringify(jsonData)
// SELECT 후
const parsedData = typeof row.headers === 'string'
? JSON.parse(row.headers)
: row.headers;
```
### 2. 민감 정보 암호화
auth_config는 암호화되어 저장되므로, 기존 암호화/복호화 로직 유지:
```typescript
import { encrypt, decrypt } from "../utils/encryption";
// 저장 시
const encryptedAuthConfig = encrypt(JSON.stringify(authConfig));
// 조회 시
const decryptedAuthConfig = JSON.parse(decrypt(row.auth_config));
```
### 3. HTTP 메소드 검증
```typescript
const VALID_HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
if (!VALID_HTTP_METHODS.includes(httpMethod)) {
throw new Error("Invalid HTTP method");
}
```
### 4. URL 검증
```typescript
try {
new URL(endpointUrl);
} catch {
throw new Error("Invalid endpoint URL");
}
```
---
## 📝 전환 체크리스트
### 1단계: Prisma 호출 전환
- [ ] `getExternalCallConfigs()` - 목록 조회 (findMany + count)
- [ ] `getExternalCallConfig()` - 단건 조회 (findUnique)
- [ ] `createExternalCallConfig()` - 생성 (create)
- [ ] `updateExternalCallConfig()` - 수정 (update)
- [ ] `deleteExternalCallConfig()` - 삭제 (delete)
- [ ] `duplicateExternalCallConfig()` - 복제 (findUnique + create)
- [ ] `testExternalCallConfig()` - 테스트 (findUnique)
- [ ] `getExternalCallHistory()` - 이력 조회 (findMany)
### 2단계: 코드 정리
- [ ] import 문 수정 (`prisma` → `query, queryOne`)
- [ ] JSON 필드 처리 확인
- [ ] 암호화/복호화 로직 유지
- [ ] Prisma import 완전 제거
### 3단계: 테스트
- [ ] 단위 테스트 작성 (8개)
- [ ] 통합 테스트 작성 (3개)
- [ ] 암호화 테스트
- [ ] HTTP 호출 테스트
### 4단계: 문서화
- [ ] 전환 완료 문서 업데이트
- [ ] API 문서 업데이트
---
## 🎯 예상 난이도 및 소요 시간
- **난이도**: ⭐⭐⭐ (중간)
- JSON 필드 처리
- 암호화/복호화 로직
- HTTP 호출 테스트
- **예상 소요 시간**: 1~1.5시간
---
**상태**: ⏳ **대기 중**
**특이사항**: JSON 필드, 민감 정보 암호화, HTTP 호출 포함