12 KiB
12 KiB
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)
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)
const userInfo = await prisma.user_info.findFirst({ where });
- 전환: findFirst →
queryOne - 복잡도: 낮음
3. updateUserStatus (라인 498)
await prisma.user_info.update({ where, data });
- 전환: update →
query - 복잡도: 낮음
4. deleteUserByAdmin (라인 2387)
await prisma.user_info.update({ where, data: { is_active: "N" } });
- 전환: update (soft delete) →
query - 복잡도: 낮음
5. getMyProfile (라인 1468, 1488, 2479)
const user = await prisma.user_info.findUnique({ where });
const dept = await prisma.dept_info.findUnique({ where });
- 전환: findUnique →
queryOne - 복잡도: 낮음
6. updateMyProfile (라인 1864, 2527)
const updateResult = await prisma.user_info.update({ where, data });
- 전환: update →
queryOnewith RETURNING - 복잡도: 중간 (동적 UPDATE)
7. createOrUpdateUser (라인 1929, 1975)
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)
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)
const companies = await prisma.company_mng.findMany({ orderBy });
- 전환: findMany →
query - 복잡도: 낮음
10. createCompany (라인 2035)
const existingCompany = await prisma.company_mng.findFirst({ where });
- 전환: findFirst (중복 체크) →
queryOne - 복잡도: 낮음
11. updateCompany (라인 2172, 2192)
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)
const existingCompany = await prisma.company_mng.findUnique({ where });
await prisma.company_mng.delete({ where });
- 전환: findUnique →
queryOne, delete →query - 복잡도: 낮음
부서 관리 (2개)
13. getDepartmentList (라인 1348)
const departments = await prisma.dept_info.findMany({ where, orderBy });
- 전환: findMany →
query - 복잡도: 낮음
14. getDeptInfo (라인 1488)
const dept = await prisma.dept_info.findUnique({ where });
- 전환: findUnique →
queryOne - 복잡도: 낮음
메뉴 관리 (3개)
15. createMenu (라인 1021)
const savedMenu = await prisma.menu_info.create({ data });
- 전환: create →
queryOnewith INSERT RETURNING - 복잡도: 중간
16. updateMenu (라인 1087)
const updatedMenu = await prisma.menu_info.update({ where, data });
- 전환: update →
queryOnewith UPDATE RETURNING - 복잡도: 중간
17. deleteMenu (라인 1149, 1211)
const deletedMenu = await prisma.menu_info.delete({ where });
// 재귀 삭제
const deletedMenu = await prisma.menu_info.delete({ where });
- 전환: delete →
query - 복잡도: 중간 (재귀 삭제 로직)
다국어 (1개)
18. getMultiLangKeys (라인 665)
const result = await prisma.multi_lang_key_master.findMany({ where, orderBy });
- 전환: findMany →
query - 복잡도: 낮음
📝 전환 전략
1단계: Import 변경
// 제거
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
// 추가
import { query, queryOne } from "../database/db";
2단계: 단순 조회 전환
- findMany →
query<T> - findUnique/findFirst →
queryOne<T>
3단계: 동적 WHERE 처리
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)
// 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<UserInfo>(usersQuery, params);
createOrUpdateUser (upsert)
// Before
const savedUser = await prisma.user_info.upsert({
where: { user_id: userId },
update: updateData,
create: createData
});
// After
const savedUser = await queryOne<UserInfo>(
`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)
// 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<UserInfo>(
`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는 다음 패턴을 따릅니다:
- 조건/필드 배열 생성
- 파라미터 배열 생성
- 파라미터 인덱스 관리
- SQL 문자열 조합
- query/queryOne 실행
에러 처리
기존 try-catch 구조를 유지하며, 데이터베이스 에러를 적절히 변환합니다.
트랜잭션
복잡한 로직은 Service Layer로 이동을 고려합니다.
🎉 완료 요약 (2025-10-01)
✅ 전환 완료 현황
| 카테고리 | 함수 수 | 상태 |
|---|---|---|
| 사용자 관리 | 9개 | ✅ 완료 |
| 회사 관리 | 5개 | ✅ 완료 |
| 부서 관리 | 1개 | ✅ 완료 |
| 메뉴 관리 | 4개 | ✅ 완료 |
| 다국어 | 1개 | ✅ 완료 |
| 총계 | 20개 | ✅ 100% 완료 |
📊 주요 성과
- 완전한 Prisma 제거: adminController.ts에서 모든 Prisma 코드 제거 완료
- 동적 쿼리 지원: 런타임 테이블 생성/수정 가능
- 일관된 에러 처리: 모든 함수에서 통일된 에러 처리 유지
- 타입 안전성: TypeScript 컴파일 에러 없음
- 코드 품질 향상: 949줄 변경 (+474/-475)
🔑 주요 변환 패턴
1. 동적 WHERE 조건
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)
const [result] = await query<any>(
`INSERT INTO table (col1, col2) VALUES ($1, $2)
ON CONFLICT (col1) DO UPDATE SET col2 = $2
RETURNING *`,
[val1, val2]
);
3. 동적 UPDATE
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]
);
🚀 다음 단계
- 테스트 실행: 개발 서버에서 모든 API 엔드포인트 테스트
- 문서 업데이트: Phase 4 전체 계획서 진행 상황 반영
- 다음 Phase: screenFileController.ts 마이그레이션 진행
마지막 업데이트: 2025-10-01 작업자: Claude Agent 완료 시간: 약 15분 변경 라인 수: 949줄 (추가 474줄, 삭제 475줄)