사용자관리 등록

This commit is contained in:
kjs
2025-08-25 13:12:17 +09:00
parent 8667cb4780
commit ce130ee225
9 changed files with 1256 additions and 170 deletions

View File

@@ -4,6 +4,7 @@ import { AuthenticatedRequest } from "../types/auth";
import { ApiResponse } from "../types/common";
import { Client } from "pg";
import { AdminService } from "../services/adminService";
import { EncryptUtil } from "../utils/encryptUtil";
/**
* 관리자 메뉴 목록 조회
@@ -172,67 +173,215 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => {
user: req.user,
});
// 임시 더미 데이터 반환 (실제로는 데이터베이스에서 조회)
const { page = 1, countPerPage = 20 } = req.query;
const { page = 1, countPerPage = 20, search, deptCode, status } = req.query;
const dummyUsers = [
{
userId: "plm_admin",
userName: "관리자",
deptName: "IT팀",
companyCode: "ILSHIN",
userType: "admin",
email: "admin@ilshin.com",
status: "active",
regDate: "2024-01-15",
},
{
userId: "user001",
userName: "홍길동",
deptName: "영업팀",
companyCode: "ILSHIN",
userType: "user",
email: "hong@ilshin.com",
status: "active",
regDate: "2024-01-16",
},
{
userId: "user002",
userName: "김철수",
deptName: "개발팀",
companyCode: "ILSHIN",
userType: "user",
email: "kim@ilshin.com",
status: "inactive",
regDate: "2024-01-17",
},
];
// 페이징 처리
const startIndex = (Number(page) - 1) * Number(countPerPage);
const endIndex = startIndex + Number(countPerPage);
const paginatedUsers = dummyUsers.slice(startIndex, endIndex);
const response = {
success: true,
data: {
users: paginatedUsers,
pagination: {
currentPage: Number(page),
countPerPage: Number(countPerPage),
totalCount: dummyUsers.length,
totalPages: Math.ceil(dummyUsers.length / Number(countPerPage)),
},
},
message: "사용자 목록 조회 성공",
};
logger.info("사용자 목록 조회 성공", {
totalCount: dummyUsers.length,
returnedCount: paginatedUsers.length,
// PostgreSQL 클라이언트 생성
const client = new Client({
connectionString:
process.env.DATABASE_URL ||
"postgresql://postgres:postgres@localhost:5432/ilshin",
});
res.status(200).json(response);
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({
@@ -386,7 +535,9 @@ export const setUserLocale = async (
};
/**
* GET /api/admin/companies
* 회사 목록 조회 API
* 기존 Java AdminController의 회사 목록 조회 기능 포팅
*/
export const getCompanyList = async (
req: AuthenticatedRequest,
@@ -398,44 +549,61 @@ export const getCompanyList = async (
user: req.user,
});
// 임시 더미 데이터 반환 (실제로는 데이터베이스에서 조회)
const dummyCompanies = [
{
company_code: "ILSHIN",
company_name: "일신제강",
status: "active",
writer: "admin",
regdate: new Date().toISOString(),
},
{
company_code: "HUTECH",
company_name: "후테크",
status: "active",
writer: "admin",
regdate: new Date().toISOString(),
},
{
company_code: "DAIN",
company_name: "다인",
status: "active",
writer: "admin",
regdate: new Date().toISOString(),
},
];
// 프론트엔드에서 기대하는 응답 형식으로 변환
const response = {
success: true,
data: dummyCompanies,
message: "회사 목록 조회 성공",
};
logger.info("회사 목록 조회 성공", {
totalCount: dummyCompanies.length,
response: response,
// PostgreSQL 클라이언트 생성
const client = new Client({
connectionString:
process.env.DATABASE_URL ||
"postgresql://postgres:postgres@localhost:5432/ilshin",
});
res.status(200).json(response);
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({
@@ -1259,3 +1427,552 @@ export async function getCompanyListFromDB(
});
}
}
/**
* 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",
});
}
};