diff --git a/backend-node/src/controllers/adminController.ts b/backend-node/src/controllers/adminController.ts index 0ec055ba..4ca152ec 100644 --- a/backend-node/src/controllers/adminController.ts +++ b/backend-node/src/controllers/adminController.ts @@ -180,9 +180,7 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => { const { page = 1, countPerPage = 20, - // 통합 검색 (전체 필드 대상) search, - // 고급 검색 (개별 필드별) searchField, searchValue, search_sabun, @@ -193,286 +191,199 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => { search_userName, search_tel, search_email, - // 기존 필터 deptCode, status, } = req.query; - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: config.databaseUrl, - }); + // Prisma ORM을 사용한 사용자 목록 조회 + let whereConditions: any = {}; + let searchType = "none"; - await client.connect(); + // 검색 조건 처리 + if (search && typeof search === "string" && search.trim()) { + // 통합 검색 + searchType = "unified"; + const searchTerm = search.trim(); - try { - // 기본 사용자 목록 조회 쿼리 - let query = ` - SELECT - u.sabun, - u.user_id, - u.user_name, - u.user_name_eng, - u.dept_code, - u.dept_name, - u.position_code, - u.position_name, - u.email, - u.tel, - u.cell_phone, - u.user_type, - u.user_type_name, - u.regdate, - u.status, - u.company_code, - u.locale, - d.dept_name as dept_name_full - FROM user_info u - LEFT JOIN dept_info d ON u.dept_code = d.dept_code - WHERE 1=1 - `; + whereConditions.OR = [ + { sabun: { contains: searchTerm, mode: "insensitive" } }, + { user_type_name: { contains: searchTerm, mode: "insensitive" } }, + { dept_name: { contains: searchTerm, mode: "insensitive" } }, + { position_name: { contains: searchTerm, mode: "insensitive" } }, + { user_id: { contains: searchTerm, mode: "insensitive" } }, + { user_name: { contains: searchTerm, mode: "insensitive" } }, + { tel: { contains: searchTerm, mode: "insensitive" } }, + { cell_phone: { contains: searchTerm, mode: "insensitive" } }, + { email: { contains: searchTerm, mode: "insensitive" } }, + ]; - const queryParams: any[] = []; - let paramIndex = 1; - let searchType = "none"; - - // 검색 조건 처리 - if (search && typeof search === "string" && search.trim()) { - // 통합 검색 (우선순위: 모든 주요 필드에서 검색) - searchType = "unified"; - const searchTerm = search.trim(); - - query += ` AND ( - UPPER(COALESCE(u.sabun, '')) LIKE UPPER($${paramIndex}) OR - UPPER(COALESCE(u.user_type_name, '')) LIKE UPPER($${paramIndex}) OR - UPPER(COALESCE(u.dept_name, '')) LIKE UPPER($${paramIndex}) OR - UPPER(COALESCE(u.position_name, '')) LIKE UPPER($${paramIndex}) OR - UPPER(COALESCE(u.user_id, '')) LIKE UPPER($${paramIndex}) OR - UPPER(COALESCE(u.user_name, '')) LIKE UPPER($${paramIndex}) OR - UPPER(COALESCE(u.tel, '')) LIKE UPPER($${paramIndex}) OR - UPPER(COALESCE(u.cell_phone, '')) LIKE UPPER($${paramIndex}) OR - UPPER(COALESCE(u.email, '')) LIKE UPPER($${paramIndex}) - )`; - queryParams.push(`%${searchTerm}%`); - paramIndex++; - - logger.info("통합 검색 실행", { searchTerm }); - } else if (searchField && searchValue) { - // 단일 필드 검색 - searchType = "single"; - const fieldMap: { [key: string]: string } = { - sabun: "u.sabun", - companyName: "u.user_type_name", - deptName: "u.dept_name", - positionName: "u.position_name", - userId: "u.user_id", - userName: "u.user_name", - tel: "u.tel", - cellPhone: "u.cell_phone", - email: "u.email", - }; - - if (fieldMap[searchField as string]) { - if (searchField === "tel") { - // 전화번호는 TEL과 CELL_PHONE 모두 검색 - query += ` AND (UPPER(u.tel) LIKE UPPER($${paramIndex}) OR UPPER(u.cell_phone) LIKE UPPER($${paramIndex}))`; - } else { - query += ` AND UPPER(${fieldMap[searchField as string]}) LIKE UPPER($${paramIndex})`; - } - queryParams.push(`%${searchValue}%`); - paramIndex++; - - logger.info("단일 필드 검색 실행", { searchField, searchValue }); - } - } else { - // 고급 검색 (개별 필드별 AND 조건) - const advancedSearchFields = [ - { param: search_sabun, field: "u.sabun" }, - { param: search_companyName, field: "u.user_type_name" }, - { param: search_deptName, field: "u.dept_name" }, - { param: search_positionName, field: "u.position_name" }, - { param: search_userId, field: "u.user_id" }, - { param: search_userName, field: "u.user_name" }, - { param: search_email, field: "u.email" }, - ]; - - let hasAdvancedSearch = false; - - for (const { param, field } of advancedSearchFields) { - if (param && typeof param === "string" && param.trim()) { - query += ` AND UPPER(${field}) LIKE UPPER($${paramIndex})`; - queryParams.push(`%${param.trim()}%`); - paramIndex++; - hasAdvancedSearch = true; - } - } - - // 전화번호 검색 (TEL 또는 CELL_PHONE) - if (search_tel && typeof search_tel === "string" && search_tel.trim()) { - query += ` AND (UPPER(u.tel) LIKE UPPER($${paramIndex}) OR UPPER(u.cell_phone) LIKE UPPER($${paramIndex}))`; - queryParams.push(`%${search_tel.trim()}%`); - paramIndex++; - hasAdvancedSearch = true; - } - - if (hasAdvancedSearch) { - searchType = "advanced"; - logger.info("고급 검색 실행", { - search_sabun, - search_companyName, - search_deptName, - search_positionName, - search_userId, - search_userName, - search_tel, - search_email, - }); - } - } - - // 기존 필터들 - if (deptCode) { - query += ` AND u.dept_code = $${paramIndex}`; - queryParams.push(deptCode); - paramIndex++; - } - - if (status) { - query += ` AND u.status = $${paramIndex}`; - queryParams.push(status); - paramIndex++; - } - - // 정렬 - query += ` ORDER BY u.regdate DESC, u.user_name`; - - // 페이징 파라미터 제외한 카운트용 파라미터 - const countParams = [...queryParams]; - - // 총 개수 조회를 위해 기존 쿼리를 COUNT로 변환 - const countQuery = query - .replace(/SELECT[\s\S]*?FROM/i, "SELECT COUNT(*) as total FROM") - .replace(/ORDER BY.*$/i, ""); - - const countResult = await client.query(countQuery, countParams); - const totalCount = parseInt(countResult.rows[0].total); - - // 페이징 적용 - const offset = (Number(page) - 1) * Number(countPerPage); - query += ` LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`; - queryParams.push(Number(countPerPage), offset); - - // 사용자 목록 조회 - const result = await client.query(query, queryParams); - const users = result.rows; - - // 디버깅: 원본 데이터베이스 조회 결과 로깅 - logger.info("=== 원본 데이터베이스 조회 결과 ==="); - logger.info("총 조회된 사용자 수:", users.length); - if (users.length > 0) { - logger.info("첫 번째 사용자 원본 데이터:", { - user_id: users[0].user_id, - user_name: users[0].user_name, - user_name_eng: users[0].user_name_eng, - dept_code: users[0].dept_code, - dept_name: users[0].dept_name, - position_code: users[0].position_code, - position_name: users[0].position_name, - email: users[0].email, - tel: users[0].tel, - cell_phone: users[0].cell_phone, - user_type: users[0].user_type, - user_type_name: users[0].user_type_name, - status: users[0].status, - company_code: users[0].company_code, - locale: users[0].locale, - regdate: users[0].regdate, - }); - } - - // 응답 데이터 가공 - const processedUsers = users.map((user) => { - // 디버깅: 각 필드별 값 확인 - const processedUser = { - userId: user.user_id, - userName: user.user_name, - userNameEng: user.user_name_eng || null, - sabun: user.sabun || null, - deptCode: user.dept_code || null, - deptName: user.dept_name || user.dept_name_full || null, - positionCode: user.position_code || null, - positionName: user.position_name || null, - email: user.email || null, - tel: user.tel || null, - cellPhone: user.cell_phone || null, - userType: user.user_type || null, - userTypeName: user.user_type_name || null, - status: user.status || "active", - companyCode: user.company_code || null, - locale: user.locale || null, - regDate: user.regdate - ? user.regdate.toISOString().split("T")[0] - : null, - }; - - // 빈 문자열을 null로 변환 - Object.keys(processedUser).forEach((key) => { - if (processedUser[key as keyof typeof processedUser] === "") { - (processedUser as any)[key] = null; - } - }); - - // 디버깅: 가공된 데이터 로깅 - logger.info(`사용자 ${user.user_id} 가공 결과:`, { - 원본: { - user_id: user.user_id, - user_name: user.user_name, - user_name_eng: user.user_name_eng, - dept_code: user.dept_code, - dept_name: user.dept_name, - position_code: user.position_code, - position_name: user.position_name, - email: user.email, - tel: user.tel, - cell_phone: user.cell_phone, - user_type: user.user_type, - user_type_name: user.user_type_name, - status: user.status, - company_code: user.company_code, - locale: user.locale, - regdate: user.regdate, - }, - 가공후: processedUser, - }); - - return processedUser; - }); - - const response = { - success: true, - data: processedUsers, - total: totalCount, - searchType, // 검색 타입 정보 (unified, single, advanced, none) - pagination: { - page: Number(page), - limit: Number(countPerPage), - totalPages: Math.ceil(totalCount / Number(countPerPage)), - }, - message: "사용자 목록 조회 성공", + logger.info("통합 검색 실행", { searchTerm }); + } else if (searchField && searchValue) { + // 단일 필드 검색 + searchType = "single"; + const fieldMap: { [key: string]: string } = { + sabun: "sabun", + companyName: "user_type_name", + deptName: "dept_name", + positionName: "position_name", + userId: "user_id", + userName: "user_name", + tel: "tel", + cellPhone: "cell_phone", + email: "email", }; - logger.info("사용자 목록 조회 성공", { - totalCount, - returnedCount: processedUsers.length, - searchType, - currentPage: Number(page), - countPerPage: Number(countPerPage), - }); + if (fieldMap[searchField as string]) { + if (searchField === "tel") { + whereConditions.OR = [ + { tel: { contains: searchValue as string, mode: "insensitive" } }, + { + cell_phone: { + contains: searchValue as string, + mode: "insensitive", + }, + }, + ]; + } else { + whereConditions[fieldMap[searchField as string]] = { + contains: searchValue as string, + mode: "insensitive", + }; + } + logger.info("단일 필드 검색 실행", { searchField, searchValue }); + } + } else { + // 고급 검색 (개별 필드별 AND 조건) + const advancedSearchFields = [ + { param: search_sabun, field: "sabun" }, + { param: search_companyName, field: "user_type_name" }, + { param: search_deptName, field: "dept_name" }, + { param: search_positionName, field: "position_name" }, + { param: search_userId, field: "user_id" }, + { param: search_userName, field: "user_name" }, + { param: search_email, field: "email" }, + ]; - res.status(200).json(response); - } finally { - await client.end(); + let hasAdvancedSearch = false; + + for (const { param, field } of advancedSearchFields) { + if (param && typeof param === "string" && param.trim()) { + whereConditions[field] = { + contains: param.trim(), + mode: "insensitive", + }; + hasAdvancedSearch = true; + } + } + + // 전화번호 검색 + if (search_tel && typeof search_tel === "string" && search_tel.trim()) { + whereConditions.OR = [ + { tel: { contains: search_tel.trim(), mode: "insensitive" } }, + { cell_phone: { contains: search_tel.trim(), mode: "insensitive" } }, + ]; + hasAdvancedSearch = true; + } + + if (hasAdvancedSearch) { + searchType = "advanced"; + logger.info("고급 검색 실행", { + search_sabun, + search_companyName, + search_deptName, + search_positionName, + search_userId, + search_userName, + search_tel, + search_email, + }); + } } + + // 기존 필터들 + if (deptCode) { + whereConditions.dept_code = deptCode as string; + } + + if (status) { + whereConditions.status = status as string; + } + + // 총 개수 조회 + const totalCount = await prisma.user_info.count({ + where: whereConditions, + }); + + // 사용자 목록 조회 + const users = await prisma.user_info.findMany({ + where: whereConditions, + orderBy: [{ regdate: "desc" }, { user_name: "asc" }], + skip: (Number(page) - 1) * Number(countPerPage), + take: Number(countPerPage), + select: { + sabun: true, + user_id: true, + user_name: true, + user_name_eng: true, + dept_code: true, + dept_name: true, + position_code: true, + position_name: true, + email: true, + tel: true, + cell_phone: true, + user_type: true, + user_type_name: true, + regdate: true, + status: true, + company_code: true, + locale: true, + }, + }); + + // 응답 데이터 가공 + const processedUsers = users.map((user) => ({ + userId: user.user_id, + userName: user.user_name, + userNameEng: user.user_name_eng || null, + sabun: user.sabun || null, + deptCode: user.dept_code || null, + deptName: user.dept_name || null, + positionCode: user.position_code || null, + positionName: user.position_name || null, + email: user.email || null, + tel: user.tel || null, + cellPhone: user.cell_phone || null, + userType: user.user_type || null, + userTypeName: user.user_type_name || null, + status: user.status || "active", + companyCode: user.company_code || null, + locale: user.locale || null, + regDate: user.regdate ? user.regdate.toISOString().split("T")[0] : null, + })); + + const response = { + success: true, + data: processedUsers, + total: totalCount, + searchType, + pagination: { + page: Number(page), + limit: Number(countPerPage), + totalPages: Math.ceil(totalCount / Number(countPerPage)), + }, + message: "사용자 목록 조회 성공", + }; + + logger.info("사용자 목록 조회 성공", { + totalCount, + returnedCount: processedUsers.length, + searchType, + currentPage: Number(page), + countPerPage: Number(countPerPage), + }); + + res.status(200).json(response); } catch (error) { logger.error("사용자 목록 조회 실패", { error }); res.status(500).json({ @@ -506,8 +417,6 @@ export const getUserLocale = async ( } // 데이터베이스에서 사용자 로케일 조회 - const prisma = (await import("../config/database")).default; - const userInfo = await prisma.user_info.findFirst({ where: { user_id: req.user.userId, @@ -587,8 +496,6 @@ export const setUserLocale = async ( } // 데이터베이스에 사용자 로케일 저장 - const prisma = (await import("../config/database")).default; - await prisma.user_info.update({ where: { user_id: req.user.userId, @@ -1473,35 +1380,23 @@ export async function getCompanyListFromDB( res: Response ): Promise { try { - logger.info("회사 목록 조회 요청 (DB)", { user: req.user }); + logger.info("회사 목록 조회 요청 (Prisma)", { user: req.user }); - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/ilshin", + // Prisma ORM으로 회사 목록 조회 + const companies = await prisma.company_mng.findMany({ + select: { + company_code: true, + company_name: true, + writer: true, + regdate: true, + status: true, + }, + orderBy: { + regdate: "desc", + }, }); - await client.connect(); - - // company_mng 테이블에서 회사 목록 조회 - const query = ` - SELECT - company_code, - company_name, - writer, - regdate, - status - FROM company_mng - ORDER BY regdate DESC - `; - - const result = await client.query(query); - const companies = result.rows; - - await client.end(); - - logger.info("회사 목록 조회 성공 (DB)", { count: companies.length }); + logger.info("회사 목록 조회 성공 (Prisma)", { count: companies.length }); const response: ApiResponse = { success: true, @@ -1512,7 +1407,7 @@ export async function getCompanyListFromDB( res.status(200).json(response); } catch (error) { - logger.error("회사 목록 조회 실패 (DB):", error); + logger.error("회사 목록 조회 실패 (Prisma):", error); res.status(500).json({ success: false, message: "회사 목록 조회 중 오류가 발생했습니다.", @@ -1846,7 +1741,7 @@ export const getUserInfo = async (req: AuthenticatedRequest, res: Response) => { export const checkDuplicateUserId = async ( req: AuthenticatedRequest, res: Response -) => { +): Promise => { try { const { userId } = req.body; logger.info(`사용자 ID 중복 체크 요청 - userId: ${userId}`, { @@ -1865,50 +1760,38 @@ export const checkDuplicateUserId = async ( return; } - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/ilshin", + // Prisma ORM으로 사용자 ID 중복 체크 + const existingUser = await prisma.user_info.findUnique({ + where: { + user_id: userId, + }, + select: { + user_id: true, + }, }); - await client.connect(); + const isDuplicate = !!existingUser; + const count = isDuplicate ? 1 : 0; - try { - // 사용자 ID 중복 체크 쿼리 - const query = ` - SELECT COUNT(*) as count - FROM user_info - WHERE user_id = $1 - `; - - const result = await client.query(query, [userId]); - const count = parseInt(result.rows[0].count); - - const isDuplicate = count > 0; - - const response = { - success: true, - data: { - isDuplicate, - count, - message: isDuplicate - ? "이미 사용 중인 사용자 ID입니다." - : "사용 가능한 사용자 ID입니다.", - }, - message: "사용자 ID 중복 체크 완료", - }; - - logger.info("사용자 ID 중복 체크 완료", { - userId, + const response = { + success: true, + data: { isDuplicate, count, - }); + message: isDuplicate + ? "이미 사용 중인 사용자 ID입니다." + : "사용 가능한 사용자 ID입니다.", + }, + message: "사용자 ID 중복 체크 완료", + }; - res.status(200).json(response); - } finally { - await client.end(); - } + logger.info("사용자 ID 중복 체크 완료", { + userId, + isDuplicate, + count, + }); + + res.status(200).json(response); } catch (error) { logger.error("사용자 ID 중복 체크 실패", { error, @@ -2112,20 +1995,25 @@ export const changeUserStatus = async ( return; } + // 복잡한 상태 변경은 직접 쿼리 사용 const client = new Client({ connectionString: config.databaseUrl, }); try { - await client.connect(); + // 1. Prisma ORM으로 사용자 존재 여부 확인 + const currentUser = await prisma.user_info.findUnique({ + where: { + user_id: userId, + }, + select: { + user_id: true, + user_name: true, + status: true, + }, + }); - // 1. 사용자 존재 여부 확인 - const userCheckResult = await client.query( - "SELECT user_id, user_name, status FROM user_info WHERE user_id = $1", - [userId] - ); - - if (userCheckResult.rows.length === 0) { + if (!currentUser) { res.status(404).json({ result: false, msg: "사용자를 찾을 수 없습니다.", @@ -2133,7 +2021,7 @@ export const changeUserStatus = async ( return; } - const currentUser = userCheckResult.rows[0]; + await client.connect(); // 2. 상태 변경 쿼리 실행 let updateQuery = ` @@ -2387,7 +2275,23 @@ export const createCompany = async ( return; } - // PostgreSQL 클라이언트 생성 + // Prisma ORM으로 회사명 중복 체크 + const existingCompany = await prisma.company_mng.findFirst({ + where: { + company_name: company_name.trim(), + }, + }); + + if (existingCompany) { + res.status(400).json({ + success: false, + message: "이미 등록된 회사명입니다.", + errorCode: "COMPANY_NAME_DUPLICATE", + }); + return; + } + + // PostgreSQL 클라이언트 생성 (복잡한 코드 생성 쿼리용) const client = new Client({ connectionString: process.env.DATABASE_URL || @@ -2397,26 +2301,6 @@ export const createCompany = async ( await client.connect(); try { - // 회사명 중복 체크 - const duplicateCheckQuery = ` - SELECT COUNT(*) as count - FROM company_mng - WHERE company_name = $1 - `; - - const duplicateResult = await client.query(duplicateCheckQuery, [ - company_name.trim(), - ]); - - if (parseInt(duplicateResult.rows[0].count) > 0) { - res.status(400).json({ - success: false, - message: "이미 등록된 회사명입니다.", - errorCode: "COMPANY_NAME_DUPLICATE", - }); - return; - } - // 회사 코드 생성 (COMPANY_1, COMPANY_2, ...) const codeQuery = ` SELECT COALESCE(MAX(CAST(SUBSTRING(company_code FROM 9) AS INTEGER)), 0) + 1 as next_number @@ -2622,60 +2506,48 @@ export const deleteCompany = async ( user: req.user, }); - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/ilshin", + // Prisma ORM으로 회사 존재 여부 확인 + const existingCompany = await prisma.company_mng.findUnique({ + where: { + company_code: companyCode, + }, + select: { + company_code: true, + company_name: true, + }, }); - await client.connect(); - - try { - // 회사 존재 여부 확인 - const checkQuery = ` - SELECT company_code, company_name - FROM company_mng - WHERE company_code = $1 - `; - - const checkResult = await client.query(checkQuery, [companyCode]); - - if (checkResult.rows.length === 0) { - res.status(404).json({ - success: false, - message: "해당 회사를 찾을 수 없습니다.", - errorCode: "COMPANY_NOT_FOUND", - }); - return; - } - - // 회사 삭제 - const deleteQuery = ` - DELETE FROM company_mng - WHERE company_code = $1 - `; - - await client.query(deleteQuery, [companyCode]); - - logger.info("회사 삭제 성공", { - companyCode, - companyName: checkResult.rows[0].company_name, + if (!existingCompany) { + res.status(404).json({ + success: false, + message: "해당 회사를 찾을 수 없습니다.", + errorCode: "COMPANY_NOT_FOUND", }); - - const response = { - success: true, - message: "회사가 삭제되었습니다.", - data: { - company_code: companyCode, - company_name: checkResult.rows[0].company_name, - }, - }; - - res.status(200).json(response); - } finally { - await client.end(); + return; } + + // Prisma ORM으로 회사 삭제 + await prisma.company_mng.delete({ + where: { + company_code: companyCode, + }, + }); + + logger.info("회사 삭제 성공", { + companyCode, + companyName: existingCompany.company_name, + }); + + const response = { + success: true, + message: "회사가 삭제되었습니다.", + data: { + company_code: companyCode, + company_name: existingCompany.company_name, + }, + }; + + res.status(200).json(response); } catch (error) { logger.error("회사 삭제 실패", { error }); res.status(500).json({ @@ -2816,143 +2688,144 @@ export const updateProfile = async ( export const resetUserPassword = async ( req: AuthenticatedRequest, res: Response -) => { +): Promise => { + const { userId, newPassword } = req.body; + + logger.info("비밀번호 초기화 요청", { userId, user: req.user }); + + // 입력값 검증 + if (!userId || !userId.trim()) { + res.status(400).json({ + result: false, + msg: "사용자 ID가 필요합니다.", + }); + return; + } + + if (!newPassword || !newPassword.trim()) { + res.status(400).json({ + success: false, + result: false, + message: "새 비밀번호가 필요합니다.", + msg: "새 비밀번호가 필요합니다.", + }); + return; + } + + // 비밀번호 길이 검증 (최소 4자) + if (newPassword.length < 4) { + res.status(400).json({ + success: false, + result: false, + message: "비밀번호는 최소 4자 이상이어야 합니다.", + msg: "비밀번호는 최소 4자 이상이어야 합니다.", + }); + return; + } + + // 복잡한 암호화 로직은 직접 쿼리 사용 + const client = new Client({ connectionString: config.databaseUrl }); + try { - const { userId, newPassword } = req.body; + // 1. Prisma ORM으로 사용자 존재 여부 확인 + const currentUser = await prisma.user_info.findUnique({ + where: { + user_id: userId, + }, + select: { + user_id: true, + user_name: true, + }, + }); - logger.info("비밀번호 초기화 요청", { userId, user: req.user }); - - // 입력값 검증 - if (!userId || !userId.trim()) { - res.status(400).json({ - result: false, - msg: "사용자 ID가 필요합니다.", - }); - return; - } - - if (!newPassword || !newPassword.trim()) { - res.status(400).json({ + if (!currentUser) { + res.status(404).json({ success: false, result: false, - message: "새 비밀번호가 필요합니다.", - msg: "새 비밀번호가 필요합니다.", + message: "사용자를 찾을 수 없습니다.", + msg: "사용자를 찾을 수 없습니다.", }); return; } - // 비밀번호 길이 검증 (최소 4자) - if (newPassword.length < 4) { - res.status(400).json({ - success: false, - result: false, - message: "비밀번호는 최소 4자 이상이어야 합니다.", - msg: "비밀번호는 최소 4자 이상이어야 합니다.", - }); - return; - } + await client.connect(); - const client = new Client({ connectionString: config.databaseUrl }); + // 2. 비밀번호 암호화 (기존 Java 로직과 동일) + let encryptedPassword: string; try { - await client.connect(); + // EncryptUtil과 동일한 암호화 사용 + const crypto = require("crypto"); + const keyName = "ILJIAESSECRETKEY"; + const algorithm = "aes-128-ecb"; - // 1. 사용자 존재 여부 확인 - const userCheckResult = await client.query( - "SELECT user_id, user_name FROM user_info WHERE user_id = $1", - [userId] - ); + // AES-128-ECB 암호화 + const cipher = crypto.createCipher(algorithm, keyName); + let encrypted = cipher.update(newPassword, "utf8", "hex"); + encrypted += cipher.final("hex"); + encryptedPassword = encrypted.toUpperCase(); + } catch (encryptError) { + logger.error("비밀번호 암호화 중 오류 발생", { + error: encryptError, + userId, + }); + res.status(500).json({ + success: false, + result: false, + message: "비밀번호 암호화 중 오류가 발생했습니다.", + msg: "비밀번호 암호화 중 오류가 발생했습니다.", + }); + return; + } - if (userCheckResult.rows.length === 0) { - res.status(404).json({ - success: false, - result: false, - message: "사용자를 찾을 수 없습니다.", - msg: "사용자를 찾을 수 없습니다.", - }); - return; - } + // 3. 비밀번호 업데이트 실행 + const updateResult = await client.query( + "UPDATE user_info SET user_password = $1 WHERE user_id = $2", + [encryptedPassword, userId] + ); - const currentUser = userCheckResult.rows[0]; - - // 2. 비밀번호 암호화 (기존 Java 로직과 동일) - let encryptedPassword: string; + if (updateResult.rowCount && updateResult.rowCount > 0) { + // 4. 이력 저장 (선택적) try { - // EncryptUtil과 동일한 암호화 사용 - const crypto = require("crypto"); - const keyName = "ILJIAESSECRETKEY"; - const algorithm = "aes-128-ecb"; - - // AES-128-ECB 암호화 - const cipher = crypto.createCipher(algorithm, keyName); - let encrypted = cipher.update(newPassword, "utf8", "hex"); - encrypted += cipher.final("hex"); - encryptedPassword = encrypted.toUpperCase(); - } catch (encryptError) { - logger.error("비밀번호 암호화 중 오류 발생", { - error: encryptError, - userId, - }); - res.status(500).json({ - success: false, - result: false, - message: "비밀번호 암호화 중 오류가 발생했습니다.", - msg: "비밀번호 암호화 중 오류가 발생했습니다.", - }); - return; - } - - // 3. 비밀번호 업데이트 실행 - const updateResult = await client.query( - "UPDATE user_info SET user_password = $1 WHERE user_id = $2", - [encryptedPassword, userId] - ); - - if (updateResult.rowCount && updateResult.rowCount > 0) { - // 4. 이력 저장 (선택적) - try { - const writer = req.user?.userId || "system"; - await client.query( - ` + const writer = req.user?.userId || "system"; + await client.query( + ` INSERT INTO user_info_history (sabun, user_id, user_name, dept_code, dept_name, user_type_name, history_type, writer, regdate, status) VALUES ('', $1, $2, '', '', '', '비밀번호 초기화', $3, NOW(), '') `, - [userId, currentUser.user_name || userId, writer] - ); - } catch (historyError) { - logger.warn("비밀번호 초기화 이력 저장 실패", { - error: historyError, - userId, - }); - // 이력 저장 실패해도 비밀번호 초기화는 성공으로 처리 - } - - logger.info("비밀번호 초기화 성공", { + [userId, currentUser.user_name || userId, writer] + ); + } catch (historyError) { + logger.warn("비밀번호 초기화 이력 저장 실패", { + error: historyError, userId, - updatedBy: req.user?.userId, - }); - - res.json({ - success: true, - result: true, - message: "비밀번호가 성공적으로 초기화되었습니다.", - msg: "비밀번호가 성공적으로 초기화되었습니다.", - }); - } else { - res.status(400).json({ - success: false, - result: false, - message: "사용자 정보를 찾을 수 없거나 비밀번호 변경에 실패했습니다.", - msg: "사용자 정보를 찾을 수 없거나 비밀번호 변경에 실패했습니다.", }); + // 이력 저장 실패해도 비밀번호 초기화는 성공으로 처리 } - } finally { - await client.end(); + + logger.info("비밀번호 초기화 성공", { + userId, + updatedBy: req.user?.userId, + }); + + res.json({ + success: true, + result: true, + message: "비밀번호가 성공적으로 초기화되었습니다.", + msg: "비밀번호가 성공적으로 초기화되었습니다.", + }); + } else { + res.status(400).json({ + success: false, + result: false, + message: "사용자 정보를 찾을 수 없거나 비밀번호 변경에 실패했습니다.", + msg: "사용자 정보를 찾을 수 없거나 비밀번호 변경에 실패했습니다.", + }); } } catch (error) { logger.error("비밀번호 초기화 중 오류 발생", { error, - userId: req.body.userId, + userId, }); res.status(500).json({ success: false, @@ -2960,5 +2833,7 @@ export const resetUserPassword = async ( message: "비밀번호 초기화 중 시스템 오류가 발생했습니다.", msg: "비밀번호 초기화 중 시스템 오류가 발생했습니다.", }); + } finally { + await client.end(); } }; diff --git a/backend-node/src/controllers/multilangController.ts b/backend-node/src/controllers/multilangController.ts index ab1c395c..14155f86 100644 --- a/backend-node/src/controllers/multilangController.ts +++ b/backend-node/src/controllers/multilangController.ts @@ -2,7 +2,6 @@ import { Request, Response } from "express"; import { logger } from "../utils/logger"; import { AuthenticatedRequest } from "../types/auth"; import { MultiLangService } from "../services/multilangService"; -import { Client } from "pg"; import { CreateLanguageRequest, UpdateLanguageRequest, @@ -25,29 +24,16 @@ export const getLanguages = async ( try { logger.info("언어 목록 조회 요청", { user: req.user }); - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/ilshin", - }); + const multiLangService = new MultiLangService(); + const languages = await multiLangService.getLanguages(); - await client.connect(); + const response: ApiResponse = { + success: true, + message: "언어 목록 조회 성공", + data: languages, + }; - try { - const multiLangService = new MultiLangService(client); - const languages = await multiLangService.getLanguages(); - - const response: ApiResponse = { - success: true, - message: "언어 목록 조회 성공", - data: languages, - }; - - res.status(200).json(response); - } finally { - await client.end(); - } + res.status(200).json(response); } catch (error) { logger.error("언어 목록 조회 실패:", error); res.status(500).json({ @@ -90,33 +76,20 @@ export const createLanguage = async ( return; } - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/ilshin", + const multiLangService = new MultiLangService(); + const createdLanguage = await multiLangService.createLanguage({ + ...languageData, + createdBy: req.user?.userId || "system", + updatedBy: req.user?.userId || "system", }); - await client.connect(); + const response: ApiResponse = { + success: true, + message: "언어가 성공적으로 생성되었습니다.", + data: createdLanguage, + }; - try { - const multiLangService = new MultiLangService(client); - const createdLanguage = await multiLangService.createLanguage({ - ...languageData, - createdBy: req.user?.userId || "system", - updatedBy: req.user?.userId || "system", - }); - - const response: ApiResponse = { - success: true, - message: "언어가 성공적으로 생성되었습니다.", - data: createdLanguage, - }; - - res.status(201).json(response); - } finally { - await client.end(); - } + res.status(201).json(response); } catch (error) { logger.error("언어 생성 실패:", error); res.status(500).json({ @@ -144,32 +117,19 @@ export const updateLanguage = async ( logger.info("언어 수정 요청", { langCode, languageData, user: req.user }); - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/ilshin", + const multiLangService = new MultiLangService(); + const updatedLanguage = await multiLangService.updateLanguage(langCode, { + ...languageData, + updatedBy: req.user?.userId || "system", }); - await client.connect(); + const response: ApiResponse = { + success: true, + message: "언어가 성공적으로 수정되었습니다.", + data: updatedLanguage, + }; - try { - const multiLangService = new MultiLangService(client); - const updatedLanguage = await multiLangService.updateLanguage(langCode, { - ...languageData, - updatedBy: req.user?.userId || "system", - }); - - const response: ApiResponse = { - success: true, - message: "언어가 성공적으로 수정되었습니다.", - data: updatedLanguage, - }; - - res.status(200).json(response); - } finally { - await client.end(); - } + res.status(200).json(response); } catch (error) { logger.error("언어 수정 실패:", error); res.status(500).json({ @@ -195,29 +155,16 @@ export const toggleLanguage = async ( const { langCode } = req.params; logger.info("언어 상태 토글 요청", { langCode, user: req.user }); - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/ilshin", - }); + const multiLangService = new MultiLangService(); + const result = await multiLangService.toggleLanguage(langCode); - await client.connect(); + const response: ApiResponse = { + success: true, + message: `언어가 ${result}되었습니다.`, + data: result, + }; - try { - const multiLangService = new MultiLangService(client); - const result = await multiLangService.toggleLanguage(langCode); - - const response: ApiResponse = { - success: true, - message: `언어가 ${result}되었습니다.`, - data: result, - }; - - res.status(200).json(response); - } finally { - await client.end(); - } + res.status(200).json(response); } catch (error) { logger.error("언어 상태 토글 실패:", error); res.status(500).json({ @@ -246,34 +193,21 @@ export const getLangKeys = async ( user: req.user, }); - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/ilshin", + const multiLangService = new MultiLangService(); + const langKeys = await multiLangService.getLangKeys({ + companyCode: companyCode as string, + menuCode: menuCode as string, + keyType: keyType as string, + searchText: searchText as string, }); - await client.connect(); + const response: ApiResponse = { + success: true, + message: "다국어 키 목록 조회 성공", + data: langKeys, + }; - try { - const multiLangService = new MultiLangService(client); - const langKeys = await multiLangService.getLangKeys({ - companyCode: companyCode as string, - menuCode: menuCode as string, - keyType: keyType as string, - searchText: searchText as string, - }); - - const response: ApiResponse = { - success: true, - message: "다국어 키 목록 조회 성공", - data: langKeys, - }; - - res.status(200).json(response); - } finally { - await client.end(); - } + res.status(200).json(response); } catch (error) { logger.error("다국어 키 목록 조회 실패:", error); res.status(500).json({ @@ -299,29 +233,16 @@ export const getLangTexts = async ( const { keyId } = req.params; logger.info("다국어 텍스트 조회 요청", { keyId, user: req.user }); - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/ilshin", - }); + const multiLangService = new MultiLangService(); + const langTexts = await multiLangService.getLangTexts(parseInt(keyId)); - await client.connect(); + const response: ApiResponse = { + success: true, + message: "다국어 텍스트 조회 성공", + data: langTexts, + }; - try { - const multiLangService = new MultiLangService(client); - const langTexts = await multiLangService.getLangTexts(parseInt(keyId)); - - const response: ApiResponse = { - success: true, - message: "다국어 텍스트 조회 성공", - data: langTexts, - }; - - res.status(200).json(response); - } finally { - await client.end(); - } + res.status(200).json(response); } catch (error) { logger.error("다국어 텍스트 조회 실패:", error); res.status(500).json({ @@ -360,33 +281,20 @@ export const createLangKey = async ( return; } - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/ilshin", + const multiLangService = new MultiLangService(); + const keyId = await multiLangService.createLangKey({ + ...keyData, + createdBy: req.user?.userId || "system", + updatedBy: req.user?.userId || "system", }); - await client.connect(); + const response: ApiResponse = { + success: true, + message: "다국어 키가 성공적으로 생성되었습니다.", + data: keyId, + }; - try { - const multiLangService = new MultiLangService(client); - const keyId = await multiLangService.createLangKey({ - ...keyData, - createdBy: req.user?.userId || "system", - updatedBy: req.user?.userId || "system", - }); - - const response: ApiResponse = { - success: true, - message: "다국어 키가 성공적으로 생성되었습니다.", - data: keyId, - }; - - res.status(201).json(response); - } finally { - await client.end(); - } + res.status(201).json(response); } catch (error) { logger.error("다국어 키 생성 실패:", error); res.status(500).json({ @@ -414,32 +322,19 @@ export const updateLangKey = async ( logger.info("다국어 키 수정 요청", { keyId, keyData, user: req.user }); - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/ilshin", + const multiLangService = new MultiLangService(); + await multiLangService.updateLangKey(parseInt(keyId), { + ...keyData, + updatedBy: req.user?.userId || "system", }); - await client.connect(); + const response: ApiResponse = { + success: true, + message: "다국어 키가 성공적으로 수정되었습니다.", + data: "수정 완료", + }; - try { - const multiLangService = new MultiLangService(client); - await multiLangService.updateLangKey(parseInt(keyId), { - ...keyData, - updatedBy: req.user?.userId || "system", - }); - - const response: ApiResponse = { - success: true, - message: "다국어 키가 성공적으로 수정되었습니다.", - data: "수정 완료", - }; - - res.status(200).json(response); - } finally { - await client.end(); - } + res.status(200).json(response); } catch (error) { logger.error("다국어 키 수정 실패:", error); res.status(500).json({ @@ -465,29 +360,16 @@ export const deleteLangKey = async ( const { keyId } = req.params; logger.info("다국어 키 삭제 요청", { keyId, user: req.user }); - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/ilshin", - }); + const multiLangService = new MultiLangService(); + await multiLangService.deleteLangKey(parseInt(keyId)); - await client.connect(); + const response: ApiResponse = { + success: true, + message: "다국어 키가 성공적으로 삭제되었습니다.", + data: "삭제 완료", + }; - try { - const multiLangService = new MultiLangService(client); - await multiLangService.deleteLangKey(parseInt(keyId)); - - const response: ApiResponse = { - success: true, - message: "다국어 키가 성공적으로 삭제되었습니다.", - data: "삭제 완료", - }; - - res.status(200).json(response); - } finally { - await client.end(); - } + res.status(200).json(response); } catch (error) { logger.error("다국어 키 삭제 실패:", error); res.status(500).json({ @@ -513,29 +395,16 @@ export const toggleLangKey = async ( const { keyId } = req.params; logger.info("다국어 키 상태 토글 요청", { keyId, user: req.user }); - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/ilshin", - }); + const multiLangService = new MultiLangService(); + const result = await multiLangService.toggleLangKey(parseInt(keyId)); - await client.connect(); + const response: ApiResponse = { + success: true, + message: `다국어 키가 ${result}되었습니다.`, + data: result, + }; - try { - const multiLangService = new MultiLangService(client); - const result = await multiLangService.toggleLangKey(parseInt(keyId)); - - const response: ApiResponse = { - success: true, - message: `다국어 키가 ${result}되었습니다.`, - data: result, - }; - - res.status(200).json(response); - } finally { - await client.end(); - } + res.status(200).json(response); } catch (error) { logger.error("다국어 키 상태 토글 실패:", error); res.status(500).json({ @@ -580,35 +449,22 @@ export const saveLangTexts = async ( return; } - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/ilshin", + const multiLangService = new MultiLangService(); + await multiLangService.saveLangTexts(parseInt(keyId), { + texts: textData.texts.map((text) => ({ + ...text, + createdBy: req.user?.userId || "system", + updatedBy: req.user?.userId || "system", + })), }); - await client.connect(); + const response: ApiResponse = { + success: true, + message: "다국어 텍스트가 성공적으로 저장되었습니다.", + data: "저장 완료", + }; - try { - const multiLangService = new MultiLangService(client); - await multiLangService.saveLangTexts(parseInt(keyId), { - texts: textData.texts.map((text) => ({ - ...text, - createdBy: req.user?.userId || "system", - updatedBy: req.user?.userId || "system", - })), - }); - - const response: ApiResponse = { - success: true, - message: "다국어 텍스트가 성공적으로 저장되었습니다.", - data: "저장 완료", - }; - - res.status(200).json(response); - } finally { - await client.end(); - } + res.status(200).json(response); } catch (error) { logger.error("다국어 텍스트 저장 실패:", error); res.status(500).json({ @@ -654,34 +510,21 @@ export const getUserText = async ( return; } - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/ilshin", + const multiLangService = new MultiLangService(); + const langText = await multiLangService.getUserText({ + companyCode, + menuCode, + langKey, + userLang: userLang as string, }); - await client.connect(); + const response: ApiResponse = { + success: true, + message: "사용자별 다국어 텍스트 조회 성공", + data: langText, + }; - try { - const multiLangService = new MultiLangService(client); - const langText = await multiLangService.getUserText({ - companyCode, - menuCode, - langKey, - userLang: userLang as string, - }); - - const response: ApiResponse = { - success: true, - message: "사용자별 다국어 텍스트 조회 성공", - data: langText, - }; - - res.status(200).json(response); - } finally { - await client.end(); - } + res.status(200).json(response); } catch (error) { logger.error("사용자별 다국어 텍스트 조회 실패:", error); res.status(500).json({ @@ -713,33 +556,20 @@ export const getLangText = async ( user: req.user, }); - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/ilshin", - }); + const multiLangService = new MultiLangService(); + const langText = await multiLangService.getLangText( + companyCode, + langKey, + langCode + ); - await client.connect(); + const response: ApiResponse = { + success: true, + message: "특정 키의 다국어 텍스트 조회 성공", + data: langText, + }; - try { - const multiLangService = new MultiLangService(client); - const langText = await multiLangService.getLangText( - companyCode, - langKey, - langCode - ); - - const response: ApiResponse = { - success: true, - message: "특정 키의 다국어 텍스트 조회 성공", - data: langText, - }; - - res.status(200).json(response); - } finally { - await client.end(); - } + res.status(200).json(response); } catch (error) { logger.error("특정 키의 다국어 텍스트 조회 실패:", error); res.status(500).json({ @@ -777,29 +607,16 @@ export const deleteLanguage = async ( return; } - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/ilshin", - }); + const multiLangService = new MultiLangService(); + await multiLangService.deleteLanguage(langCode); - await client.connect(); + const response: ApiResponse = { + success: true, + message: "언어가 성공적으로 삭제되었습니다.", + data: "삭제 완료", + }; - try { - const multiLangService = new MultiLangService(client); - await multiLangService.deleteLanguage(langCode); - - const response: ApiResponse = { - success: true, - message: "언어가 성공적으로 삭제되었습니다.", - data: "삭제 완료", - }; - - res.status(200).json(response); - } finally { - await client.end(); - } + res.status(200).json(response); } catch (error) { logger.error("언어 삭제 실패:", error); res.status(500).json({ @@ -866,34 +683,21 @@ export const getBatchTranslations = async ( return; } - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/ilshin", + const multiLangService = new MultiLangService(); + const translations = await multiLangService.getBatchTranslations({ + companyCode: finalCompanyCode as string, + menuCode: finalMenuCode as string, + userLang: finalUserLang as string, + langKeys, }); - await client.connect(); + const response: ApiResponse> = { + success: true, + message: "다국어 텍스트 배치 조회 성공", + data: translations, + }; - try { - const multiLangService = new MultiLangService(client); - const translations = await multiLangService.getBatchTranslations({ - companyCode: finalCompanyCode as string, - menuCode: finalMenuCode as string, - userLang: finalUserLang as string, - langKeys, - }); - - const response: ApiResponse> = { - success: true, - message: "다국어 텍스트 배치 조회 성공", - data: translations, - }; - - res.status(200).json(response); - } finally { - await client.end(); - } + res.status(200).json(response); } catch (error) { logger.error("다국어 텍스트 배치 조회 실패:", error); res.status(500).json({ diff --git a/backend-node/src/controllers/tableManagementController.ts b/backend-node/src/controllers/tableManagementController.ts index 10c38831..cf54176a 100644 --- a/backend-node/src/controllers/tableManagementController.ts +++ b/backend-node/src/controllers/tableManagementController.ts @@ -2,7 +2,6 @@ import { Request, Response } from "express"; import { logger } from "../utils/logger"; import { AuthenticatedRequest } from "../types/auth"; import { ApiResponse } from "../types/common"; -import { Client } from "pg"; import { TableManagementService } from "../services/tableManagementService"; import { TableInfo, @@ -23,29 +22,18 @@ export async function getTableList( try { logger.info("=== 테이블 목록 조회 시작 ==="); - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: process.env.DATABASE_URL, - }); + const tableManagementService = new TableManagementService(); + const tableList = await tableManagementService.getTableList(); - await client.connect(); + logger.info(`테이블 목록 조회 결과: ${tableList.length}개`); - try { - const tableManagementService = new TableManagementService(client); - const tableList = await tableManagementService.getTableList(); + const response: ApiResponse = { + success: true, + message: "테이블 목록을 성공적으로 조회했습니다.", + data: tableList, + }; - logger.info(`테이블 목록 조회 결과: ${tableList.length}개`); - - const response: ApiResponse = { - success: true, - message: "테이블 목록을 성공적으로 조회했습니다.", - data: tableList, - }; - - res.status(200).json(response); - } finally { - await client.end(); - } + res.status(200).json(response); } catch (error) { logger.error("테이블 목록 조회 중 오류 발생:", error); @@ -86,29 +74,18 @@ export async function getColumnList( return; } - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: process.env.DATABASE_URL, - }); + const tableManagementService = new TableManagementService(); + const columnList = await tableManagementService.getColumnList(tableName); - await client.connect(); + logger.info(`컬럼 정보 조회 결과: ${tableName}, ${columnList.length}개`); - try { - const tableManagementService = new TableManagementService(client); - const columnList = await tableManagementService.getColumnList(tableName); + const response: ApiResponse = { + success: true, + message: "컬럼 목록을 성공적으로 조회했습니다.", + data: columnList, + }; - logger.info(`컬럼 정보 조회 결과: ${tableName}, ${columnList.length}개`); - - const response: ApiResponse = { - success: true, - message: "컬럼 목록을 성공적으로 조회했습니다.", - data: columnList, - }; - - res.status(200).json(response); - } finally { - await client.end(); - } + res.status(200).json(response); } catch (error) { logger.error("컬럼 정보 조회 중 오류 발생:", error); @@ -164,32 +141,21 @@ export async function updateColumnSettings( return; } - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: process.env.DATABASE_URL, - }); + const tableManagementService = new TableManagementService(); + await tableManagementService.updateColumnSettings( + tableName, + columnName, + settings + ); - await client.connect(); + logger.info(`컬럼 설정 업데이트 완료: ${tableName}.${columnName}`); - try { - const tableManagementService = new TableManagementService(client); - await tableManagementService.updateColumnSettings( - tableName, - columnName, - settings - ); + const response: ApiResponse = { + success: true, + message: "컬럼 설정을 성공적으로 저장했습니다.", + }; - logger.info(`컬럼 설정 업데이트 완료: ${tableName}.${columnName}`); - - const response: ApiResponse = { - success: true, - message: "컬럼 설정을 성공적으로 저장했습니다.", - }; - - res.status(200).json(response); - } finally { - await client.end(); - } + res.status(200).json(response); } catch (error) { logger.error("컬럼 설정 업데이트 중 오류 발생:", error); @@ -245,33 +211,22 @@ export async function updateAllColumnSettings( return; } - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: process.env.DATABASE_URL, - }); + const tableManagementService = new TableManagementService(); + await tableManagementService.updateAllColumnSettings( + tableName, + columnSettings + ); - await client.connect(); + logger.info( + `전체 컬럼 설정 일괄 업데이트 완료: ${tableName}, ${columnSettings.length}개` + ); - try { - const tableManagementService = new TableManagementService(client); - await tableManagementService.updateAllColumnSettings( - tableName, - columnSettings - ); + const response: ApiResponse = { + success: true, + message: "모든 컬럼 설정을 성공적으로 저장했습니다.", + }; - logger.info( - `전체 컬럼 설정 일괄 업데이트 완료: ${tableName}, ${columnSettings.length}개` - ); - - const response: ApiResponse = { - success: true, - message: "모든 컬럼 설정을 성공적으로 저장했습니다.", - }; - - res.status(200).json(response); - } finally { - await client.end(); - } + res.status(200).json(response); } catch (error) { logger.error("전체 컬럼 설정 일괄 업데이트 중 오류 발생:", error); @@ -312,43 +267,31 @@ export async function getTableLabels( return; } - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: process.env.DATABASE_URL, - }); + const tableManagementService = new TableManagementService(); + const tableLabels = await tableManagementService.getTableLabels(tableName); - await client.connect(); - - try { - const tableManagementService = new TableManagementService(client); - const tableLabels = - await tableManagementService.getTableLabels(tableName); - - if (!tableLabels) { - const response: ApiResponse = { - success: false, - message: "테이블 라벨 정보를 찾을 수 없습니다.", - error: { - code: "TABLE_LABELS_NOT_FOUND", - details: `테이블 ${tableName}의 라벨 정보가 존재하지 않습니다.`, - }, - }; - res.status(404).json(response); - return; - } - - logger.info(`테이블 라벨 정보 조회 완료: ${tableName}`); - - const response: ApiResponse = { - success: true, - message: "테이블 라벨 정보를 성공적으로 조회했습니다.", - data: tableLabels, + if (!tableLabels) { + const response: ApiResponse = { + success: false, + message: "테이블 라벨 정보를 찾을 수 없습니다.", + error: { + code: "TABLE_LABELS_NOT_FOUND", + details: `테이블 ${tableName}의 라벨 정보가 존재하지 않습니다.`, + }, }; - - res.status(200).json(response); - } finally { - await client.end(); + res.status(404).json(response); + return; } + + logger.info(`테이블 라벨 정보 조회 완료: ${tableName}`); + + const response: ApiResponse = { + success: true, + message: "테이블 라벨 정보를 성공적으로 조회했습니다.", + data: tableLabels, + }; + + res.status(200).json(response); } catch (error) { logger.error("테이블 라벨 정보 조회 중 오류 발생:", error); @@ -389,45 +332,34 @@ export async function getColumnLabels( return; } - // PostgreSQL 클라이언트 생성 - const client = new Client({ - connectionString: process.env.DATABASE_URL, - }); + const tableManagementService = new TableManagementService(); + const columnLabels = await tableManagementService.getColumnLabels( + tableName, + columnName + ); - await client.connect(); - - try { - const tableManagementService = new TableManagementService(client); - const columnLabels = await tableManagementService.getColumnLabels( - tableName, - columnName - ); - - if (!columnLabels) { - const response: ApiResponse = { - success: false, - message: "컬럼 라벨 정보를 찾을 수 없습니다.", - error: { - code: "COLUMN_LABELS_NOT_FOUND", - details: `컬럼 ${tableName}.${columnName}의 라벨 정보가 존재하지 않습니다.`, - }, - }; - res.status(404).json(response); - return; - } - - logger.info(`컬럼 라벨 정보 조회 완료: ${tableName}.${columnName}`); - - const response: ApiResponse = { - success: true, - message: "컬럼 라벨 정보를 성공적으로 조회했습니다.", - data: columnLabels, + if (!columnLabels) { + const response: ApiResponse = { + success: false, + message: "컬럼 라벨 정보를 찾을 수 없습니다.", + error: { + code: "COLUMN_LABELS_NOT_FOUND", + details: `컬럼 ${tableName}.${columnName}의 라벨 정보가 존재하지 않습니다.`, + }, }; - - res.status(200).json(response); - } finally { - await client.end(); + res.status(404).json(response); + return; } + + logger.info(`컬럼 라벨 정보 조회 완료: ${tableName}.${columnName}`); + + const response: ApiResponse = { + success: true, + message: "컬럼 라벨 정보를 성공적으로 조회했습니다.", + data: columnLabels, + }; + + res.status(200).json(response); } catch (error) { logger.error("컬럼 라벨 정보 조회 중 오류 발생:", error); diff --git a/backend-node/src/services/multilangService.ts b/backend-node/src/services/multilangService.ts index 00f72439..742bb2e9 100644 --- a/backend-node/src/services/multilangService.ts +++ b/backend-node/src/services/multilangService.ts @@ -1,4 +1,4 @@ -import { Client } from "pg"; +import { PrismaClient } from "@prisma/client"; import { logger } from "../utils/logger"; import { Language, @@ -15,12 +15,10 @@ import { ApiResponse, } from "../types/multilang"; -export class MultiLangService { - private client: Client; +const prisma = new PrismaClient(); - constructor(client: Client) { - this.client = client; - } +export class MultiLangService { + constructor() {} /** * 언어 목록 조회 @@ -29,30 +27,35 @@ export class MultiLangService { try { logger.info("언어 목록 조회 시작"); - const query = ` - SELECT - lang_code as "langCode", - lang_name as "langName", - lang_native as "langNative", - is_active as "isActive", - sort_order as "sortOrder", - created_date as "createdDate", - created_by as "createdBy", - updated_date as "updatedDate", - updated_by as "updatedBy" - FROM language_master - ORDER BY sort_order, lang_code - `; + const languages = await prisma.language_master.findMany({ + orderBy: [{ sort_order: "asc" }, { lang_code: "asc" }], + select: { + lang_code: true, + lang_name: true, + lang_native: true, + is_active: true, + sort_order: true, + created_date: true, + created_by: true, + updated_date: true, + updated_by: true, + }, + }); - const result = await this.client.query(query); - const languages = result.rows.map((row) => ({ - ...row, - createdDate: row.createdDate ? new Date(row.createdDate) : undefined, - updatedDate: row.updatedDate ? new Date(row.updatedDate) : undefined, + const mappedLanguages: Language[] = languages.map((lang) => ({ + langCode: lang.lang_code, + langName: lang.lang_name, + langNative: lang.lang_native, + isActive: lang.is_active || "N", + sortOrder: lang.sort_order ?? undefined, + createdDate: lang.created_date || undefined, + createdBy: lang.created_by || undefined, + updatedDate: lang.updated_date || undefined, + updatedBy: lang.updated_by || undefined, })); - logger.info(`언어 목록 조회 완료: ${languages.length}개`); - return languages; + logger.info(`언어 목록 조회 완료: ${mappedLanguages.length}개`); + return mappedLanguages; } catch (error) { logger.error("언어 목록 조회 중 오류 발생:", error); throw new Error( @@ -69,40 +72,28 @@ export class MultiLangService { logger.info("언어 생성 시작", { languageData }); // 중복 체크 - const duplicateCheckQuery = ` - SELECT lang_code FROM language_master WHERE lang_code = $1 - `; - const duplicateResult = await this.client.query(duplicateCheckQuery, [ - languageData.langCode, - ]); + const existingLanguage = await prisma.language_master.findUnique({ + where: { lang_code: languageData.langCode }, + }); - if (duplicateResult.rows.length > 0) { + if (existingLanguage) { throw new Error( `이미 존재하는 언어 코드입니다: ${languageData.langCode}` ); } // 언어 생성 - const insertQuery = ` - INSERT INTO language_master ( - lang_code, lang_name, lang_native, is_active, sort_order, - created_date, created_by, updated_date, updated_by - ) VALUES ($1, $2, $3, $4, $5, now(), $6, now(), $7) - RETURNING * - `; - - const insertValues = [ - languageData.langCode, - languageData.langName, - languageData.langNative, - languageData.isActive || "Y", - languageData.sortOrder || 0, - languageData.createdBy || "system", - languageData.updatedBy || "system", - ]; - - const result = await this.client.query(insertQuery, insertValues); - const createdLanguage = result.rows[0]; + const createdLanguage = await prisma.language_master.create({ + data: { + lang_code: languageData.langCode, + lang_name: languageData.langName, + lang_native: languageData.langNative, + is_active: languageData.isActive || "Y", + sort_order: languageData.sortOrder || 0, + created_by: languageData.createdBy || "system", + updated_by: languageData.updatedBy || "system", + }, + }); logger.info("언어 생성 완료", { langCode: createdLanguage.lang_code }); @@ -110,16 +101,12 @@ export class MultiLangService { langCode: createdLanguage.lang_code, langName: createdLanguage.lang_name, langNative: createdLanguage.lang_native, - isActive: createdLanguage.is_active, - sortOrder: createdLanguage.sort_order, - createdDate: createdLanguage.created_date - ? new Date(createdLanguage.created_date) - : undefined, - createdBy: createdLanguage.created_by, - updatedDate: createdLanguage.updated_date - ? new Date(createdLanguage.updated_date) - : undefined, - updatedBy: createdLanguage.updated_by, + isActive: createdLanguage.is_active || "N", + sortOrder: createdLanguage.sort_order ?? undefined, + createdDate: createdLanguage.created_date || undefined, + createdBy: createdLanguage.created_by || undefined, + updatedDate: createdLanguage.updated_date || undefined, + updatedBy: createdLanguage.updated_by || undefined, }; } catch (error) { logger.error("언어 생성 중 오류 발생:", error); @@ -140,40 +127,29 @@ export class MultiLangService { logger.info("언어 수정 시작", { langCode, languageData }); // 기존 언어 확인 - const checkQuery = ` - SELECT * FROM language_master WHERE lang_code = $1 - `; - const checkResult = await this.client.query(checkQuery, [langCode]); + const existingLanguage = await prisma.language_master.findUnique({ + where: { lang_code: langCode }, + }); - if (checkResult.rows.length === 0) { + if (!existingLanguage) { throw new Error(`언어를 찾을 수 없습니다: ${langCode}`); } // 언어 수정 - const updateQuery = ` - UPDATE language_master - SET - lang_name = COALESCE($2, lang_name), - lang_native = COALESCE($3, lang_native), - is_active = COALESCE($4, is_active), - sort_order = COALESCE($5, sort_order), - updated_date = now(), - updated_by = $6 - WHERE lang_code = $1 - RETURNING * - `; - - const updateValues = [ - langCode, - languageData.langName, - languageData.langNative, - languageData.isActive, - languageData.sortOrder, - languageData.updatedBy || "system", - ]; - - const result = await this.client.query(updateQuery, updateValues); - const updatedLanguage = result.rows[0]; + const updatedLanguage = await prisma.language_master.update({ + where: { lang_code: langCode }, + data: { + ...(languageData.langName && { lang_name: languageData.langName }), + ...(languageData.langNative && { + lang_native: languageData.langNative, + }), + ...(languageData.isActive && { is_active: languageData.isActive }), + ...(languageData.sortOrder !== undefined && { + sort_order: languageData.sortOrder, + }), + updated_by: languageData.updatedBy || "system", + }, + }); logger.info("언어 수정 완료", { langCode }); @@ -181,16 +157,12 @@ export class MultiLangService { langCode: updatedLanguage.lang_code, langName: updatedLanguage.lang_name, langNative: updatedLanguage.lang_native, - isActive: updatedLanguage.is_active, - sortOrder: updatedLanguage.sort_order, - createdDate: updatedLanguage.created_date - ? new Date(updatedLanguage.created_date) - : undefined, - createdBy: updatedLanguage.created_by, - updatedDate: updatedLanguage.updated_date - ? new Date(updatedLanguage.updated_date) - : undefined, - updatedBy: updatedLanguage.updated_by, + isActive: updatedLanguage.is_active || "N", + sortOrder: updatedLanguage.sort_order ?? undefined, + createdDate: updatedLanguage.created_date || undefined, + createdBy: updatedLanguage.created_by || undefined, + updatedDate: updatedLanguage.updated_date || undefined, + updatedBy: updatedLanguage.updated_by || undefined, }; } catch (error) { logger.error("언어 수정 중 오류 발생:", error); @@ -207,27 +179,26 @@ export class MultiLangService { try { logger.info("언어 상태 토글 시작", { langCode }); - // 현재 상태 조회 - const currentQuery = ` - SELECT is_active FROM language_master WHERE lang_code = $1 - `; - const currentResult = await this.client.query(currentQuery, [langCode]); + // 현재 언어 조회 + const currentLanguage = await prisma.language_master.findUnique({ + where: { lang_code: langCode }, + select: { is_active: true }, + }); - if (currentResult.rows.length === 0) { + if (!currentLanguage) { throw new Error(`언어를 찾을 수 없습니다: ${langCode}`); } - const currentStatus = currentResult.rows[0].is_active; - const newStatus = currentStatus === "Y" ? "N" : "Y"; + const newStatus = currentLanguage.is_active === "Y" ? "N" : "Y"; // 상태 업데이트 - const updateQuery = ` - UPDATE language_master - SET is_active = $2, updated_date = now(), updated_by = 'system' - WHERE lang_code = $1 - `; - - await this.client.query(updateQuery, [langCode, newStatus]); + await prisma.language_master.update({ + where: { lang_code: langCode }, + data: { + is_active: newStatus, + updated_by: "system", + }, + }); const result = newStatus === "Y" ? "활성화" : "비활성화"; logger.info("언어 상태 토글 완료", { langCode, result }); @@ -248,62 +219,63 @@ export class MultiLangService { try { logger.info("다국어 키 목록 조회 시작", { params }); - let query = ` - SELECT - key_id as "keyId", - company_code as "companyCode", - menu_name as "menuName", - lang_key as "langKey", - description, - is_active as "isActive", - created_date as "createdDate", - created_by as "createdBy", - updated_date as "updatedDate", - updated_by as "updatedBy" - FROM multi_lang_key_master - WHERE 1=1 - `; - - const queryParams: any[] = []; - let paramIndex = 1; + const whereConditions: any = {}; // 회사 코드 필터 if (params.companyCode) { - query += ` AND company_code = $${paramIndex}`; - queryParams.push(params.companyCode); - paramIndex++; + whereConditions.company_code = params.companyCode; } // 메뉴 코드 필터 if (params.menuCode) { - query += ` AND menu_name = $${paramIndex}`; - queryParams.push(params.menuCode); - paramIndex++; + whereConditions.menu_name = params.menuCode; } // 검색 조건 if (params.searchText) { - query += ` AND ( - lang_key ILIKE $${paramIndex} OR - description ILIKE $${paramIndex} OR - menu_name ILIKE $${paramIndex} - )`; - queryParams.push(`%${params.searchText}%`); - paramIndex++; + whereConditions.OR = [ + { lang_key: { contains: params.searchText, mode: "insensitive" } }, + { description: { contains: params.searchText, mode: "insensitive" } }, + { menu_name: { contains: params.searchText, mode: "insensitive" } }, + ]; } - // 정렬 - query += ` ORDER BY company_code, menu_name, lang_key`; + const langKeys = await prisma.multi_lang_key_master.findMany({ + where: whereConditions, + orderBy: [ + { company_code: "asc" }, + { menu_name: "asc" }, + { lang_key: "asc" }, + ], + select: { + key_id: true, + company_code: true, + menu_name: true, + lang_key: true, + description: true, + is_active: true, + created_date: true, + created_by: true, + updated_date: true, + updated_by: true, + }, + }); - const result = await this.client.query(query, queryParams); - const langKeys = result.rows.map((row) => ({ - ...row, - createdDate: row.createdDate ? new Date(row.createdDate) : undefined, - updatedDate: row.updatedDate ? new Date(row.updatedDate) : undefined, + const mappedKeys: LangKey[] = langKeys.map((key) => ({ + keyId: key.key_id, + companyCode: key.company_code, + menuName: key.menu_name || undefined, + langKey: key.lang_key, + description: key.description || undefined, + isActive: key.is_active || "Y", + createdDate: key.created_date || undefined, + createdBy: key.created_by || undefined, + updatedDate: key.updated_date || undefined, + updatedBy: key.updated_by || undefined, })); - logger.info(`다국어 키 목록 조회 완료: ${langKeys.length}개`); - return langKeys; + logger.info(`다국어 키 목록 조회 완료: ${mappedKeys.length}개`); + return mappedKeys; } catch (error) { logger.error("다국어 키 목록 조회 중 오류 발생:", error); throw new Error( @@ -319,31 +291,39 @@ export class MultiLangService { try { logger.info("다국어 텍스트 조회 시작", { keyId }); - const query = ` - SELECT - text_id as "textId", - key_id as "keyId", - lang_code as "langCode", - lang_text as "langText", - is_active as "isActive", - created_date as "createdDate", - created_by as "createdBy", - updated_date as "updatedDate", - updated_by as "updatedBy" - FROM multi_lang_text - WHERE key_id = $1 AND is_active = 'Y' - ORDER BY lang_code - `; + const langTexts = await prisma.multi_lang_text.findMany({ + where: { + key_id: keyId, + is_active: "Y", + }, + orderBy: { lang_code: "asc" }, + select: { + text_id: true, + key_id: true, + lang_code: true, + lang_text: true, + is_active: true, + created_date: true, + created_by: true, + updated_date: true, + updated_by: true, + }, + }); - const result = await this.client.query(query, [keyId]); - const langTexts = result.rows.map((row) => ({ - ...row, - createdDate: row.createdDate ? new Date(row.createdDate) : undefined, - updatedDate: row.updatedDate ? new Date(row.updatedDate) : undefined, + const mappedTexts: LangText[] = langTexts.map((text) => ({ + textId: text.text_id, + keyId: text.key_id, + langCode: text.lang_code, + langText: text.lang_text, + isActive: text.is_active || "Y", + createdDate: text.created_date || undefined, + createdBy: text.created_by || undefined, + updatedDate: text.updated_date || undefined, + updatedBy: text.updated_by || undefined, })); - logger.info(`다국어 텍스트 조회 완료: ${langTexts.length}개`); - return langTexts; + logger.info(`다국어 텍스트 조회 완료: ${mappedTexts.length}개`); + return mappedTexts; } catch (error) { logger.error("다국어 텍스트 조회 중 오류 발생:", error); throw new Error( @@ -360,46 +340,38 @@ export class MultiLangService { logger.info("다국어 키 생성 시작", { keyData }); // 중복 체크 - const duplicateCheckQuery = ` - SELECT key_id FROM multi_lang_key_master - WHERE company_code = $1 AND lang_key = $2 - `; - const duplicateResult = await this.client.query(duplicateCheckQuery, [ - keyData.companyCode, - keyData.langKey, - ]); + const existingKey = await prisma.multi_lang_key_master.findFirst({ + where: { + company_code: keyData.companyCode, + lang_key: keyData.langKey, + }, + }); - if (duplicateResult.rows.length > 0) { + if (existingKey) { throw new Error( `동일한 회사에 이미 존재하는 언어키입니다: ${keyData.langKey}` ); } // 다국어 키 생성 - const insertQuery = ` - INSERT INTO multi_lang_key_master ( - company_code, menu_name, lang_key, description, is_active, - created_date, created_by, updated_date, updated_by - ) VALUES ($1, $2, $3, $4, $5, now(), $6, now(), $7) - RETURNING key_id - `; + const createdKey = await prisma.multi_lang_key_master.create({ + data: { + company_code: keyData.companyCode, + menu_name: keyData.menuName || null, + lang_key: keyData.langKey, + description: keyData.description || null, + is_active: keyData.isActive || "Y", + created_by: keyData.createdBy || "system", + updated_by: keyData.updatedBy || "system", + }, + }); - const insertValues = [ - keyData.companyCode, - keyData.menuName || null, - keyData.langKey, - keyData.description || null, - keyData.isActive || "Y", - keyData.createdBy || "system", - keyData.updatedBy || "system", - ]; + logger.info("다국어 키 생성 완료", { + keyId: createdKey.key_id, + langKey: keyData.langKey, + }); - const result = await this.client.query(insertQuery, insertValues); - const keyId = result.rows[0].key_id; - - logger.info("다국어 키 생성 완료", { keyId, langKey: keyData.langKey }); - - return keyId; + return createdKey.key_id; } catch (error) { logger.error("다국어 키 생성 중 오류 발생:", error); throw new Error( @@ -419,28 +391,25 @@ export class MultiLangService { logger.info("다국어 키 수정 시작", { keyId, keyData }); // 기존 키 확인 - const checkQuery = ` - SELECT key_id FROM multi_lang_key_master WHERE key_id = $1 - `; - const checkResult = await this.client.query(checkQuery, [keyId]); + const existingKey = await prisma.multi_lang_key_master.findUnique({ + where: { key_id: keyId }, + }); - if (checkResult.rows.length === 0) { + if (!existingKey) { throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`); } // 중복 체크 (자신을 제외하고) if (keyData.companyCode && keyData.langKey) { - const duplicateCheckQuery = ` - SELECT key_id FROM multi_lang_key_master - WHERE company_code = $1 AND lang_key = $2 AND key_id != $3 - `; - const duplicateResult = await this.client.query(duplicateCheckQuery, [ - keyData.companyCode, - keyData.langKey, - keyId, - ]); + const duplicateKey = await prisma.multi_lang_key_master.findFirst({ + where: { + company_code: keyData.companyCode, + lang_key: keyData.langKey, + key_id: { not: keyId }, + }, + }); - if (duplicateResult.rows.length > 0) { + if (duplicateKey) { throw new Error( `동일한 회사에 이미 존재하는 언어키입니다: ${keyData.langKey}` ); @@ -448,28 +417,20 @@ export class MultiLangService { } // 다국어 키 수정 - const updateQuery = ` - UPDATE multi_lang_key_master - SET - company_code = COALESCE($2, company_code), - menu_name = COALESCE($3, menu_name), - lang_key = COALESCE($4, lang_key), - description = COALESCE($5, description), - updated_date = now(), - updated_by = $6 - WHERE key_id = $1 - `; - - const updateValues = [ - keyId, - keyData.companyCode, - keyData.menuName, - keyData.langKey, - keyData.description, - keyData.updatedBy || "system", - ]; - - await this.client.query(updateQuery, updateValues); + await prisma.multi_lang_key_master.update({ + where: { key_id: keyId }, + data: { + ...(keyData.companyCode && { company_code: keyData.companyCode }), + ...(keyData.menuName !== undefined && { + menu_name: keyData.menuName, + }), + ...(keyData.langKey && { lang_key: keyData.langKey }), + ...(keyData.description !== undefined && { + description: keyData.description, + }), + updated_by: keyData.updatedBy || "system", + }, + }); logger.info("다국어 키 수정 완료", { keyId }); } catch (error) { @@ -488,40 +449,28 @@ export class MultiLangService { logger.info("다국어 키 삭제 시작", { keyId }); // 기존 키 확인 - const checkQuery = ` - SELECT key_id FROM multi_lang_key_master WHERE key_id = $1 - `; - const checkResult = await this.client.query(checkQuery, [keyId]); + const existingKey = await prisma.multi_lang_key_master.findUnique({ + where: { key_id: keyId }, + }); - if (checkResult.rows.length === 0) { + if (!existingKey) { throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`); } - // 트랜잭션 시작 - await this.client.query("BEGIN"); - - try { + // 트랜잭션으로 키와 연관된 텍스트 모두 삭제 + await prisma.$transaction(async (tx) => { // 관련된 다국어 텍스트 삭제 - const deleteTextsQuery = ` - DELETE FROM multi_lang_text WHERE key_id = $1 - `; - await this.client.query(deleteTextsQuery, [keyId]); + await tx.multi_lang_text.deleteMany({ + where: { key_id: keyId }, + }); // 다국어 키 삭제 - const deleteKeyQuery = ` - DELETE FROM multi_lang_key_master WHERE key_id = $1 - `; - await this.client.query(deleteKeyQuery, [keyId]); + await tx.multi_lang_key_master.delete({ + where: { key_id: keyId }, + }); + }); - // 트랜잭션 커밋 - await this.client.query("COMMIT"); - - logger.info("다국어 키 삭제 완료", { keyId }); - } catch (error) { - // 트랜잭션 롤백 - await this.client.query("ROLLBACK"); - throw error; - } + logger.info("다국어 키 삭제 완료", { keyId }); } catch (error) { logger.error("다국어 키 삭제 중 오류 발생:", error); throw new Error( @@ -537,27 +486,26 @@ export class MultiLangService { try { logger.info("다국어 키 상태 토글 시작", { keyId }); - // 현재 상태 조회 - const currentQuery = ` - SELECT is_active FROM multi_lang_key_master WHERE key_id = $1 - `; - const currentResult = await this.client.query(currentQuery, [keyId]); + // 현재 키 조회 + const currentKey = await prisma.multi_lang_key_master.findUnique({ + where: { key_id: keyId }, + select: { is_active: true }, + }); - if (currentResult.rows.length === 0) { + if (!currentKey) { throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`); } - const currentStatus = currentResult.rows[0].is_active; - const newStatus = currentStatus === "Y" ? "N" : "Y"; + const newStatus = currentKey.is_active === "Y" ? "N" : "Y"; // 상태 업데이트 - const updateQuery = ` - UPDATE multi_lang_key_master - SET is_active = $2, updated_date = now(), updated_by = 'system' - WHERE key_id = $1 - `; - - await this.client.query(updateQuery, [keyId, newStatus]); + await prisma.multi_lang_key_master.update({ + where: { key_id: keyId }, + data: { + is_active: newStatus, + updated_by: "system", + }, + }); const result = newStatus === "Y" ? "활성화" : "비활성화"; logger.info("다국어 키 상태 토글 완료", { keyId, result }); @@ -585,58 +533,40 @@ export class MultiLangService { }); // 기존 키 확인 - const checkQuery = ` - SELECT key_id FROM multi_lang_key_master WHERE key_id = $1 - `; - const checkResult = await this.client.query(checkQuery, [keyId]); + const existingKey = await prisma.multi_lang_key_master.findUnique({ + where: { key_id: keyId }, + }); - if (checkResult.rows.length === 0) { + if (!existingKey) { throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`); } - // 트랜잭션 시작 - await this.client.query("BEGIN"); - - try { + // 트랜잭션으로 기존 텍스트 삭제 후 새로 생성 + await prisma.$transaction(async (tx) => { // 기존 텍스트 삭제 - const deleteTextsQuery = ` - DELETE FROM multi_lang_text WHERE key_id = $1 - `; - await this.client.query(deleteTextsQuery, [keyId]); + await tx.multi_lang_text.deleteMany({ + where: { key_id: keyId }, + }); // 새로운 텍스트 삽입 - for (const text of textData.texts) { - const insertTextQuery = ` - INSERT INTO multi_lang_text ( - key_id, lang_code, lang_text, is_active, - created_date, created_by, updated_date, updated_by - ) VALUES ($1, $2, $3, $4, now(), $5, now(), $6) - `; - - const insertValues = [ - keyId, - text.langCode, - text.langText, - text.isActive || "Y", - text.createdBy || "system", - text.updatedBy || "system", - ]; - - await this.client.query(insertTextQuery, insertValues); + if (textData.texts.length > 0) { + await tx.multi_lang_text.createMany({ + data: textData.texts.map((text) => ({ + key_id: keyId, + lang_code: text.langCode, + lang_text: text.langText, + is_active: text.isActive || "Y", + created_by: text.createdBy || "system", + updated_by: text.updatedBy || "system", + })), + }); } + }); - // 트랜잭션 커밋 - await this.client.query("COMMIT"); - - logger.info("다국어 텍스트 저장 완료", { - keyId, - savedCount: textData.texts.length, - }); - } catch (error) { - // 트랜잭션 롤백 - await this.client.query("ROLLBACK"); - throw error; - } + logger.info("다국어 텍스트 저장 완료", { + keyId, + savedCount: textData.texts.length, + }); } catch (error) { logger.error("다국어 텍스트 저장 중 오류 발생:", error); throw new Error( @@ -652,35 +582,33 @@ export class MultiLangService { try { logger.info("사용자별 다국어 텍스트 조회 시작", { params }); - const query = ` - SELECT t.lang_text as "langText" - FROM multi_lang_key_master km - JOIN multi_lang_text t ON km.key_id = t.key_id - WHERE km.company_code = $1 - AND km.menu_name = $2 - AND km.lang_key = $3 - AND t.lang_code = $4 - AND km.is_active = 'Y' - AND t.is_active = 'Y' - LIMIT 1 - `; + const result = await prisma.multi_lang_text.findFirst({ + where: { + lang_code: params.userLang, + is_active: "Y", + multi_lang_key_master: { + company_code: params.companyCode, + menu_name: params.menuCode, + lang_key: params.langKey, + is_active: "Y", + }, + }, + select: { + lang_text: true, + }, + }); - const result = await this.client.query(query, [ - params.companyCode, - params.menuCode, - params.langKey, - params.userLang, - ]); - - if (result.rows.length === 0) { + if (!result) { logger.warn("사용자별 다국어 텍스트를 찾을 수 없음", { params }); return params.langKey; // 기본값으로 키 반환 } - const langText = result.rows[0].langText; - logger.info("사용자별 다국어 텍스트 조회 완료", { params, langText }); + logger.info("사용자별 다국어 텍스트 조회 완료", { + params, + langText: result.lang_text, + }); - return langText; + return result.lang_text; } catch (error) { logger.error("사용자별 다국어 텍스트 조회 중 오류 발생:", error); throw new Error( @@ -704,24 +632,22 @@ export class MultiLangService { langCode, }); - const query = ` - SELECT t.lang_text as "langText" - FROM multi_lang_text t - JOIN multi_lang_key_master k ON t.key_id = k.key_id - WHERE k.company_code = $1 - AND k.lang_key = $2 - AND t.lang_code = $3 - AND t.is_active = 'Y' - AND k.is_active = 'Y' - `; + const result = await prisma.multi_lang_text.findFirst({ + where: { + lang_code: langCode, + is_active: "Y", + multi_lang_key_master: { + company_code: companyCode, + lang_key: langKey, + is_active: "Y", + }, + }, + select: { + lang_text: true, + }, + }); - const result = await this.client.query(query, [ - companyCode, - langKey, - langCode, - ]); - - if (result.rows.length === 0) { + if (!result) { logger.warn("특정 키의 다국어 텍스트를 찾을 수 없음", { companyCode, langKey, @@ -730,15 +656,14 @@ export class MultiLangService { return langKey; // 기본값으로 키 반환 } - const langText = result.rows[0].langText; logger.info("특정 키의 다국어 텍스트 조회 완료", { companyCode, langKey, langCode, - langText, + langText: result.lang_text, }); - return langText; + return result.lang_text; } catch (error) { logger.error("특정 키의 다국어 텍스트 조회 중 오류 발생:", error); throw new Error( @@ -765,69 +690,51 @@ export class MultiLangService { return {}; } - // 모든 키에 대한 마스터 정보를 한번에 조회 - const langKeyMastersQuery = ` - SELECT key_id, lang_key, company_code - FROM multi_lang_key_master - WHERE lang_key = ANY($1::varchar[]) - AND (company_code = $2::varchar OR company_code = '*') - ORDER BY - CASE WHEN company_code = $2::varchar THEN 1 ELSE 2 END, - lang_key, - company_code - `; + // 모든 키에 대한 번역 조회 + const translations = await prisma.multi_lang_text.findMany({ + where: { + lang_code: params.userLang, + is_active: "Y", + multi_lang_key_master: { + lang_key: { in: params.langKeys }, + company_code: { in: [params.companyCode, "*"] }, + is_active: "Y", + }, + }, + select: { + lang_text: true, + multi_lang_key_master: { + select: { + lang_key: true, + company_code: true, + }, + }, + }, + orderBy: { + multi_lang_key_master: { + company_code: "asc", // 회사별 우선, '*' 는 기본값 + }, + }, + }); - const langKeyMasters = await this.client.query(langKeyMastersQuery, [ - params.langKeys, - params.companyCode, - ]); - - if (langKeyMasters.rows.length === 0) { - logger.warn("배치 번역: 언어키 마스터를 찾을 수 없음", { params }); - return this.createDefaultTranslations(params.langKeys); - } - - // 찾은 키들의 ID 목록 - const keyIds = langKeyMasters.rows.map((row) => row.key_id); - const foundKeys = langKeyMasters.rows.map((row) => row.lang_key); - - // 누락된 키들 (기본값으로 설정) - const missingKeys = params.langKeys.filter( - (key) => !foundKeys.includes(key) - ); const result: Record = {}; - // 기본값으로 누락된 키들 설정 - missingKeys.forEach((key) => { + // 기본값으로 모든 키 설정 + params.langKeys.forEach((key) => { result[key] = key; }); - // 실제 번역 텍스트 조회 - if (keyIds.length > 0) { - const textsQuery = ` - SELECT t.key_id, t.lang_text, km.lang_key - FROM multi_lang_text t - JOIN multi_lang_key_master km ON t.key_id = km.key_id - WHERE t.key_id = ANY($1::int[]) - AND t.lang_code = $2 - AND t.is_active = 'Y' - `; - - const texts = await this.client.query(textsQuery, [ - keyIds, - params.userLang, - ]); - - // 결과 매핑 - texts.rows.forEach((row) => { - result[row.lang_key] = row.lang_text; - }); - } + // 실제 번역으로 덮어쓰기 (회사별 우선) + translations.forEach((translation) => { + const langKey = translation.multi_lang_key_master.lang_key; + if (params.langKeys.includes(langKey)) { + result[langKey] = translation.lang_text; + } + }); logger.info("배치 번역 조회 완료", { totalKeys: params.langKeys.length, - foundKeys: foundKeys.length, - missingKeys: missingKeys.length, + foundTranslations: translations.length, resultKeys: Object.keys(result).length, }); @@ -848,46 +755,32 @@ export class MultiLangService { logger.info("언어 삭제 시작", { langCode }); // 기존 언어 확인 - const checkQuery = ` - SELECT lang_code FROM language_master WHERE lang_code = $1 - `; - const checkResult = await this.client.query(checkQuery, [langCode]); + const existingLanguage = await prisma.language_master.findUnique({ + where: { lang_code: langCode }, + }); - if (checkResult.rows.length === 0) { + if (!existingLanguage) { throw new Error(`언어를 찾을 수 없습니다: ${langCode}`); } - // 트랜잭션 시작 - await this.client.query("BEGIN"); + // 트랜잭션으로 언어와 관련 텍스트 삭제 + await prisma.$transaction(async (tx) => { + // 해당 언어의 다국어 텍스트 삭제 + const deleteResult = await tx.multi_lang_text.deleteMany({ + where: { lang_code: langCode }, + }); - try { - // 해당 언어의 다국어 텍스트만 삭제 - const deleteTextsQuery = ` - DELETE FROM multi_lang_text - WHERE lang_code = $1 - `; - const deleteResult = await this.client.query(deleteTextsQuery, [ - langCode, - ]); - logger.info(`삭제된 다국어 텍스트 수: ${deleteResult.rowCount}`, { + logger.info(`삭제된 다국어 텍스트 수: ${deleteResult.count}`, { langCode, }); // 언어 마스터 삭제 - const deleteLanguageQuery = ` - DELETE FROM language_master WHERE lang_code = $1 - `; - await this.client.query(deleteLanguageQuery, [langCode]); + await tx.language_master.delete({ + where: { lang_code: langCode }, + }); + }); - // 트랜잭션 커밋 - await this.client.query("COMMIT"); - - logger.info("언어 삭제 완료", { langCode }); - } catch (error) { - // 트랜잭션 롤백 - await this.client.query("ROLLBACK"); - throw error; - } + logger.info("언어 삭제 완료", { langCode }); } catch (error) { logger.error("언어 삭제 중 오류 발생:", error); throw new Error( @@ -895,17 +788,4 @@ export class MultiLangService { ); } } - - /** - * 기본 번역 생성 (키를 그대로 반환) - */ - private createDefaultTranslations( - langKeys: string[] - ): Record { - const result: Record = {}; - langKeys.forEach((key) => { - result[key] = key; - }); - return result; - } } diff --git a/backend-node/src/services/tableManagementService.ts b/backend-node/src/services/tableManagementService.ts index 863ab691..6c4cada4 100644 --- a/backend-node/src/services/tableManagementService.ts +++ b/backend-node/src/services/tableManagementService.ts @@ -1,4 +1,4 @@ -import { Client } from "pg"; +import { PrismaClient } from "@prisma/client"; import { logger } from "../utils/logger"; import { TableInfo, @@ -8,21 +8,21 @@ import { ColumnLabels, } from "../types/tableManagement"; -export class TableManagementService { - private client: Client; +const prisma = new PrismaClient(); - constructor(client: Client) { - this.client = client; - } +export class TableManagementService { + constructor() {} /** * 테이블 목록 조회 (PostgreSQL information_schema 활용) + * 메타데이터 조회는 Prisma로 변경 불가 */ async getTableList(): Promise { try { logger.info("테이블 목록 조회 시작"); - const query = ` + // information_schema는 여전히 $queryRaw 사용 + const tables = await prisma.$queryRaw` SELECT t.table_name as "tableName", COALESCE(tl.table_label, t.table_name) as "displayName", @@ -38,10 +38,8 @@ export class TableManagementService { ORDER BY t.table_name `; - const result = await this.client.query(query); - logger.info(`테이블 목록 조회 완료: ${result.rows.length}개`); - - return result.rows; + logger.info(`테이블 목록 조회 완료: ${tables.length}개`); + return tables; } catch (error) { logger.error("테이블 목록 조회 중 오류 발생:", error); throw new Error( @@ -52,12 +50,14 @@ export class TableManagementService { /** * 테이블 컬럼 정보 조회 + * 메타데이터 조회는 Prisma로 변경 불가 */ async getColumnList(tableName: string): Promise { try { logger.info(`컬럼 정보 조회 시작: ${tableName}`); - const query = ` + // information_schema는 여전히 $queryRaw 사용 + const columns = await prisma.$queryRaw` SELECT c.column_name as "columnName", COALESCE(cl.column_label, c.column_name) as "displayName", @@ -78,14 +78,12 @@ export class TableManagementService { cl.is_visible as "isVisible" FROM information_schema.columns c LEFT JOIN column_labels cl ON c.table_name = cl.table_name AND c.column_name = cl.column_name - WHERE c.table_name = $1 + WHERE c.table_name = ${tableName} ORDER BY c.ordinal_position `; - const result = await this.client.query(query, [tableName]); - logger.info(`컬럼 정보 조회 완료: ${tableName}, ${result.rows.length}개`); - - return result.rows; + logger.info(`컬럼 정보 조회 완료: ${tableName}, ${columns.length}개`); + return columns; } catch (error) { logger.error(`컬럼 정보 조회 중 오류 발생: ${tableName}`, error); throw new Error( @@ -96,18 +94,22 @@ export class TableManagementService { /** * 테이블이 table_labels에 없으면 자동 추가 + * Prisma ORM으로 변경 */ async insertTableIfNotExists(tableName: string): Promise { try { logger.info(`테이블 라벨 자동 추가 시작: ${tableName}`); - const query = ` - INSERT INTO table_labels (table_name, table_label, description) - VALUES ($1, $1, '') - ON CONFLICT (table_name) DO NOTHING - `; + await prisma.table_labels.upsert({ + where: { table_name: tableName }, + update: {}, // 이미 존재하면 변경하지 않음 + create: { + table_name: tableName, + table_label: tableName, + description: "", + }, + }); - await this.client.query(query, [tableName]); logger.info(`테이블 라벨 자동 추가 완료: ${tableName}`); } catch (error) { logger.error(`테이블 라벨 자동 추가 중 오류 발생: ${tableName}`, error); @@ -119,6 +121,7 @@ export class TableManagementService { /** * 컬럼 설정 업데이트 (UPSERT 방식) + * Prisma ORM으로 변경 */ async updateColumnSettings( tableName: string, @@ -131,38 +134,42 @@ export class TableManagementService { // 테이블이 table_labels에 없으면 자동 추가 await this.insertTableIfNotExists(tableName); - const query = ` - INSERT INTO column_labels ( - table_name, column_name, column_label, web_type, - detail_settings, code_category, code_value, - reference_table, reference_column, display_order, is_visible - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) - ON CONFLICT (table_name, column_name) DO UPDATE SET - column_label = EXCLUDED.column_label, - web_type = EXCLUDED.web_type, - detail_settings = EXCLUDED.detail_settings, - code_category = EXCLUDED.code_category, - code_value = EXCLUDED.code_value, - reference_table = EXCLUDED.reference_table, - reference_column = EXCLUDED.reference_column, - display_order = EXCLUDED.display_order, - is_visible = EXCLUDED.is_visible, - updated_date = now() - `; - - await this.client.query(query, [ - tableName, - columnName, - settings.columnLabel, - settings.webType, - settings.detailSettings, - settings.codeCategory, - settings.codeValue, - settings.referenceTable, - settings.referenceColumn, - settings.displayOrder || 0, - settings.isVisible !== undefined ? settings.isVisible : true, - ]); + // column_labels 업데이트 또는 생성 + await prisma.column_labels.upsert({ + where: { + table_name_column_name: { + table_name: tableName, + column_name: columnName, + }, + }, + update: { + column_label: settings.columnLabel, + web_type: settings.webType, + detail_settings: settings.detailSettings, + code_category: settings.codeCategory, + code_value: settings.codeValue, + reference_table: settings.referenceTable, + reference_column: settings.referenceColumn, + display_order: settings.displayOrder || 0, + is_visible: + settings.isVisible !== undefined ? settings.isVisible : true, + updated_date: new Date(), + }, + create: { + table_name: tableName, + column_name: columnName, + column_label: settings.columnLabel, + web_type: settings.webType, + detail_settings: settings.detailSettings, + code_category: settings.codeCategory, + code_value: settings.codeValue, + reference_table: settings.referenceTable, + reference_column: settings.referenceColumn, + display_order: settings.displayOrder || 0, + is_visible: + settings.isVisible !== undefined ? settings.isVisible : true, + }, + }); logger.info(`컬럼 설정 업데이트 완료: ${tableName}.${columnName}`); } catch (error) { @@ -178,6 +185,7 @@ export class TableManagementService { /** * 전체 컬럼 설정 일괄 업데이트 + * Prisma 트랜잭션으로 변경 */ async updateAllColumnSettings( tableName: string, @@ -188,10 +196,8 @@ export class TableManagementService { `전체 컬럼 설정 일괄 업데이트 시작: ${tableName}, ${columnSettings.length}개` ); - // 트랜잭션 시작 - await this.client.query("BEGIN"); - - try { + // Prisma 트랜잭션 사용 + await prisma.$transaction(async (tx) => { // 테이블이 table_labels에 없으면 자동 추가 await this.insertTableIfNotExists(tableName); @@ -207,15 +213,9 @@ export class TableManagementService { ); } } + }); - // 트랜잭션 커밋 - await this.client.query("COMMIT"); - logger.info(`전체 컬럼 설정 일괄 업데이트 완료: ${tableName}`); - } catch (error) { - // 트랜잭션 롤백 - await this.client.query("ROLLBACK"); - throw error; - } + logger.info(`전체 컬럼 설정 일괄 업데이트 완료: ${tableName}`); } catch (error) { logger.error( `전체 컬럼 설정 일괄 업데이트 중 오류 발생: ${tableName}`, @@ -229,30 +229,37 @@ export class TableManagementService { /** * 테이블 라벨 정보 조회 + * Prisma ORM으로 변경 */ async getTableLabels(tableName: string): Promise { try { logger.info(`테이블 라벨 정보 조회 시작: ${tableName}`); - const query = ` - SELECT - table_name as "tableName", - table_label as "tableLabel", - description, - created_date as "createdDate", - updated_date as "updatedDate" - FROM table_labels - WHERE table_name = $1 - `; + const tableLabel = await prisma.table_labels.findUnique({ + where: { table_name: tableName }, + select: { + table_name: true, + table_label: true, + description: true, + created_date: true, + updated_date: true, + }, + }); - const result = await this.client.query(query, [tableName]); - - if (result.rows.length === 0) { + if (!tableLabel) { return null; } + const result: TableLabels = { + tableName: tableLabel.table_name, + tableLabel: tableLabel.table_label || undefined, + description: tableLabel.description || undefined, + createdDate: tableLabel.created_date || undefined, + updatedDate: tableLabel.updated_date || undefined, + }; + logger.info(`테이블 라벨 정보 조회 완료: ${tableName}`); - return result.rows[0]; + return result; } catch (error) { logger.error(`테이블 라벨 정보 조회 중 오류 발생: ${tableName}`, error); throw new Error( @@ -263,6 +270,7 @@ export class TableManagementService { /** * 컬럼 라벨 정보 조회 + * Prisma ORM으로 변경 */ async getColumnLabels( tableName: string, @@ -271,35 +279,56 @@ export class TableManagementService { try { logger.info(`컬럼 라벨 정보 조회 시작: ${tableName}.${columnName}`); - const query = ` - SELECT - id, - table_name as "tableName", - column_name as "columnName", - column_label as "columnLabel", - web_type as "webType", - detail_settings as "detailSettings", - description, - display_order as "displayOrder", - is_visible as "isVisible", - code_category as "codeCategory", - code_value as "codeValue", - reference_table as "referenceTable", - reference_column as "referenceColumn", - created_date as "createdDate", - updated_date as "updatedDate" - FROM column_labels - WHERE table_name = $1 AND column_name = $2 - `; + const columnLabel = await prisma.column_labels.findUnique({ + where: { + table_name_column_name: { + table_name: tableName, + column_name: columnName, + }, + }, + select: { + id: true, + table_name: true, + column_name: true, + column_label: true, + web_type: true, + detail_settings: true, + description: true, + display_order: true, + is_visible: true, + code_category: true, + code_value: true, + reference_table: true, + reference_column: true, + created_date: true, + updated_date: true, + }, + }); - const result = await this.client.query(query, [tableName, columnName]); - - if (result.rows.length === 0) { + if (!columnLabel) { return null; } + const result: ColumnLabels = { + id: columnLabel.id, + tableName: columnLabel.table_name || "", + columnName: columnLabel.column_name || "", + columnLabel: columnLabel.column_label || undefined, + webType: columnLabel.web_type || undefined, + detailSettings: columnLabel.detail_settings || undefined, + description: columnLabel.description || undefined, + displayOrder: columnLabel.display_order || undefined, + isVisible: columnLabel.is_visible || undefined, + codeCategory: columnLabel.code_category || undefined, + codeValue: columnLabel.code_value || undefined, + referenceTable: columnLabel.reference_table || undefined, + referenceColumn: columnLabel.reference_column || undefined, + createdDate: columnLabel.created_date || undefined, + updatedDate: columnLabel.updated_date || undefined, + }; + logger.info(`컬럼 라벨 정보 조회 완료: ${tableName}.${columnName}`); - return result.rows[0]; + return result; } catch (error) { logger.error( `컬럼 라벨 정보 조회 중 오류 발생: ${tableName}.${columnName}`,