# ๐Ÿ“‹ 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( `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( `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( `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 ํ˜ธ์ถœ ํฌํ•จ