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 { AdminService } from "../services/adminService"; import { EncryptUtil } from "../utils/encryptUtil"; /** * 관리자 메뉴 목록 조회 */ export async function getAdminMenus( req: AuthenticatedRequest, res: Response ): Promise { try { logger.info("=== 관리자 메뉴 목록 조회 시작 ==="); // 현재 로그인한 사용자의 회사 코드와 로케일 가져오기 const userCompanyCode = req.user?.companyCode || "ILSHIN"; const userLang = (req.query.userLang as string) || "ko"; logger.info(`사용자 회사 코드: ${userCompanyCode}`); logger.info(`사용자 로케일: ${userLang}`); const paramMap = { userCompanyCode, userLang, SYSTEM_NAME: "PLM", }; const menuList = await AdminService.getAdminMenuList(paramMap); logger.info(`관리자 메뉴 조회 결과: ${menuList.length}개`); if (menuList.length > 0) { logger.info("첫 번째 메뉴:", menuList[0]); } const response: ApiResponse = { success: true, message: "관리자 메뉴 목록 조회 성공", data: menuList, }; res.status(200).json(response); } catch (error) { logger.error("관리자 메뉴 목록 조회 중 오류 발생:", error); const response: ApiResponse = { success: false, message: "관리자 메뉴 목록 조회 중 오류가 발생했습니다.", error: { code: "ADMIN_MENU_LIST_ERROR", details: error instanceof Error ? error.message : "Unknown error", }, }; res.status(500).json(response); } } /** * 사용자 메뉴 목록 조회 */ export async function getUserMenus( req: AuthenticatedRequest, res: Response ): Promise { try { logger.info("=== 사용자 메뉴 목록 조회 시작 ==="); // 현재 로그인한 사용자의 회사 코드와 로케일 가져오기 const userCompanyCode = req.user?.companyCode || "ILSHIN"; const userLang = (req.query.userLang as string) || "ko"; logger.info(`사용자 회사 코드: ${userCompanyCode}`); logger.info(`사용자 로케일: ${userLang}`); const paramMap = { userCompanyCode, userLang, SYSTEM_NAME: "PLM", }; const menuList = await AdminService.getUserMenuList(paramMap); logger.info(`사용자 메뉴 조회 결과: ${menuList.length}개`); if (menuList.length > 0) { logger.info("첫 번째 메뉴:", menuList[0]); } const response: ApiResponse = { success: true, message: "사용자 메뉴 목록 조회 성공", data: menuList, }; res.status(200).json(response); } catch (error) { logger.error("사용자 메뉴 목록 조회 중 오류 발생:", error); const response: ApiResponse = { success: false, message: "사용자 메뉴 목록 조회 중 오류가 발생했습니다.", error: { code: "USER_MENU_LIST_ERROR", details: error instanceof Error ? error.message : "Unknown error", }, }; res.status(500).json(response); } } /** * 메뉴 정보 조회 */ export async function getMenuInfo( req: AuthenticatedRequest, res: Response ): Promise { try { const { menuId } = req.params; logger.info(`=== 메뉴 정보 조회 시작 - menuId: ${menuId} ===`); const menuInfo = await AdminService.getMenuInfo(menuId); if (!menuInfo) { const response: ApiResponse = { success: false, message: "메뉴를 찾을 수 없습니다.", error: { code: "MENU_NOT_FOUND", details: `Menu ID: ${menuId}`, }, }; res.status(404).json(response); return; } const response: ApiResponse = { success: true, message: "메뉴 정보 조회 성공", data: menuInfo, }; res.status(200).json(response); } catch (error) { logger.error("메뉴 정보 조회 중 오류 발생:", error); const response: ApiResponse = { success: false, message: "메뉴 정보 조회 중 오류가 발생했습니다.", error: { code: "MENU_INFO_ERROR", details: error instanceof Error ? error.message : "Unknown error", }, }; res.status(500).json(response); } } /** * GET /api/admin/users * 사용자 목록 조회 API * 기존 Java AdminController.getUserList() 포팅 */ export const getUserList = async (req: AuthenticatedRequest, res: Response) => { try { logger.info("사용자 목록 조회 요청", { query: req.query, user: req.user, }); const { page = 1, countPerPage = 20, search, deptCode, status } = req.query; // PostgreSQL 클라이언트 생성 const client = new Client({ connectionString: process.env.DATABASE_URL || "postgresql://postgres:postgres@localhost:5432/ilshin", }); await client.connect(); 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 `; const queryParams: any[] = []; let paramIndex = 1; // 검색 조건 추가 if (search) { query += ` AND ( u.user_name ILIKE $${paramIndex} OR u.user_id ILIKE $${paramIndex} OR u.sabun ILIKE $${paramIndex} OR u.email ILIKE $${paramIndex} )`; queryParams.push(`%${search}%`); paramIndex++; } // 부서 코드 필터 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 countQuery = ` SELECT COUNT(*) as total FROM user_info u WHERE 1=1 ${ search ? `AND ( u.user_name ILIKE $1 OR u.user_id ILIKE $1 OR u.sabun ILIKE $1 OR u.email ILIKE $1 )` : "" } ${deptCode ? `AND u.dept_code = $${search ? 2 : 1}` : ""} ${status ? `AND u.status = $${search ? (deptCode ? 3 : 2) : deptCode ? 2 : 1}` : ""} `; const countParams = queryParams.slice(0, -2); // 페이징 파라미터 제외 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: { users: processedUsers, pagination: { currentPage: Number(page), countPerPage: Number(countPerPage), totalCount: totalCount, totalPages: Math.ceil(totalCount / Number(countPerPage)), }, }, message: "사용자 목록 조회 성공", }; logger.info("사용자 목록 조회 성공", { totalCount, returnedCount: processedUsers.length, currentPage: Number(page), countPerPage: Number(countPerPage), }); res.status(200).json(response); } finally { await client.end(); } } catch (error) { logger.error("사용자 목록 조회 실패", { error }); res.status(500).json({ success: false, message: "사용자 목록 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } }; /** * GET /api/admin/user-locale * 사용자 로케일 조회 API */ export const getUserLocale = async ( req: AuthenticatedRequest, res: Response ): Promise => { try { logger.info("사용자 로케일 조회 요청", { query: req.query, user: req.user, }); if (!req.user?.userId) { res.status(400).json({ success: false, message: "사용자 정보가 없습니다.", }); return; } // 데이터베이스에서 사용자 로케일 조회 const prisma = (await import("../config/database")).default; const userInfo = await prisma.user_info.findFirst({ where: { user_id: req.user.userId, }, select: { locale: true, }, }); let userLocale = "en"; // 기본값 if (userInfo?.locale) { userLocale = userInfo.locale; logger.info("데이터베이스에서 사용자 로케일 조회 성공", { userId: req.user.userId, locale: userLocale, }); } else { logger.info("사용자 로케일이 설정되지 않음, 기본값 사용", { userId: req.user.userId, defaultLocale: userLocale, }); } const response = { success: true, data: userLocale, message: "사용자 로케일 조회 성공", }; logger.info("사용자 로케일 조회 성공", { userLocale, userId: req.user.userId, fromDatabase: !!userInfo?.locale, }); res.status(200).json(response); } catch (error) { logger.error("사용자 로케일 조회 실패", { error }); res.status(500).json({ success: false, message: "사용자 로케일 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } }; /** * 사용자 로케일 설정 */ export const setUserLocale = async ( req: AuthenticatedRequest, res: Response ): Promise => { try { logger.info("사용자 로케일 설정 요청", { body: req.body, user: req.user, }); if (!req.user?.userId) { res.status(400).json({ success: false, message: "사용자 정보가 없습니다.", }); return; } const { locale } = req.body; if (!locale || !["ko", "en", "ja", "zh"].includes(locale)) { res.status(400).json({ success: false, message: "유효하지 않은 로케일입니다. (ko, en, ja, zh 중 선택)", }); return; } // 데이터베이스에 사용자 로케일 저장 const prisma = (await import("../config/database")).default; await prisma.user_info.update({ where: { user_id: req.user.userId, }, data: { locale: locale, }, }); logger.info("사용자 로케일을 데이터베이스에 저장 완료", { locale, userId: req.user.userId, }); const response = { success: true, data: locale, message: "사용자 로케일 설정 성공", }; logger.info("사용자 로케일 설정 성공", { locale, userId: req.user.userId, }); res.status(200).json(response); } catch (error) { logger.error("사용자 로케일 설정 실패", { error }); res.status(500).json({ success: false, message: "사용자 로케일 설정 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } }; /** * GET /api/admin/companies * 회사 목록 조회 API * 기존 Java AdminController의 회사 목록 조회 기능 포팅 */ export const getCompanyList = async ( req: AuthenticatedRequest, res: Response ) => { try { logger.info("회사 목록 조회 요청", { query: req.query, user: req.user, }); // PostgreSQL 클라이언트 생성 const client = new Client({ connectionString: process.env.DATABASE_URL || "postgresql://postgres:postgres@localhost:5432/ilshin", }); await client.connect(); try { // 회사 목록 조회 쿼리 const query = ` SELECT company_code, company_name, status, writer, regdate, 'company' as data_type FROM company_mng WHERE status = 'active' OR status IS NULL ORDER BY company_name `; const result = await client.query(query); const companies = result.rows; // 프론트엔드에서 기대하는 응답 형식으로 변환 const response = { success: true, data: companies.map((company) => ({ company_code: company.company_code, company_name: company.company_name, status: company.status || "active", writer: company.writer, regdate: company.regdate ? company.regdate.toISOString() : new Date().toISOString(), data_type: company.data_type, })), message: "회사 목록 조회 성공", }; logger.info("회사 목록 조회 성공", { totalCount: companies.length, companies: companies.map((c) => ({ code: c.company_code, name: c.company_name, })), }); res.status(200).json(response); } finally { await client.end(); } } catch (error) { logger.error("회사 목록 조회 실패", { error }); res.status(500).json({ success: false, message: "회사 목록 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } }; /** * 다국어 언어 목록 조회 (더미 데이터) */ export async function getLanguageList( req: AuthenticatedRequest, res: Response ): Promise { try { logger.info("다국어 언어 목록 조회 요청"); // 더미 데이터 반환 const languages = [ { langCode: "KR", langName: "한국어", langNative: "한국어", isActive: "Y", }, { langCode: "EN", langName: "English", langNative: "English", isActive: "Y", }, { langCode: "JP", langName: "日本語", langNative: "日本語", isActive: "Y", }, { langCode: "CN", langName: "中文", langNative: "中文", isActive: "Y" }, ]; const response: ApiResponse = { success: true, message: "언어 목록 조회 성공", data: languages, }; res.status(200).json(response); } catch (error) { logger.error("언어 목록 조회 실패:", error); res.status(500).json({ success: false, message: "언어 목록 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 다국어 키 목록 조회 (더미 데이터) */ export async function getLangKeyList( req: AuthenticatedRequest, res: Response ): Promise { try { logger.info("다국어 키 목록 조회 요청", { query: req.query, user: req.user, }); // 더미 데이터 반환 const langKeys = [ { keyId: 1, companyCode: "ILSHIN", menuName: "사용자 관리", langKey: "user.management.title", description: "사용자 관리 페이지 제목", isActive: "Y", createdDate: new Date().toISOString(), createdBy: "admin", updatedDate: new Date().toISOString(), updatedBy: "admin", }, { keyId: 2, companyCode: "ILSHIN", menuName: "메뉴 관리", langKey: "menu.management.title", description: "메뉴 관리 페이지 제목", isActive: "Y", createdDate: new Date().toISOString(), createdBy: "admin", updatedDate: new Date().toISOString(), updatedBy: "admin", }, { keyId: 3, companyCode: "HUTECH", menuName: "대시보드", langKey: "dashboard.title", description: "대시보드 페이지 제목", isActive: "Y", createdDate: new Date().toISOString(), createdBy: "admin", updatedDate: new Date().toISOString(), updatedBy: "admin", }, ]; // 프론트엔드에서 기대하는 응답 형식으로 변환 const response: ApiResponse = { success: true, message: "다국어 키 목록 조회 성공", data: langKeys, }; logger.info("다국어 키 목록 조회 성공", { totalCount: langKeys.length, response: response, }); res.status(200).json(response); } catch (error) { logger.error("다국어 키 목록 조회 실패:", error); res.status(500).json({ success: false, message: "다국어 키 목록 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 다국어 텍스트 목록 조회 (더미 데이터) */ export async function getLangTextList( req: AuthenticatedRequest, res: Response ): Promise { try { const { keyId } = req.params; logger.info(`다국어 텍스트 목록 조회 요청: keyId = ${keyId}`); // 더미 데이터 반환 const langTexts = [ { textId: 1, keyId: parseInt(keyId), langCode: "KR", langText: "사용자 관리", isActive: "Y", }, { textId: 2, keyId: parseInt(keyId), langCode: "EN", langText: "User Management", isActive: "Y", }, { textId: 3, keyId: parseInt(keyId), langCode: "JP", langText: "ユーザー管理", isActive: "Y", }, ]; const response: ApiResponse = { success: true, message: "다국어 텍스트 목록 조회 성공", data: langTexts, }; res.status(200).json(response); } catch (error) { logger.error("다국어 텍스트 목록 조회 실패:", error); res.status(500).json({ success: false, message: "다국어 텍스트 목록 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 다국어 텍스트 저장 (더미 데이터) */ export async function saveLangTexts( req: AuthenticatedRequest, res: Response ): Promise { try { const { keyId } = req.params; const textData = req.body; logger.info(`다국어 텍스트 저장 요청: keyId = ${keyId}`, { textData }); // 더미 응답 const response: ApiResponse = { success: true, message: "다국어 텍스트 저장 성공", data: { savedCount: textData.length }, }; res.status(200).json(response); } catch (error) { logger.error("다국어 텍스트 저장 실패:", error); res.status(500).json({ success: false, message: "다국어 텍스트 저장 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 다국어 키 저장 (더미 데이터) */ export async function saveLangKey( req: AuthenticatedRequest, res: Response ): Promise { try { const keyData = req.body; logger.info("다국어 키 저장 요청", { keyData }); // 더미 응답 const response: ApiResponse = { success: true, message: "다국어 키 저장 성공", data: { keyId: Math.floor(Math.random() * 1000) + 1 }, }; res.status(200).json(response); } catch (error) { logger.error("다국어 키 저장 실패:", error); res.status(500).json({ success: false, message: "다국어 키 저장 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 다국어 키 수정 (더미 데이터) */ export async function updateLangKey( req: AuthenticatedRequest, res: Response ): Promise { try { const { keyId } = req.params; const keyData = req.body; logger.info(`다국어 키 수정 요청: keyId = ${keyId}`, { keyData }); // 더미 응답 const response: ApiResponse = { success: true, message: "다국어 키 수정 성공", data: { keyId: parseInt(keyId) }, }; res.status(200).json(response); } catch (error) { logger.error("다국어 키 수정 실패:", error); res.status(500).json({ success: false, message: "다국어 키 수정 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 다국어 키 삭제 (더미 데이터) */ export async function deleteLangKey( req: AuthenticatedRequest, res: Response ): Promise { try { const { keyId } = req.params; logger.info(`다국어 키 삭제 요청: keyId = ${keyId}`); // 더미 응답 const response: ApiResponse = { success: true, message: "다국어 키 삭제 성공", data: { deletedKeyId: parseInt(keyId) }, }; res.status(200).json(response); } catch (error) { logger.error("다국어 키 삭제 실패:", error); res.status(500).json({ success: false, message: "다국어 키 삭제 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 다국어 키 상태 토글 (더미 데이터) */ export async function toggleLangKeyStatus( req: AuthenticatedRequest, res: Response ): Promise { try { const { keyId } = req.params; logger.info(`다국어 키 상태 토글 요청: keyId = ${keyId}`); // 더미 응답 const response: ApiResponse = { success: true, message: "다국어 키 상태 토글 성공", data: "활성화", }; res.status(200).json(response); } catch (error) { logger.error("다국어 키 상태 토글 실패:", error); res.status(500).json({ success: false, message: "다국어 키 상태 토글 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 언어 저장 (더미 데이터) */ export async function saveLanguage( req: AuthenticatedRequest, res: Response ): Promise { try { const langData = req.body; logger.info("언어 저장 요청", { langData }); // 더미 응답 const response: ApiResponse = { success: true, message: "언어 저장 성공", data: { langCode: langData.langCode }, }; res.status(200).json(response); } catch (error) { logger.error("언어 저장 실패:", error); res.status(500).json({ success: false, message: "언어 저장 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 언어 수정 (더미 데이터) */ export async function updateLanguage( req: AuthenticatedRequest, res: Response ): Promise { try { const { langCode } = req.params; const langData = req.body; logger.info(`언어 수정 요청: langCode = ${langCode}`, { langData }); // 더미 응답 const response: ApiResponse = { success: true, message: "언어 수정 성공", data: { langCode }, }; res.status(200).json(response); } catch (error) { logger.error("언어 수정 실패:", error); res.status(500).json({ success: false, message: "언어 수정 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 언어 상태 토글 (더미 데이터) */ export async function toggleLanguageStatus( req: AuthenticatedRequest, res: Response ): Promise { try { const { langCode } = req.params; logger.info(`언어 상태 토글 요청: langCode = ${langCode}`); // 더미 응답 const response: ApiResponse = { success: true, message: "언어 상태 토글 성공", data: "활성화", }; res.status(200).json(response); } catch (error) { logger.error("언어 상태 토글 실패:", error); res.status(500).json({ success: false, message: "언어 상태 토글 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 메뉴 저장 (추가/수정) */ export async function saveMenu( req: AuthenticatedRequest, res: Response ): Promise { try { const menuData = req.body; logger.info("메뉴 저장 요청", { menuData, user: req.user }); // PostgreSQL 클라이언트 생성 const client = new Client({ connectionString: process.env.DATABASE_URL || "postgresql://postgres:postgres@localhost:5432/ilshin", }); await client.connect(); // 실제 데이터베이스에 저장 const query = ` INSERT INTO menu_info ( objid, menu_type, parent_obj_id, menu_name_kor, menu_name_eng, seq, menu_url, menu_desc, writer, regdate, status, system_name, company_code, lang_key, lang_key_desc ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15 ) RETURNING * `; const values = [ Date.now(), // objid menuData.menuType || null, // menu_type menuData.parentObjId || null, // parent_obj_id menuData.menuNameKor, // menu_name_kor menuData.menuNameEng || null, // menu_name_eng menuData.seq || null, // seq menuData.menuUrl || null, // menu_url menuData.menuDesc || null, // menu_desc req.user?.userId || "admin", // writer new Date(), // regdate menuData.status || "active", // status menuData.systemName || "PLM", // system_name menuData.companyCode || "*", // company_code menuData.langKey || null, // lang_key menuData.langKeyDesc || null, // lang_key_desc ]; const result = await client.query(query, values); const savedMenu = result.rows[0]; await client.end(); logger.info("메뉴 저장 성공", { savedMenu }); const response: ApiResponse = { success: true, message: "메뉴가 성공적으로 저장되었습니다.", data: { objid: savedMenu.objid, menuNameKor: savedMenu.menu_name_kor, menuNameEng: savedMenu.menu_name_eng, menuUrl: savedMenu.menu_url, menuDesc: savedMenu.menu_desc, status: savedMenu.status, writer: savedMenu.writer, regdate: savedMenu.regdate, }, }; res.status(200).json(response); } catch (error) { logger.error("메뉴 저장 실패:", error); res.status(500).json({ success: false, message: "메뉴 저장 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 메뉴 수정 */ export async function updateMenu( req: AuthenticatedRequest, res: Response ): Promise { try { const { menuId } = req.params; const menuData = req.body; logger.info(`메뉴 수정 요청: menuId = ${menuId}`, { menuData, user: req.user, }); // PostgreSQL 클라이언트 생성 const client = new Client({ connectionString: process.env.DATABASE_URL || "postgresql://postgres:postgres@localhost:5432/ilshin", }); await client.connect(); // 실제 데이터베이스에서 메뉴 수정 const query = ` UPDATE menu_info SET menu_type = $1, parent_obj_id = $2, menu_name_kor = $3, menu_name_eng = $4, seq = $5, menu_url = $6, menu_desc = $7, status = $8, system_name = $9, company_code = $10, lang_key = $11, lang_key_desc = $12 WHERE objid = $13 RETURNING * `; const values = [ menuData.menuType ? BigInt(menuData.menuType) : null, // menu_type menuData.parentObjId ? BigInt(menuData.parentObjId) : null, // parent_obj_id menuData.menuNameKor, // menu_name_kor menuData.menuNameEng || null, // menu_name_eng menuData.seq ? BigInt(menuData.seq) : null, // seq menuData.menuUrl || null, // menu_url menuData.menuDesc || null, // menu_desc menuData.status || "active", // status menuData.systemName || "PLM", // system_name menuData.companyCode || "*", // company_code menuData.langKey || null, // lang_key menuData.langKeyDesc || null, // lang_key_desc BigInt(menuId), // objid (WHERE 조건) ]; const result = await client.query(query, values); if (result.rowCount === 0) { await client.end(); res.status(404).json({ success: false, message: "수정할 메뉴를 찾을 수 없습니다.", }); return; } const updatedMenu = result.rows[0]; await client.end(); logger.info("메뉴 수정 성공", { updatedMenu }); const response: ApiResponse = { success: true, message: "메뉴가 성공적으로 수정되었습니다.", data: { objid: updatedMenu.objid.toString(), menuNameKor: updatedMenu.menu_name_kor, menuNameEng: updatedMenu.menu_name_eng, menuUrl: updatedMenu.menu_url, menuDesc: updatedMenu.menu_desc, status: updatedMenu.status, writer: updatedMenu.writer, regdate: updatedMenu.regdate, }, }; res.status(200).json(response); } catch (error) { logger.error("메뉴 수정 실패:", error); res.status(500).json({ success: false, message: "메뉴 수정 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 메뉴 삭제 */ export async function deleteMenu( req: AuthenticatedRequest, res: Response ): Promise { try { const { menuId } = req.params; logger.info(`메뉴 삭제 요청: menuId = ${menuId}`, { user: req.user }); // PostgreSQL 클라이언트 생성 const client = new Client({ connectionString: process.env.DATABASE_URL || "postgresql://postgres:postgres@localhost:5432/ilshin", }); await client.connect(); // 실제 데이터베이스에서 메뉴 삭제 const query = ` DELETE FROM menu_info WHERE objid = $1 RETURNING * `; const result = await client.query(query, [BigInt(menuId)]); if (result.rowCount === 0) { await client.end(); res.status(404).json({ success: false, message: "삭제할 메뉴를 찾을 수 없습니다.", }); return; } const deletedMenu = result.rows[0]; await client.end(); logger.info("메뉴 삭제 성공", { deletedMenu }); const response: ApiResponse = { success: true, message: "메뉴가 성공적으로 삭제되었습니다.", data: { objid: deletedMenu.objid.toString(), menuNameKor: deletedMenu.menu_name_kor, menuNameEng: deletedMenu.menu_name_eng, menuUrl: deletedMenu.menu_url, menuDesc: deletedMenu.menu_desc, status: deletedMenu.status, writer: deletedMenu.writer, regdate: deletedMenu.regdate, }, }; res.status(200).json(response); } catch (error) { logger.error("메뉴 삭제 실패:", error); res.status(500).json({ success: false, message: "메뉴 삭제 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 메뉴 일괄 삭제 */ export async function deleteMenusBatch( req: AuthenticatedRequest, res: Response ): Promise { try { const menuIds = req.body as string[]; logger.info("메뉴 일괄 삭제 요청", { menuIds, user: req.user }); if (!Array.isArray(menuIds) || menuIds.length === 0) { res.status(400).json({ success: false, message: "삭제할 메뉴 ID 목록이 필요합니다.", }); return; } // PostgreSQL 클라이언트 생성 const client = new Client({ connectionString: process.env.DATABASE_URL || "postgresql://postgres:postgres@localhost:5432/ilshin", }); await client.connect(); let deletedCount = 0; let failedCount = 0; const deletedMenus: any[] = []; const failedMenuIds: string[] = []; // 각 메뉴 ID에 대해 삭제 시도 for (const menuId of menuIds) { try { const query = ` DELETE FROM menu_info WHERE objid = $1 RETURNING * `; const result = await client.query(query, [BigInt(menuId)]); if (result.rowCount && result.rowCount > 0) { deletedCount++; deletedMenus.push(result.rows[0]); } else { failedCount++; failedMenuIds.push(menuId); } } catch (error) { logger.error(`메뉴 삭제 실패 (ID: ${menuId}):`, error); failedCount++; failedMenuIds.push(menuId); } } await client.end(); logger.info("메뉴 일괄 삭제 완료", { total: menuIds.length, deletedCount, failedCount, deletedMenus, failedMenuIds, }); const response: ApiResponse = { success: true, message: `메뉴 일괄 삭제 완료: ${deletedCount}개 삭제, ${failedCount}개 실패`, data: { deletedCount, failedCount, total: menuIds.length, deletedMenus, failedMenuIds, }, }; res.status(200).json(response); } catch (error) { logger.error("메뉴 일괄 삭제 실패:", error); res.status(500).json({ success: false, message: "메뉴 일괄 삭제 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * 회사 목록 조회 (실제 데이터베이스에서) */ export async function getCompanyListFromDB( req: AuthenticatedRequest, res: Response ): Promise { try { logger.info("회사 목록 조회 요청 (DB)", { user: req.user }); // PostgreSQL 클라이언트 생성 const client = new Client({ connectionString: process.env.DATABASE_URL || "postgresql://postgres:postgres@localhost:5432/ilshin", }); 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 }); const response: ApiResponse = { success: true, message: "회사 목록 조회 성공", data: companies, total: companies.length, }; res.status(200).json(response); } catch (error) { logger.error("회사 목록 조회 실패 (DB):", error); res.status(500).json({ success: false, message: "회사 목록 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } } /** * GET /api/admin/departments * 부서 목록 조회 API * 기존 Java AdminController의 부서 목록 조회 기능 포팅 */ export const getDepartmentList = async ( req: AuthenticatedRequest, res: Response ) => { try { logger.info("부서 목록 조회 요청", { query: req.query, user: req.user, }); const { companyCode, status, search } = req.query; // PostgreSQL 클라이언트 생성 const client = new Client({ connectionString: process.env.DATABASE_URL || "postgresql://postgres:postgres@localhost:5432/ilshin", }); await client.connect(); try { // 부서 목록 조회 쿼리 let query = ` SELECT dept_code, parent_dept_code, dept_name, master_sabun, master_user_id, location, location_name, regdate, data_type, status, sales_yn, company_name FROM dept_info WHERE 1=1 `; const queryParams: any[] = []; let paramIndex = 1; // 회사 코드 필터 if (companyCode) { query += ` AND company_name = $${paramIndex}`; queryParams.push(companyCode); paramIndex++; } // 상태 필터 if (status) { query += ` AND status = $${paramIndex}`; queryParams.push(status); paramIndex++; } // 검색 조건 if (search) { query += ` AND ( dept_name ILIKE $${paramIndex} OR dept_code ILIKE $${paramIndex} OR location_name ILIKE $${paramIndex} )`; queryParams.push(`%${search}%`); paramIndex++; } // 정렬 (상위 부서 먼저, 그 다음 부서명 순) query += ` ORDER BY CASE WHEN parent_dept_code IS NULL OR parent_dept_code = '' THEN 0 ELSE 1 END, dept_name`; const result = await client.query(query, queryParams); const departments = result.rows; // 부서 트리 구조 생성 const deptMap = new Map(); const rootDepartments: any[] = []; // 모든 부서를 맵에 저장 departments.forEach((dept) => { deptMap.set(dept.dept_code, { deptCode: dept.dept_code, deptName: dept.dept_name, parentDeptCode: dept.parent_dept_code, masterSabun: dept.master_sabun, masterUserId: dept.master_user_id, location: dept.location, locationName: dept.location_name, regdate: dept.regdate ? dept.regdate.toISOString() : null, dataType: dept.data_type, status: dept.status || "active", salesYn: dept.sales_yn, companyName: dept.company_name, children: [], }); }); // 부서 트리 구조 생성 departments.forEach((dept) => { const deptNode = deptMap.get(dept.dept_code); if (dept.parent_dept_code && deptMap.has(dept.parent_dept_code)) { // 상위 부서가 있으면 children에 추가 const parentDept = deptMap.get(dept.parent_dept_code); parentDept.children.push(deptNode); } else { // 상위 부서가 없으면 루트 부서로 추가 rootDepartments.push(deptNode); } }); const response = { success: true, data: { departments: rootDepartments, flatList: departments.map((dept) => ({ deptCode: dept.dept_code, deptName: dept.dept_name, parentDeptCode: dept.parent_dept_code, masterSabun: dept.master_sabun, masterUserId: dept.master_user_id, location: dept.location, locationName: dept.location_name, regdate: dept.regdate ? dept.regdate.toISOString() : null, dataType: dept.data_type, status: dept.status || "active", salesYn: dept.sales_yn, companyName: dept.company_name, })), }, message: "부서 목록 조회 성공", total: departments.length, }; logger.info("부서 목록 조회 성공", { totalCount: departments.length, rootCount: rootDepartments.length, }); res.status(200).json(response); } finally { await client.end(); } } catch (error) { logger.error("부서 목록 조회 실패", { error }); res.status(500).json({ success: false, message: "부서 목록 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } }; /** * GET /api/admin/users/:userId * 사용자 상세 조회 API * 기존 Java AdminController의 사용자 상세 조회 기능 포팅 */ export const getUserInfo = async (req: AuthenticatedRequest, res: Response) => { try { const { userId } = req.params; logger.info(`사용자 상세 조회 요청 - userId: ${userId}`, { user: req.user, }); if (!userId) { res.status(400).json({ success: false, message: "사용자 ID가 필요합니다.", error: { code: "USER_ID_REQUIRED", details: "userId parameter is required", }, }); return; } // PostgreSQL 클라이언트 생성 const client = new Client({ connectionString: process.env.DATABASE_URL || "postgresql://postgres:postgres@localhost:5432/ilshin", }); await client.connect(); try { // 사용자 상세 정보 조회 쿼리 const query = ` SELECT u.sabun, u.user_id, u.user_name, u.user_name_eng, u.user_name_cn, 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.end_date, u.fax_no, u.partner_objid, u.rank, u.locale, u.company_code, u.data_type, d.dept_name as dept_name_full, d.parent_dept_code, d.location, d.location_name, d.sales_yn, d.company_name as dept_company_name FROM user_info u LEFT JOIN dept_info d ON u.dept_code = d.dept_code WHERE u.user_id = $1 `; const result = await client.query(query, [userId]); if (result.rows.length === 0) { res.status(404).json({ success: false, message: "사용자를 찾을 수 없습니다.", error: { code: "USER_NOT_FOUND", details: `User ID: ${userId}`, }, }); return; } const user = result.rows[0]; // 응답 데이터 가공 const userInfo = { sabun: user.sabun, userId: user.user_id, userName: user.user_name, userNameEng: user.user_name_eng, userNameCn: user.user_name_cn, deptCode: user.dept_code, deptName: user.dept_name || user.dept_name_full, positionCode: user.position_code, positionName: user.position_name, email: user.email, tel: user.tel, cellPhone: user.cell_phone, userType: user.user_type, userTypeName: user.user_type_name, regdate: user.regdate ? user.regdate.toISOString() : null, status: user.status || "active", endDate: user.end_date ? user.end_date.toISOString() : null, faxNo: user.fax_no, partnerObjid: user.partner_objid, rank: user.rank, locale: user.locale, companyCode: user.company_code, dataType: user.data_type, // 부서 정보 deptInfo: { deptCode: user.dept_code, deptName: user.dept_name || user.dept_name_full, parentDeptCode: user.parent_dept_code, location: user.location, locationName: user.location_name, salesYn: user.sales_yn, companyName: user.dept_company_name, }, }; const response = { success: true, data: userInfo, message: "사용자 상세 정보 조회 성공", }; logger.info("사용자 상세 정보 조회 성공", { userId, userName: user.user_name, }); res.status(200).json(response); } finally { await client.end(); } } catch (error) { logger.error("사용자 상세 정보 조회 실패", { error, userId: req.params.userId, }); res.status(500).json({ success: false, message: "사용자 상세 정보 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } }; /** * POST /api/admin/users/check-duplicate * 사용자 ID 중복 체크 API * 기존 Java AdminController의 checkDuplicateUserId 기능 포팅 */ export const checkDuplicateUserId = async ( req: AuthenticatedRequest, res: Response ) => { try { const { userId } = req.body; logger.info(`사용자 ID 중복 체크 요청 - userId: ${userId}`, { user: req.user, }); if (!userId) { res.status(400).json({ success: false, message: "사용자 ID가 필요합니다.", error: { code: "USER_ID_REQUIRED", details: "userId is required", }, }); return; } // PostgreSQL 클라이언트 생성 const client = new Client({ connectionString: process.env.DATABASE_URL || "postgresql://postgres:postgres@localhost:5432/ilshin", }); await client.connect(); 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, isDuplicate, count, }); res.status(200).json(response); } finally { await client.end(); } } catch (error) { logger.error("사용자 ID 중복 체크 실패", { error, userId: req.body?.userId, }); res.status(500).json({ success: false, message: "사용자 ID 중복 체크 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } }; /** * POST /api/admin/users * 사용자 등록/수정 API * 기존 Java AdminController의 saveUserInfo 기능 포팅 */ export const saveUser = async (req: AuthenticatedRequest, res: Response) => { try { const userData = req.body; logger.info("사용자 저장 요청", { userData, user: req.user }); // 필수 필드 검증 const requiredFields = ["userId", "userName", "userPassword"]; for (const field of requiredFields) { if (!userData[field] || userData[field].trim() === "") { res.status(400).json({ success: false, message: `${field}는 필수 입력 항목입니다.`, error: { code: "REQUIRED_FIELD_MISSING", details: `Required field: ${field}`, }, }); return; } } // PostgreSQL 클라이언트 생성 const client = new Client({ connectionString: process.env.DATABASE_URL || "postgresql://postgres:postgres@localhost:5432/ilshin", }); await client.connect(); try { // 기존 사용자 확인 const checkQuery = ` SELECT user_id FROM user_info WHERE user_id = $1 `; const checkResult = await client.query(checkQuery, [userData.userId]); const isUpdate = checkResult.rows.length > 0; if (isUpdate) { // 기존 사용자 수정 const updateQuery = ` UPDATE user_info SET user_name = $1, user_name_eng = $2, user_password = $3, dept_code = $4, dept_name = $5, position_code = $6, position_name = $7, email = $8, tel = $9, cell_phone = $10, user_type = $11, user_type_name = $12, sabun = $13, company_code = $14, status = $15, locale = $16 WHERE user_id = $17 `; const updateValues = [ userData.userName, userData.userNameEng || null, await EncryptUtil.encrypt(userData.userPassword), // 비밀번호 암호화 userData.deptCode || null, userData.deptName || null, userData.positionCode || null, userData.positionName || null, userData.email || null, userData.tel || null, userData.cellPhone || null, userData.userType || null, userData.userTypeName || null, userData.sabun || null, userData.companyCode || null, userData.status || "active", userData.locale || null, userData.userId, ]; await client.query(updateQuery, updateValues); logger.info("사용자 정보 수정 완료", { userId: userData.userId }); } else { // 새 사용자 등록 const insertQuery = ` INSERT INTO user_info ( user_id, user_name, user_name_eng, user_password, dept_code, dept_name, position_code, position_name, email, tel, cell_phone, user_type, user_type_name, sabun, company_code, status, locale, regdate ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18 ) `; const insertValues = [ userData.userId, userData.userName, userData.userNameEng || null, await EncryptUtil.encrypt(userData.userPassword), // 비밀번호 암호화 userData.deptCode || null, userData.deptName || null, userData.positionCode || null, userData.positionName || null, userData.email || null, userData.tel || null, userData.cellPhone || null, userData.userType || null, userData.userTypeName || null, userData.sabun || null, userData.companyCode || null, userData.status || "active", userData.locale || null, new Date(), ]; await client.query(insertQuery, insertValues); logger.info("새 사용자 등록 완료", { userId: userData.userId }); } const response = { success: true, result: true, message: isUpdate ? "사용자 정보가 수정되었습니다." : "사용자가 등록되었습니다.", data: { userId: userData.userId, isUpdate, }, }; res.status(200).json(response); } finally { await client.end(); } } catch (error) { logger.error("사용자 저장 실패", { error, userData: req.body }); res.status(500).json({ success: false, result: false, message: "사용자 저장 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } };