ERP-node/PHASE3.12_EXTERNAL_CALL_CON...

8.8 KiB

📋 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.tsquery(), 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)

변경 전:

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,
});

변경 후:

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 필드 생성

변경 전:

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,
  },
});

변경 후:

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 포함)

변경 전:

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,
});

변경 후:

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 - 인증 설정 (암호화됨)
// INSERT/UPDATE 시
JSON.stringify(jsonData);

// SELECT 후
const parsedData =
  typeof row.headers === "string" ? JSON.parse(row.headers) : row.headers;

2. 민감 정보 암호화

auth_config는 암호화되어 저장되므로, 기존 암호화/복호화 로직 유지:

import { encrypt, decrypt } from "../utils/encryption";

// 저장 시
const encryptedAuthConfig = encrypt(JSON.stringify(authConfig));

// 조회 시
const decryptedAuthConfig = JSON.parse(decrypt(row.auth_config));

3. HTTP 메소드 검증

const VALID_HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
if (!VALID_HTTP_METHODS.includes(httpMethod)) {
  throw new Error("Invalid HTTP method");
}

4. URL 검증

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_dataJSON.stringify())
  • 중복 검사 로직 유지

코드 정리

  • import 문 수정 완료
  • Prisma import 완전 제거
  • TypeScript 컴파일 성공
  • Linter 오류 없음

📝 원본 전환 체크리스트

1단계: Prisma 호출 전환 ( 완료)

  • getExternalCallConfigs() - 목록 조회 (findMany + count)
  • getExternalCallConfig() - 단건 조회 (findUnique)
  • createExternalCallConfig() - 생성 (create)
  • updateExternalCallConfig() - 수정 (update)
  • deleteExternalCallConfig() - 삭제 (delete)
  • duplicateExternalCallConfig() - 복제 (findUnique + create)
  • testExternalCallConfig() - 테스트 (findUnique)
  • getExternalCallHistory() - 이력 조회 (findMany)

2단계: 코드 정리

  • import 문 수정 (prismaquery, queryOne)
  • JSON 필드 처리 확인
  • 암호화/복호화 로직 유지
  • Prisma import 완전 제거

3단계: 테스트

  • 단위 테스트 작성 (8개)
  • 통합 테스트 작성 (3개)
  • 암호화 테스트
  • HTTP 호출 테스트

4단계: 문서화

  • 전환 완료 문서 업데이트
  • API 문서 업데이트

🎯 예상 난이도 및 소요 시간

  • 난이도: (중간)
    • JSON 필드 처리
    • 암호화/복호화 로직
    • HTTP 호출 테스트
  • 예상 소요 시간: 1~1.5시간

상태: 대기 중
특이사항: JSON 필드, 민감 정보 암호화, HTTP 호출 포함