357 lines
8.8 KiB
Markdown
357 lines
8.8 KiB
Markdown
# 📋 Phase 3.12: ExternalCallConfigService Raw Query 전환 계획
|
|
|
|
## 📋 개요
|
|
|
|
ExternalCallConfigService는 **8개의 Prisma 호출**이 있으며, 외부 API 호출 설정 관리를 담당하는 서비스입니다.
|
|
|
|
### 📊 기본 정보
|
|
|
|
| 항목 | 내용 |
|
|
| --------------- | -------------------------------------------------------- |
|
|
| 파일 위치 | `backend-node/src/services/externalCallConfigService.ts` |
|
|
| 파일 크기 | 612 라인 |
|
|
| Prisma 호출 | 0개 (전환 완료) |
|
|
| **현재 진행률** | **8/8 (100%)** ✅ **전환 완료** |
|
|
| 복잡도 | 중간 (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");
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## ✅ 전환 완료 내역
|
|
|
|
### 전환된 Prisma 호출 (8개)
|
|
|
|
1. **`getConfigs()`** - 목록 조회 (findMany → query)
|
|
2. **`getConfigById()`** - 단건 조회 (findUnique → queryOne)
|
|
3. **`createConfig()`** - 중복 검사 (findFirst → queryOne)
|
|
4. **`createConfig()`** - 생성 (create → queryOne with INSERT)
|
|
5. **`updateConfig()`** - 중복 검사 (findFirst → queryOne)
|
|
6. **`updateConfig()`** - 수정 (update → queryOne with 동적 UPDATE)
|
|
7. **`deleteConfig()`** - 삭제 (update → query)
|
|
8. **`getExternalCallConfigsForButtonControl()`** - 조회 (findMany → query)
|
|
|
|
### 주요 기술적 개선사항
|
|
|
|
- 동적 WHERE 조건 생성 (company_code, call_type, api_type, is_active, search)
|
|
- ILIKE를 활용한 대소문자 구분 없는 검색
|
|
- 동적 UPDATE 쿼리 (9개 필드)
|
|
- JSON 필드 처리 (`config_data` → `JSON.stringify()`)
|
|
- 중복 검사 로직 유지
|
|
|
|
### 코드 정리
|
|
|
|
- [x] import 문 수정 완료
|
|
- [x] Prisma import 완전 제거
|
|
- [x] TypeScript 컴파일 성공
|
|
- [x] Linter 오류 없음
|
|
|
|
## 📝 원본 전환 체크리스트
|
|
|
|
### 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 호출 포함
|