2025-08-25 11:07:39 +09:00
|
|
|
import { Request, Response } from "express";
|
|
|
|
|
import { logger } from "../utils/logger";
|
2025-08-21 13:28:49 +09:00
|
|
|
import { AuthenticatedRequest } from "../types/auth";
|
2025-08-25 11:07:39 +09:00
|
|
|
import { ApiResponse } from "../types/common";
|
|
|
|
|
import { Client } from "pg";
|
2025-08-21 09:41:46 +09:00
|
|
|
import { AdminService } from "../services/adminService";
|
2025-08-25 13:12:17 +09:00
|
|
|
import { EncryptUtil } from "../utils/encryptUtil";
|
2025-08-21 09:41:46 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 관리자 메뉴 목록 조회
|
|
|
|
|
*/
|
|
|
|
|
export async function getAdminMenus(
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
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<any[]> = {
|
|
|
|
|
success: true,
|
|
|
|
|
message: "관리자 메뉴 목록 조회 성공",
|
|
|
|
|
data: menuList,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
res.status(200).json(response);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("관리자 메뉴 목록 조회 중 오류 발생:", error);
|
|
|
|
|
|
|
|
|
|
const response: ApiResponse<null> = {
|
|
|
|
|
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<void> {
|
|
|
|
|
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<any[]> = {
|
|
|
|
|
success: true,
|
|
|
|
|
message: "사용자 메뉴 목록 조회 성공",
|
|
|
|
|
data: menuList,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
res.status(200).json(response);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("사용자 메뉴 목록 조회 중 오류 발생:", error);
|
|
|
|
|
|
|
|
|
|
const response: ApiResponse<null> = {
|
|
|
|
|
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<void> {
|
|
|
|
|
try {
|
|
|
|
|
const { menuId } = req.params;
|
|
|
|
|
logger.info(`=== 메뉴 정보 조회 시작 - menuId: ${menuId} ===`);
|
|
|
|
|
|
|
|
|
|
const menuInfo = await AdminService.getMenuInfo(menuId);
|
|
|
|
|
|
|
|
|
|
if (!menuInfo) {
|
|
|
|
|
const response: ApiResponse<null> = {
|
|
|
|
|
success: false,
|
|
|
|
|
message: "메뉴를 찾을 수 없습니다.",
|
|
|
|
|
error: {
|
|
|
|
|
code: "MENU_NOT_FOUND",
|
|
|
|
|
details: `Menu ID: ${menuId}`,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
res.status(404).json(response);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const response: ApiResponse<any> = {
|
|
|
|
|
success: true,
|
|
|
|
|
message: "메뉴 정보 조회 성공",
|
|
|
|
|
data: menuInfo,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
res.status(200).json(response);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("메뉴 정보 조회 중 오류 발생:", error);
|
|
|
|
|
|
|
|
|
|
const response: ApiResponse<null> = {
|
|
|
|
|
success: false,
|
|
|
|
|
message: "메뉴 정보 조회 중 오류가 발생했습니다.",
|
|
|
|
|
error: {
|
|
|
|
|
code: "MENU_INFO_ERROR",
|
|
|
|
|
details: error instanceof Error ? error.message : "Unknown error",
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
res.status(500).json(response);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-21 13:28:49 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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,
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-25 13:12:17 +09:00
|
|
|
const { page = 1, countPerPage = 20, search, deptCode, status } = req.query;
|
2025-08-21 13:28:49 +09:00
|
|
|
|
2025-08-25 13:12:17 +09:00
|
|
|
// PostgreSQL 클라이언트 생성
|
|
|
|
|
const client = new Client({
|
|
|
|
|
connectionString:
|
|
|
|
|
process.env.DATABASE_URL ||
|
|
|
|
|
"postgresql://postgres:postgres@localhost:5432/ilshin",
|
|
|
|
|
});
|
2025-08-21 13:28:49 +09:00
|
|
|
|
2025-08-25 13:12:17 +09:00
|
|
|
await client.connect();
|
2025-08-21 13:28:49 +09:00
|
|
|
|
2025-08-25 13:12:17 +09:00
|
|
|
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)),
|
|
|
|
|
},
|
2025-08-21 13:28:49 +09:00
|
|
|
},
|
2025-08-25 13:12:17 +09:00
|
|
|
message: "사용자 목록 조회 성공",
|
|
|
|
|
};
|
2025-08-21 13:28:49 +09:00
|
|
|
|
2025-08-25 13:12:17 +09:00
|
|
|
logger.info("사용자 목록 조회 성공", {
|
|
|
|
|
totalCount,
|
|
|
|
|
returnedCount: processedUsers.length,
|
|
|
|
|
currentPage: Number(page),
|
|
|
|
|
countPerPage: Number(countPerPage),
|
|
|
|
|
});
|
2025-08-21 13:28:49 +09:00
|
|
|
|
2025-08-25 13:12:17 +09:00
|
|
|
res.status(200).json(response);
|
|
|
|
|
} finally {
|
|
|
|
|
await client.end();
|
|
|
|
|
}
|
2025-08-21 13:28:49 +09:00
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("사용자 목록 조회 실패", { error });
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "사용자 목록 조회 중 오류가 발생했습니다.",
|
|
|
|
|
error: error instanceof Error ? error.message : "Unknown error",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-08-21 14:47:07 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /api/admin/user-locale
|
|
|
|
|
* 사용자 로케일 조회 API
|
|
|
|
|
*/
|
|
|
|
|
export const getUserLocale = async (
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
2025-08-25 11:07:39 +09:00
|
|
|
): Promise<void> => {
|
2025-08-21 14:47:07 +09:00
|
|
|
try {
|
|
|
|
|
logger.info("사용자 로케일 조회 요청", {
|
|
|
|
|
query: req.query,
|
|
|
|
|
user: req.user,
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-25 11:07:39 +09:00
|
|
|
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,
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-08-21 14:47:07 +09:00
|
|
|
|
|
|
|
|
const response = {
|
|
|
|
|
success: true,
|
|
|
|
|
data: userLocale,
|
|
|
|
|
message: "사용자 로케일 조회 성공",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
logger.info("사용자 로케일 조회 성공", {
|
|
|
|
|
userLocale,
|
2025-08-25 11:07:39 +09:00
|
|
|
userId: req.user.userId,
|
|
|
|
|
fromDatabase: !!userInfo?.locale,
|
2025-08-21 14:47:07 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-25 11:07:39 +09:00
|
|
|
* 사용자 로케일 설정
|
|
|
|
|
*/
|
|
|
|
|
export const setUserLocale = async (
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
): Promise<void> => {
|
|
|
|
|
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",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-25 13:12:17 +09:00
|
|
|
* GET /api/admin/companies
|
2025-08-21 14:47:07 +09:00
|
|
|
* 회사 목록 조회 API
|
2025-08-25 13:12:17 +09:00
|
|
|
* 기존 Java AdminController의 회사 목록 조회 기능 포팅
|
2025-08-21 14:47:07 +09:00
|
|
|
*/
|
|
|
|
|
export const getCompanyList = async (
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
logger.info("회사 목록 조회 요청", {
|
|
|
|
|
query: req.query,
|
|
|
|
|
user: req.user,
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-25 13:12:17 +09:00
|
|
|
// PostgreSQL 클라이언트 생성
|
|
|
|
|
const client = new Client({
|
|
|
|
|
connectionString:
|
|
|
|
|
process.env.DATABASE_URL ||
|
|
|
|
|
"postgresql://postgres:postgres@localhost:5432/ilshin",
|
|
|
|
|
});
|
2025-08-21 14:47:07 +09:00
|
|
|
|
2025-08-25 13:12:17 +09:00
|
|
|
await client.connect();
|
2025-08-21 14:47:07 +09:00
|
|
|
|
2025-08-25 13:12:17 +09:00
|
|
|
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: "회사 목록 조회 성공",
|
|
|
|
|
};
|
2025-08-21 14:47:07 +09:00
|
|
|
|
2025-08-25 13:12:17 +09:00
|
|
|
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();
|
|
|
|
|
}
|
2025-08-21 14:47:07 +09:00
|
|
|
} 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<void> {
|
|
|
|
|
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<any[]> = {
|
|
|
|
|
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<void> {
|
|
|
|
|
try {
|
2025-08-25 11:07:39 +09:00
|
|
|
logger.info("다국어 키 목록 조회 요청", {
|
|
|
|
|
query: req.query,
|
|
|
|
|
user: req.user,
|
|
|
|
|
});
|
2025-08-21 14:47:07 +09:00
|
|
|
|
|
|
|
|
// 더미 데이터 반환
|
|
|
|
|
const langKeys = [
|
|
|
|
|
{
|
|
|
|
|
keyId: 1,
|
|
|
|
|
companyCode: "ILSHIN",
|
|
|
|
|
menuName: "사용자 관리",
|
|
|
|
|
langKey: "user.management.title",
|
|
|
|
|
description: "사용자 관리 페이지 제목",
|
|
|
|
|
isActive: "Y",
|
2025-08-25 11:07:39 +09:00
|
|
|
createdDate: new Date().toISOString(),
|
|
|
|
|
createdBy: "admin",
|
|
|
|
|
updatedDate: new Date().toISOString(),
|
|
|
|
|
updatedBy: "admin",
|
2025-08-21 14:47:07 +09:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
keyId: 2,
|
|
|
|
|
companyCode: "ILSHIN",
|
|
|
|
|
menuName: "메뉴 관리",
|
|
|
|
|
langKey: "menu.management.title",
|
|
|
|
|
description: "메뉴 관리 페이지 제목",
|
|
|
|
|
isActive: "Y",
|
2025-08-25 11:07:39 +09:00
|
|
|
createdDate: new Date().toISOString(),
|
|
|
|
|
createdBy: "admin",
|
|
|
|
|
updatedDate: new Date().toISOString(),
|
|
|
|
|
updatedBy: "admin",
|
2025-08-21 14:47:07 +09:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
keyId: 3,
|
|
|
|
|
companyCode: "HUTECH",
|
|
|
|
|
menuName: "대시보드",
|
|
|
|
|
langKey: "dashboard.title",
|
|
|
|
|
description: "대시보드 페이지 제목",
|
|
|
|
|
isActive: "Y",
|
2025-08-25 11:07:39 +09:00
|
|
|
createdDate: new Date().toISOString(),
|
|
|
|
|
createdBy: "admin",
|
|
|
|
|
updatedDate: new Date().toISOString(),
|
|
|
|
|
updatedBy: "admin",
|
2025-08-21 14:47:07 +09:00
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
2025-08-25 11:07:39 +09:00
|
|
|
// 프론트엔드에서 기대하는 응답 형식으로 변환
|
2025-08-21 14:47:07 +09:00
|
|
|
const response: ApiResponse<any[]> = {
|
|
|
|
|
success: true,
|
|
|
|
|
message: "다국어 키 목록 조회 성공",
|
|
|
|
|
data: langKeys,
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-25 11:07:39 +09:00
|
|
|
logger.info("다국어 키 목록 조회 성공", {
|
|
|
|
|
totalCount: langKeys.length,
|
|
|
|
|
response: response,
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-21 14:47:07 +09:00
|
|
|
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<void> {
|
|
|
|
|
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<any[]> = {
|
|
|
|
|
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<void> {
|
|
|
|
|
try {
|
|
|
|
|
const { keyId } = req.params;
|
|
|
|
|
const textData = req.body;
|
|
|
|
|
logger.info(`다국어 텍스트 저장 요청: keyId = ${keyId}`, { textData });
|
|
|
|
|
|
|
|
|
|
// 더미 응답
|
|
|
|
|
const response: ApiResponse<any> = {
|
|
|
|
|
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<void> {
|
|
|
|
|
try {
|
|
|
|
|
const keyData = req.body;
|
|
|
|
|
logger.info("다국어 키 저장 요청", { keyData });
|
|
|
|
|
|
|
|
|
|
// 더미 응답
|
|
|
|
|
const response: ApiResponse<any> = {
|
|
|
|
|
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<void> {
|
|
|
|
|
try {
|
|
|
|
|
const { keyId } = req.params;
|
|
|
|
|
const keyData = req.body;
|
|
|
|
|
logger.info(`다국어 키 수정 요청: keyId = ${keyId}`, { keyData });
|
|
|
|
|
|
|
|
|
|
// 더미 응답
|
|
|
|
|
const response: ApiResponse<any> = {
|
|
|
|
|
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<void> {
|
|
|
|
|
try {
|
|
|
|
|
const { keyId } = req.params;
|
|
|
|
|
logger.info(`다국어 키 삭제 요청: keyId = ${keyId}`);
|
|
|
|
|
|
|
|
|
|
// 더미 응답
|
|
|
|
|
const response: ApiResponse<any> = {
|
|
|
|
|
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<void> {
|
|
|
|
|
try {
|
|
|
|
|
const { keyId } = req.params;
|
|
|
|
|
logger.info(`다국어 키 상태 토글 요청: keyId = ${keyId}`);
|
|
|
|
|
|
|
|
|
|
// 더미 응답
|
|
|
|
|
const response: ApiResponse<any> = {
|
|
|
|
|
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<void> {
|
|
|
|
|
try {
|
|
|
|
|
const langData = req.body;
|
|
|
|
|
logger.info("언어 저장 요청", { langData });
|
|
|
|
|
|
|
|
|
|
// 더미 응답
|
|
|
|
|
const response: ApiResponse<any> = {
|
|
|
|
|
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<void> {
|
|
|
|
|
try {
|
|
|
|
|
const { langCode } = req.params;
|
|
|
|
|
const langData = req.body;
|
|
|
|
|
logger.info(`언어 수정 요청: langCode = ${langCode}`, { langData });
|
|
|
|
|
|
|
|
|
|
// 더미 응답
|
|
|
|
|
const response: ApiResponse<any> = {
|
|
|
|
|
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<void> {
|
|
|
|
|
try {
|
|
|
|
|
const { langCode } = req.params;
|
|
|
|
|
logger.info(`언어 상태 토글 요청: langCode = ${langCode}`);
|
|
|
|
|
|
|
|
|
|
// 더미 응답
|
|
|
|
|
const response: ApiResponse<any> = {
|
|
|
|
|
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",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-25 11:07:39 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 메뉴 저장 (추가/수정)
|
|
|
|
|
*/
|
|
|
|
|
export async function saveMenu(
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
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<any> = {
|
|
|
|
|
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<void> {
|
|
|
|
|
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<any> = {
|
|
|
|
|
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<void> {
|
|
|
|
|
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<any> = {
|
|
|
|
|
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<void> {
|
|
|
|
|
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<any> = {
|
|
|
|
|
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<void> {
|
|
|
|
|
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<any> = {
|
|
|
|
|
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",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-25 13:12:17 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|