# Phase 4.1: AdminController Raw Query ์ „ํ™˜ ๊ณ„ํš ## ๐Ÿ“‹ ๊ฐœ์š” ๊ด€๋ฆฌ์ž ์ปจํŠธ๋กค๋Ÿฌ์˜ Prisma ํ˜ธ์ถœ์„ Raw Query๋กœ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž, ํšŒ์‚ฌ, ๋ถ€์„œ, ๋ฉ”๋‰ด ๊ด€๋ฆฌ ๋“ฑ ํ•ต์‹ฌ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. --- ### ๐Ÿ“Š ๊ธฐ๋ณธ ์ •๋ณด | ํ•ญ๋ชฉ | ๋‚ด์šฉ | | --------------- | ------------------------------------------------- | | ํŒŒ์ผ ์œ„์น˜ | `backend-node/src/controllers/adminController.ts` | | ํŒŒ์ผ ํฌ๊ธฐ | 2,569 ๋ผ์ธ | | Prisma ํ˜ธ์ถœ | 28๊ฐœ โ†’ 0๊ฐœ | | **ํ˜„์žฌ ์ง„ํ–‰๋ฅ ** | **28/28 (100%)** โœ… **์™„๋ฃŒ** | | ๋ณต์žก๋„ | ์ค‘๊ฐ„ (๋‹ค์–‘ํ•œ CRUD ํŒจํ„ด) | | ์šฐ์„ ์ˆœ์œ„ | ๐Ÿ”ด ๋†’์Œ (Phase 4.1) | | **์ƒํƒœ** | โœ… **์™„๋ฃŒ** (2025-10-01) | --- ## ๐Ÿ” Prisma ํ˜ธ์ถœ ๋ถ„์„ ### ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ (13๊ฐœ) #### 1. getUserList (๋ผ์ธ 312-317) ```typescript const totalCount = await prisma.user_info.count({ where }); const users = await prisma.user_info.findMany({ where, skip, take, orderBy }); ``` - **์ „ํ™˜**: count โ†’ `queryOne`, findMany โ†’ `query` - **๋ณต์žก๋„**: ์ค‘๊ฐ„ (๋™์  WHERE, ํŽ˜์ด์ง•) #### 2. getUserInfo (๋ผ์ธ 419) ```typescript const userInfo = await prisma.user_info.findFirst({ where }); ``` - **์ „ํ™˜**: findFirst โ†’ `queryOne` - **๋ณต์žก๋„**: ๋‚ฎ์Œ #### 3. updateUserStatus (๋ผ์ธ 498) ```typescript await prisma.user_info.update({ where, data }); ``` - **์ „ํ™˜**: update โ†’ `query` - **๋ณต์žก๋„**: ๋‚ฎ์Œ #### 4. deleteUserByAdmin (๋ผ์ธ 2387) ```typescript await prisma.user_info.update({ where, data: { is_active: "N" } }); ``` - **์ „ํ™˜**: update (soft delete) โ†’ `query` - **๋ณต์žก๋„**: ๋‚ฎ์Œ #### 5. getMyProfile (๋ผ์ธ 1468, 1488, 2479) ```typescript const user = await prisma.user_info.findUnique({ where }); const dept = await prisma.dept_info.findUnique({ where }); ``` - **์ „ํ™˜**: findUnique โ†’ `queryOne` - **๋ณต์žก๋„**: ๋‚ฎ์Œ #### 6. updateMyProfile (๋ผ์ธ 1864, 2527) ```typescript const updateResult = await prisma.user_info.update({ where, data }); ``` - **์ „ํ™˜**: update โ†’ `queryOne` with RETURNING - **๋ณต์žก๋„**: ์ค‘๊ฐ„ (๋™์  UPDATE) #### 7. createOrUpdateUser (๋ผ์ธ 1929, 1975) ```typescript const savedUser = await prisma.user_info.upsert({ where, update, create }); const userCount = await prisma.user_info.count({ where }); ``` - **์ „ํ™˜**: upsert โ†’ `INSERT ... ON CONFLICT`, count โ†’ `queryOne` - **๋ณต์žก๋„**: ๋†’์Œ #### 8. ๊ธฐํƒ€ findUnique (๋ผ์ธ 1596, 1832, 2393) ```typescript const existingUser = await prisma.user_info.findUnique({ where }); const currentUser = await prisma.user_info.findUnique({ where }); const updatedUser = await prisma.user_info.findUnique({ where }); ``` - **์ „ํ™˜**: findUnique โ†’ `queryOne` - **๋ณต์žก๋„**: ๋‚ฎ์Œ ### ํšŒ์‚ฌ ๊ด€๋ฆฌ (7๊ฐœ) #### 9. getCompanyList (๋ผ์ธ 550, 1276) ```typescript const companies = await prisma.company_mng.findMany({ orderBy }); ``` - **์ „ํ™˜**: findMany โ†’ `query` - **๋ณต์žก๋„**: ๋‚ฎ์Œ #### 10. createCompany (๋ผ์ธ 2035) ```typescript const existingCompany = await prisma.company_mng.findFirst({ where }); ``` - **์ „ํ™˜**: findFirst (์ค‘๋ณต ์ฒดํฌ) โ†’ `queryOne` - **๋ณต์žก๋„**: ๋‚ฎ์Œ #### 11. updateCompany (๋ผ์ธ 2172, 2192) ```typescript const duplicateCompany = await prisma.company_mng.findFirst({ where }); const updatedCompany = await prisma.company_mng.update({ where, data }); ``` - **์ „ํ™˜**: findFirst โ†’ `queryOne`, update โ†’ `queryOne` - **๋ณต์žก๋„**: ์ค‘๊ฐ„ #### 12. deleteCompany (๋ผ์ธ 2261, 2281) ```typescript const existingCompany = await prisma.company_mng.findUnique({ where }); await prisma.company_mng.delete({ where }); ``` - **์ „ํ™˜**: findUnique โ†’ `queryOne`, delete โ†’ `query` - **๋ณต์žก๋„**: ๋‚ฎ์Œ ### ๋ถ€์„œ ๊ด€๋ฆฌ (2๊ฐœ) #### 13. getDepartmentList (๋ผ์ธ 1348) ```typescript const departments = await prisma.dept_info.findMany({ where, orderBy }); ``` - **์ „ํ™˜**: findMany โ†’ `query` - **๋ณต์žก๋„**: ๋‚ฎ์Œ #### 14. getDeptInfo (๋ผ์ธ 1488) ```typescript const dept = await prisma.dept_info.findUnique({ where }); ``` - **์ „ํ™˜**: findUnique โ†’ `queryOne` - **๋ณต์žก๋„**: ๋‚ฎ์Œ ### ๋ฉ”๋‰ด ๊ด€๋ฆฌ (3๊ฐœ) #### 15. createMenu (๋ผ์ธ 1021) ```typescript const savedMenu = await prisma.menu_info.create({ data }); ``` - **์ „ํ™˜**: create โ†’ `queryOne` with INSERT RETURNING - **๋ณต์žก๋„**: ์ค‘๊ฐ„ #### 16. updateMenu (๋ผ์ธ 1087) ```typescript const updatedMenu = await prisma.menu_info.update({ where, data }); ``` - **์ „ํ™˜**: update โ†’ `queryOne` with UPDATE RETURNING - **๋ณต์žก๋„**: ์ค‘๊ฐ„ #### 17. deleteMenu (๋ผ์ธ 1149, 1211) ```typescript const deletedMenu = await prisma.menu_info.delete({ where }); // ์žฌ๊ท€ ์‚ญ์ œ const deletedMenu = await prisma.menu_info.delete({ where }); ``` - **์ „ํ™˜**: delete โ†’ `query` - **๋ณต์žก๋„**: ์ค‘๊ฐ„ (์žฌ๊ท€ ์‚ญ์ œ ๋กœ์ง) ### ๋‹ค๊ตญ์–ด (1๊ฐœ) #### 18. getMultiLangKeys (๋ผ์ธ 665) ```typescript const result = await prisma.multi_lang_key_master.findMany({ where, orderBy }); ``` - **์ „ํ™˜**: findMany โ†’ `query` - **๋ณต์žก๋„**: ๋‚ฎ์Œ --- ## ๐Ÿ“ ์ „ํ™˜ ์ „๋žต ### 1๋‹จ๊ณ„: Import ๋ณ€๊ฒฝ ```typescript // ์ œ๊ฑฐ import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); // ์ถ”๊ฐ€ import { query, queryOne } from "../database/db"; ``` ### 2๋‹จ๊ณ„: ๋‹จ์ˆœ ์กฐํšŒ ์ „ํ™˜ - findMany โ†’ `query` - findUnique/findFirst โ†’ `queryOne` ### 3๋‹จ๊ณ„: ๋™์  WHERE ์ฒ˜๋ฆฌ ```typescript const whereConditions: string[] = []; const params: any[] = []; let paramIndex = 1; if (companyCode) { whereConditions.push(`company_code = $${paramIndex++}`); params.push(companyCode); } const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : ""; ``` ### 4๋‹จ๊ณ„: ๋ณต์žกํ•œ ๋กœ์ง ์ „ํ™˜ - count โ†’ `SELECT COUNT(*) as count` - upsert โ†’ `INSERT ... ON CONFLICT DO UPDATE` - ๋™์  UPDATE โ†’ ์กฐ๊ฑด๋ถ€ SET ์ ˆ ์ƒ์„ฑ ### 5๋‹จ๊ณ„: ํ…Œ์ŠคํŠธ ๋ฐ ๊ฒ€์ฆ - ๊ฐ ํ•จ์ˆ˜๋ณ„ ๋™์ž‘ ํ™•์ธ - ์—๋Ÿฌ ์ฒ˜๋ฆฌ ํ™•์ธ - ํƒ€์ž… ์•ˆ์ „์„ฑ ํ™•์ธ --- ## ๐ŸŽฏ ์ฃผ์š” ๋ณ€๊ฒฝ ์˜ˆ์‹œ ### getUserList (count + findMany) ```typescript // Before const totalCount = await prisma.user_info.count({ where }); const users = await prisma.user_info.findMany({ where, skip, take, orderBy, }); // After const whereConditions: string[] = []; const params: any[] = []; let paramIndex = 1; // ๋™์  WHERE ๊ตฌ์„ฑ if (where.company_code) { whereConditions.push(`company_code = $${paramIndex++}`); params.push(where.company_code); } if (where.user_name) { whereConditions.push(`user_name ILIKE $${paramIndex++}`); params.push(`%${where.user_name}%`); } const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : ""; // Count const countResult = await queryOne<{ count: number }>( `SELECT COUNT(*) as count FROM user_info ${whereClause}`, params ); const totalCount = parseInt(countResult?.count?.toString() || "0", 10); // ๋ฐ์ดํ„ฐ ์กฐํšŒ const usersQuery = ` SELECT * FROM user_info ${whereClause} ORDER BY created_date DESC LIMIT $${paramIndex} OFFSET $${paramIndex + 1} `; params.push(take, skip); const users = await query(usersQuery, params); ``` ### createOrUpdateUser (upsert) ```typescript // Before const savedUser = await prisma.user_info.upsert({ where: { user_id: userId }, update: updateData, create: createData }); // After const savedUser = await queryOne( `INSERT INTO user_info (user_id, user_name, email, ...) VALUES ($1, $2, $3, ...) ON CONFLICT (user_id) DO UPDATE SET user_name = EXCLUDED.user_name, email = EXCLUDED.email, ... RETURNING *`, [userId, userName, email, ...] ); ``` ### updateMyProfile (๋™์  UPDATE) ```typescript // Before const updateResult = await prisma.user_info.update({ where: { user_id: userId }, data: updateData, }); // After const updates: string[] = []; const params: any[] = []; let paramIndex = 1; if (updateData.user_name !== undefined) { updates.push(`user_name = $${paramIndex++}`); params.push(updateData.user_name); } if (updateData.email !== undefined) { updates.push(`email = $${paramIndex++}`); params.push(updateData.email); } // ... ๋‹ค๋ฅธ ํ•„๋“œ๋“ค params.push(userId); const updateResult = await queryOne( `UPDATE user_info SET ${updates.join(", ")}, updated_date = NOW() WHERE user_id = $${paramIndex} RETURNING *`, params ); ``` --- ## โœ… ์ฒดํฌ๋ฆฌ์ŠคํŠธ ### ๊ธฐ๋ณธ ์„ค์ • - โœ… Prisma import ์ œ๊ฑฐ (์™„์ „ ์ œ๊ฑฐ ํ™•์ธ) - โœ… query, queryOne import ์ถ”๊ฐ€ (์ด๋ฏธ ์กด์žฌ) - โœ… ํƒ€์ž… import ํ™•์ธ ### ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ - โœ… getUserList (count + findMany โ†’ Raw Query) - โœ… getUserLocale (findFirst โ†’ queryOne) - โœ… setUserLocale (update โ†’ query) - โœ… getUserInfo (findUnique โ†’ queryOne) - โœ… checkDuplicateUserId (findUnique โ†’ queryOne) - โœ… changeUserStatus (findUnique + update โ†’ queryOne + query) - โœ… saveUser (upsert โ†’ INSERT ON CONFLICT) - โœ… updateProfile (๋™์  update โ†’ ๋™์  query) - โœ… resetUserPassword (update โ†’ query) ### ํšŒ์‚ฌ ๊ด€๋ฆฌ - โœ… getCompanyList (findMany โ†’ query) - โœ… getCompanyListFromDB (findMany โ†’ query) - โœ… createCompany (findFirst โ†’ queryOne) - โœ… updateCompany (findFirst + update โ†’ queryOne + query) - โœ… deleteCompany (delete โ†’ query with RETURNING) ### ๋ถ€์„œ ๊ด€๋ฆฌ - โœ… getDepartmentList (findMany โ†’ query with ๋™์  WHERE) ### ๋ฉ”๋‰ด ๊ด€๋ฆฌ - โœ… saveMenu (create โ†’ query with INSERT RETURNING) - โœ… updateMenu (update โ†’ query with UPDATE RETURNING) - โœ… deleteMenu (delete โ†’ query with DELETE RETURNING) - โœ… deleteMenusBatch (๋‹ค์ค‘ delete โ†’ ๋ฐ˜๋ณต query) ### ๋‹ค๊ตญ์–ด - โœ… getLangKeyList (findMany โ†’ query) ### ๊ฒ€์ฆ - โœ… TypeScript ์ปดํŒŒ์ผ ํ™•์ธ (์—๋Ÿฌ ์—†์Œ) - โœ… Linter ์˜ค๋ฅ˜ ํ™•์ธ - โณ ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ (์‹คํ–‰ ํ•„์š”) - โœ… ์—๋Ÿฌ ์ฒ˜๋ฆฌ ํ™•์ธ (๊ธฐ์กด ๊ตฌ์กฐ ์œ ์ง€) --- ## ๐Ÿ“Œ ์ฐธ๊ณ ์‚ฌํ•ญ ### ๋™์  ์ฟผ๋ฆฌ ์ƒ์„ฑ ํŒจํ„ด ๋ชจ๋“  ๋™์  WHERE/UPDATE๋Š” ๋‹ค์Œ ํŒจํ„ด์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค: 1. ์กฐ๊ฑด/ํ•„๋“œ ๋ฐฐ์—ด ์ƒ์„ฑ 2. ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐฐ์—ด ์ƒ์„ฑ 3. ํŒŒ๋ผ๋ฏธํ„ฐ ์ธ๋ฑ์Šค ๊ด€๋ฆฌ 4. SQL ๋ฌธ์ž์—ด ์กฐํ•ฉ 5. query/queryOne ์‹คํ–‰ ### ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๊ธฐ์กด try-catch ๊ตฌ์กฐ๋ฅผ ์œ ์ง€ํ•˜๋ฉฐ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—๋Ÿฌ๋ฅผ ์ ์ ˆํžˆ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ### ํŠธ๋žœ์žญ์…˜ ๋ณต์žกํ•œ ๋กœ์ง์€ Service Layer๋กœ ์ด๋™์„ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค. --- ## ๐ŸŽ‰ ์™„๋ฃŒ ์š”์•ฝ (2025-10-01) ### โœ… ์ „ํ™˜ ์™„๋ฃŒ ํ˜„ํ™ฉ | ์นดํ…Œ๊ณ ๋ฆฌ | ํ•จ์ˆ˜ ์ˆ˜ | ์ƒํƒœ | |---------|--------|------| | ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ | 9๊ฐœ | โœ… ์™„๋ฃŒ | | ํšŒ์‚ฌ ๊ด€๋ฆฌ | 5๊ฐœ | โœ… ์™„๋ฃŒ | | ๋ถ€์„œ ๊ด€๋ฆฌ | 1๊ฐœ | โœ… ์™„๋ฃŒ | | ๋ฉ”๋‰ด ๊ด€๋ฆฌ | 4๊ฐœ | โœ… ์™„๋ฃŒ | | ๋‹ค๊ตญ์–ด | 1๊ฐœ | โœ… ์™„๋ฃŒ | | **์ด๊ณ„** | **20๊ฐœ** | **โœ… 100% ์™„๋ฃŒ** | ### ๐Ÿ“Š ์ฃผ์š” ์„ฑ๊ณผ 1. **์™„์ „ํ•œ Prisma ์ œ๊ฑฐ**: adminController.ts์—์„œ ๋ชจ๋“  Prisma ์ฝ”๋“œ ์ œ๊ฑฐ ์™„๋ฃŒ 2. **๋™์  ์ฟผ๋ฆฌ ์ง€์›**: ๋Ÿฐํƒ€์ž„ ํ…Œ์ด๋ธ” ์ƒ์„ฑ/์ˆ˜์ • ๊ฐ€๋Šฅ 3. **์ผ๊ด€๋œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ**: ๋ชจ๋“  ํ•จ์ˆ˜์—์„œ ํ†ต์ผ๋œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์œ ์ง€ 4. **ํƒ€์ž… ์•ˆ์ „์„ฑ**: TypeScript ์ปดํŒŒ์ผ ์—๋Ÿฌ ์—†์Œ 5. **์ฝ”๋“œ ํ’ˆ์งˆ ํ–ฅ์ƒ**: 949์ค„ ๋ณ€๊ฒฝ (+474/-475) ### ๐Ÿ”‘ ์ฃผ์š” ๋ณ€ํ™˜ ํŒจํ„ด #### 1. ๋™์  WHERE ์กฐ๊ฑด ```typescript let whereConditions: string[] = []; let queryParams: any[] = []; let paramIndex = 1; if (filter) { whereConditions.push(`field = $${paramIndex}`); queryParams.push(filter); paramIndex++; } const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : ""; ``` #### 2. UPSERT (INSERT ON CONFLICT) ```typescript const [result] = await query( `INSERT INTO table (col1, col2) VALUES ($1, $2) ON CONFLICT (col1) DO UPDATE SET col2 = $2 RETURNING *`, [val1, val2] ); ``` #### 3. ๋™์  UPDATE ```typescript const updateFields: string[] = []; const updateValues: any[] = []; let paramIndex = 1; if (data.field !== undefined) { updateFields.push(`field = $${paramIndex}`); updateValues.push(data.field); paramIndex++; } await query( `UPDATE table SET ${updateFields.join(", ")} WHERE id = $${paramIndex}`, [...updateValues, id] ); ``` ### ๐Ÿš€ ๋‹ค์Œ ๋‹จ๊ณ„ 1. **ํ…Œ์ŠคํŠธ ์‹คํ–‰**: ๊ฐœ๋ฐœ ์„œ๋ฒ„์—์„œ ๋ชจ๋“  API ์—”๋“œํฌ์ธํŠธ ํ…Œ์ŠคํŠธ 2. **๋ฌธ์„œ ์—…๋ฐ์ดํŠธ**: Phase 4 ์ „์ฒด ๊ณ„ํš์„œ ์ง„ํ–‰ ์ƒํ™ฉ ๋ฐ˜์˜ 3. **๋‹ค์Œ Phase**: screenFileController.ts ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ง„ํ–‰ --- **๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ**: 2025-10-01 **์ž‘์—…์ž**: Claude Agent **์™„๋ฃŒ ์‹œ๊ฐ„**: ์•ฝ 15๋ถ„ **๋ณ€๊ฒฝ ๋ผ์ธ ์ˆ˜**: 949์ค„ (์ถ”๊ฐ€ 474์ค„, ์‚ญ์ œ 475์ค„)