TK-FB(공장관리+신고)와 M-Project(부적합관리)를 3개 독립 시스템으로 분리하기 위한 전체 코드 구조 작성. - SSO 인증 서비스 (bcrypt + pbkdf2 이중 해시 지원) - System 1: 공장관리 (TK-FB 기반, 신고 코드 제거) - System 2: 신고 (TK-FB에서 workIssue 코드 추출) - System 3: 부적합관리 (M-Project 기반) - Gateway 포털 (path-based 라우팅) - 통합 docker-compose.yml 및 배포 스크립트 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
740 lines
22 KiB
JavaScript
740 lines
22 KiB
JavaScript
/**
|
|
* 사용자 관리 컨트롤러
|
|
*
|
|
* 사용자 CRUD 및 상태 관리 기능을 제공하는 컨트롤러
|
|
*
|
|
* @author TK-FB-Project
|
|
* @since 2025-12-11
|
|
*/
|
|
|
|
const bcrypt = require('bcrypt');
|
|
const { ValidationError, ForbiddenError, NotFoundError, ConflictError, DatabaseError } = require('../utils/errors');
|
|
const { asyncHandler } = require('../middlewares/errorHandler');
|
|
const logger = require('../utils/logger');
|
|
|
|
/**
|
|
* 관리자 권한 확인 헬퍼 함수
|
|
*/
|
|
const checkAdminPermission = (user) => {
|
|
if (!user || !['admin', 'system'].includes(user.access_level)) {
|
|
throw new ForbiddenError('관리자 권한이 필요합니다');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 모든 사용자 조회
|
|
*/
|
|
const getAllUsers = asyncHandler(async (req, res) => {
|
|
checkAdminPermission(req.user);
|
|
|
|
logger.info('사용자 목록 조회 요청', { requestedBy: req.user?.username });
|
|
|
|
const { getDb } = require('../dbPool');
|
|
const db = await getDb();
|
|
|
|
try {
|
|
const query = `
|
|
SELECT
|
|
u.user_id,
|
|
u.username,
|
|
u.name,
|
|
u.email,
|
|
u.role_id,
|
|
r.name as role,
|
|
u._access_level_old as access_level,
|
|
u.is_active,
|
|
u.worker_id,
|
|
w.worker_name,
|
|
w.department_id,
|
|
d.department_name,
|
|
u.created_at,
|
|
u.updated_at,
|
|
u.last_login_at as last_login
|
|
FROM users u
|
|
LEFT JOIN roles r ON u.role_id = r.id
|
|
LEFT JOIN workers w ON u.worker_id = w.worker_id
|
|
LEFT JOIN departments d ON w.department_id = d.department_id
|
|
ORDER BY u.created_at DESC
|
|
`;
|
|
|
|
const [users] = await db.execute(query);
|
|
|
|
logger.info('사용자 목록 조회 성공', { count: users.length });
|
|
|
|
res.json({
|
|
success: true,
|
|
data: users,
|
|
message: '사용자 목록 조회 성공'
|
|
});
|
|
} catch (error) {
|
|
logger.error('사용자 목록 조회 실패', { error: error.message });
|
|
throw new DatabaseError('사용자 목록을 조회하는데 실패했습니다');
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 특정 사용자 조회
|
|
*/
|
|
const getUserById = asyncHandler(async (req, res) => {
|
|
const { id } = req.params;
|
|
|
|
if (!id || isNaN(id)) {
|
|
throw new ValidationError('유효하지 않은 사용자 ID입니다');
|
|
}
|
|
|
|
logger.info('사용자 조회 요청', { userId: id });
|
|
|
|
const { getDb } = require('../dbPool');
|
|
const db = await getDb();
|
|
|
|
try {
|
|
const query = `
|
|
SELECT
|
|
user_id,
|
|
username,
|
|
name,
|
|
email,
|
|
phone,
|
|
role,
|
|
access_level,
|
|
is_active,
|
|
created_at,
|
|
updated_at,
|
|
last_login
|
|
FROM users
|
|
WHERE user_id = ?
|
|
`;
|
|
|
|
const [users] = await db.execute(query, [id]);
|
|
|
|
if (users.length === 0) {
|
|
throw new NotFoundError('사용자를 찾을 수 없습니다');
|
|
}
|
|
|
|
logger.info('사용자 조회 성공', { userId: id, username: users[0].username });
|
|
|
|
res.json({
|
|
success: true,
|
|
data: users[0],
|
|
message: '사용자 조회 성공'
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof NotFoundError) {
|
|
throw error;
|
|
}
|
|
logger.error('사용자 조회 실패', { userId: id, error: error.message });
|
|
throw new DatabaseError('사용자를 조회하는데 실패했습니다');
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 새 사용자 생성
|
|
*/
|
|
const createUser = asyncHandler(async (req, res) => {
|
|
checkAdminPermission(req.user);
|
|
|
|
const { username, name, email, phone, role, password } = req.body;
|
|
|
|
logger.info('사용자 생성 요청', { username, name, role });
|
|
|
|
// 필수 필드 검증
|
|
if (!username || !name || !role || !password) {
|
|
throw new ValidationError('필수 필드가 누락되었습니다', {
|
|
required: ['username', 'name', 'role', 'password'],
|
|
received: { username, name, role, password: '***' }
|
|
});
|
|
}
|
|
|
|
// 사용자명 유효성 검증
|
|
if (username.length < 3 || username.length > 20) {
|
|
throw new ValidationError('사용자명은 3-20자 사이여야 합니다');
|
|
}
|
|
|
|
// 비밀번호 유효성 검증
|
|
if (password.length < 6) {
|
|
throw new ValidationError('비밀번호는 최소 6자 이상이어야 합니다');
|
|
}
|
|
|
|
// 권한 레벨 검증
|
|
const validRoles = ['admin', 'group_leader', 'worker'];
|
|
if (!validRoles.includes(role)) {
|
|
throw new ValidationError('유효하지 않은 권한입니다', {
|
|
valid: validRoles,
|
|
received: role
|
|
});
|
|
}
|
|
|
|
const { getDb } = require('../dbPool');
|
|
const db = await getDb();
|
|
|
|
try {
|
|
// 사용자명 중복 확인
|
|
const checkQuery = 'SELECT user_id FROM users WHERE username = ?';
|
|
const [existing] = await db.execute(checkQuery, [username]);
|
|
|
|
if (existing.length > 0) {
|
|
throw new ConflictError('이미 존재하는 사용자명입니다');
|
|
}
|
|
|
|
// 비밀번호 해시화
|
|
const hashedPassword = await bcrypt.hash(password, 10);
|
|
|
|
// 사용자 생성
|
|
const insertQuery = `
|
|
INSERT INTO users (username, name, email, phone, role, access_level, password_hash, is_active, created_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, 1, NOW())
|
|
`;
|
|
|
|
const [result] = await db.execute(insertQuery, [
|
|
username,
|
|
name,
|
|
email || null,
|
|
phone || null,
|
|
role,
|
|
role, // access_level을 role과 동일하게 설정
|
|
hashedPassword
|
|
]);
|
|
|
|
logger.info('사용자 생성 성공', {
|
|
userId: result.insertId,
|
|
username,
|
|
name,
|
|
role,
|
|
createdBy: req.user.username
|
|
});
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
data: { user_id: result.insertId },
|
|
message: '사용자가 성공적으로 생성되었습니다'
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof ConflictError) {
|
|
throw error;
|
|
}
|
|
logger.error('사용자 생성 실패', { username, error: error.message });
|
|
throw new DatabaseError('사용자를 생성하는데 실패했습니다');
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 사용자 정보 수정
|
|
*/
|
|
const updateUser = asyncHandler(async (req, res) => {
|
|
checkAdminPermission(req.user);
|
|
|
|
const { id } = req.params;
|
|
const { username, name, email, role, role_id, password, worker_id } = req.body;
|
|
|
|
if (!id || isNaN(id)) {
|
|
throw new ValidationError('유효하지 않은 사용자 ID입니다');
|
|
}
|
|
|
|
logger.info('사용자 수정 요청', { userId: id, body: req.body });
|
|
|
|
// 최소 하나의 수정 필드가 필요
|
|
if (!username && !name && email === undefined && !role && !role_id && !password && worker_id === undefined) {
|
|
throw new ValidationError('수정할 필드가 없습니다');
|
|
}
|
|
|
|
const { getDb } = require('../dbPool');
|
|
const db = await getDb();
|
|
|
|
try {
|
|
// 사용자 존재 확인
|
|
const checkQuery = 'SELECT user_id, username, is_active FROM users WHERE user_id = ?';
|
|
const [existing] = await db.execute(checkQuery, [id]);
|
|
|
|
if (existing.length === 0) {
|
|
throw new NotFoundError('사용자를 찾을 수 없습니다');
|
|
}
|
|
|
|
if (existing[0].is_active === 0) {
|
|
throw new ValidationError('비활성화된 사용자는 수정할 수 없습니다');
|
|
}
|
|
|
|
// 업데이트할 필드들
|
|
const updates = [];
|
|
const values = [];
|
|
|
|
if (username) {
|
|
if (username.length < 3 || username.length > 20) {
|
|
throw new ValidationError('사용자명은 3-20자 사이여야 합니다');
|
|
}
|
|
|
|
// 사용자명 중복 확인 (자신 제외)
|
|
const dupQuery = 'SELECT user_id FROM users WHERE username = ? AND user_id != ?';
|
|
const [duplicate] = await db.execute(dupQuery, [username, id]);
|
|
|
|
if (duplicate.length > 0) {
|
|
throw new ConflictError('이미 존재하는 사용자명입니다');
|
|
}
|
|
|
|
updates.push('username = ?');
|
|
values.push(username);
|
|
}
|
|
|
|
if (name) {
|
|
updates.push('name = ?');
|
|
values.push(name);
|
|
}
|
|
|
|
if (email !== undefined) {
|
|
updates.push('email = ?');
|
|
values.push(email || null);
|
|
}
|
|
|
|
// role_id 또는 role 문자열 처리
|
|
if (role_id) {
|
|
// role_id가 유효한지 확인
|
|
const [roleCheck] = await db.execute('SELECT id, name FROM roles WHERE id = ?', [role_id]);
|
|
if (roleCheck.length === 0) {
|
|
throw new ValidationError('유효하지 않은 역할 ID입니다');
|
|
}
|
|
updates.push('role_id = ?');
|
|
values.push(role_id);
|
|
logger.info('role_id로 역할 변경', { userId: id, role_id, role_name: roleCheck[0].name });
|
|
} else if (role) {
|
|
// role 문자열을 role_id로 변환 (하위 호환성)
|
|
const roleNameMap = {
|
|
'admin': 'Admin',
|
|
'system': 'System Admin',
|
|
'user': 'User',
|
|
'guest': 'Guest',
|
|
'group_leader': 'User', // 임시 매핑
|
|
'worker': 'User' // 임시 매핑
|
|
};
|
|
const roleName = roleNameMap[role.toLowerCase()] || role;
|
|
const [roleCheck] = await db.execute('SELECT id FROM roles WHERE name = ?', [roleName]);
|
|
|
|
if (roleCheck.length === 0) {
|
|
throw new ValidationError(`유효하지 않은 권한입니다: ${role}`);
|
|
}
|
|
updates.push('role_id = ?');
|
|
values.push(roleCheck[0].id);
|
|
logger.info('role 문자열로 역할 변경', { userId: id, role, role_id: roleCheck[0].id });
|
|
}
|
|
|
|
if (password) {
|
|
if (password.length < 6) {
|
|
throw new ValidationError('비밀번호는 최소 6자 이상이어야 합니다');
|
|
}
|
|
const hashedPassword = await bcrypt.hash(password, 10);
|
|
updates.push('password = ?');
|
|
values.push(hashedPassword);
|
|
}
|
|
|
|
// worker_id 업데이트 (null도 허용 - 연결 해제)
|
|
if (worker_id !== undefined) {
|
|
if (worker_id !== null) {
|
|
// worker_id가 유효한지 확인
|
|
const [workerCheck] = await db.execute('SELECT worker_id, worker_name FROM workers WHERE worker_id = ?', [worker_id]);
|
|
if (workerCheck.length === 0) {
|
|
throw new ValidationError('유효하지 않은 작업자 ID입니다');
|
|
}
|
|
logger.info('작업자 연결', { userId: id, worker_id, worker_name: workerCheck[0].worker_name });
|
|
} else {
|
|
logger.info('작업자 연결 해제', { userId: id });
|
|
}
|
|
updates.push('worker_id = ?');
|
|
values.push(worker_id);
|
|
}
|
|
|
|
updates.push('updated_at = NOW()');
|
|
values.push(id);
|
|
|
|
const updateQuery = `UPDATE users SET ${updates.join(', ')} WHERE user_id = ?`;
|
|
|
|
logger.info('실행할 UPDATE 쿼리', { query: updateQuery, values });
|
|
await db.execute(updateQuery, values);
|
|
|
|
logger.info('사용자 수정 성공', {
|
|
userId: id,
|
|
username: existing[0].username,
|
|
updatedFields: Object.keys(req.body),
|
|
updatedBy: req.user.username
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: { user_id: id },
|
|
message: '사용자 정보가 성공적으로 수정되었습니다'
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof NotFoundError || error instanceof ValidationError || error instanceof ConflictError) {
|
|
throw error;
|
|
}
|
|
logger.error('사용자 수정 실패', { userId: id, error: error.message, stack: error.stack });
|
|
throw new DatabaseError('사용자 정보를 수정하는데 실패했습니다');
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 사용자 상태 변경 (활성화/비활성화)
|
|
*/
|
|
const updateUserStatus = asyncHandler(async (req, res) => {
|
|
checkAdminPermission(req.user);
|
|
|
|
const { id } = req.params;
|
|
const { is_active } = req.body;
|
|
|
|
if (!id || isNaN(id)) {
|
|
throw new ValidationError('유효하지 않은 사용자 ID입니다');
|
|
}
|
|
|
|
if (is_active === undefined || ![0, 1, true, false].includes(is_active)) {
|
|
throw new ValidationError('유효하지 않은 활성 상태 값입니다');
|
|
}
|
|
|
|
const activeValue = is_active === true || is_active === 1 ? 1 : 0;
|
|
|
|
// 자기 자신 비활성화 방지
|
|
if (parseInt(id) === req.user.user_id && activeValue === 0) {
|
|
throw new ValidationError('자기 자신을 비활성화할 수 없습니다');
|
|
}
|
|
|
|
logger.info('사용자 상태 변경 요청', { userId: id, is_active: activeValue });
|
|
|
|
const { getDb } = require('../dbPool');
|
|
const db = await getDb();
|
|
|
|
try {
|
|
// 사용자 존재 확인
|
|
const checkQuery = 'SELECT user_id, username, is_active FROM users WHERE user_id = ?';
|
|
const [users] = await db.execute(checkQuery, [id]);
|
|
|
|
if (users.length === 0) {
|
|
throw new NotFoundError('사용자를 찾을 수 없습니다');
|
|
}
|
|
|
|
// 상태 변경이 필요한지 확인
|
|
if (users[0].is_active === activeValue) {
|
|
const status = activeValue === 1 ? '활성' : '비활성';
|
|
throw new ValidationError(`사용자가 이미 ${status} 상태입니다`);
|
|
}
|
|
|
|
const query = 'UPDATE users SET is_active = ?, updated_at = NOW() WHERE user_id = ?';
|
|
await db.execute(query, [activeValue, id]);
|
|
|
|
const statusText = activeValue === 1 ? '활성화' : '비활성화';
|
|
|
|
logger.info(`사용자 ${statusText} 성공`, {
|
|
userId: id,
|
|
username: users[0].username,
|
|
newStatus: activeValue,
|
|
updatedBy: req.user.username
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: { user_id: id, is_active: activeValue },
|
|
message: `사용자가 성공적으로 ${statusText}되었습니다`
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof NotFoundError || error instanceof ValidationError) {
|
|
throw error;
|
|
}
|
|
logger.error('사용자 상태 변경 실패', { userId: id, error: error.message });
|
|
throw new DatabaseError('사용자 상태를 변경하는데 실패했습니다');
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 사용자 삭제 (Soft Delete)
|
|
*/
|
|
const deleteUser = asyncHandler(async (req, res) => {
|
|
checkAdminPermission(req.user);
|
|
|
|
const { id } = req.params;
|
|
|
|
if (!id || isNaN(id)) {
|
|
throw new ValidationError('유효하지 않은 사용자 ID입니다');
|
|
}
|
|
|
|
// 자기 자신 삭제 방지
|
|
if (req.user && req.user.user_id == id) {
|
|
throw new ValidationError('자기 자신은 삭제할 수 없습니다');
|
|
}
|
|
|
|
logger.info('사용자 삭제 요청', { userId: id });
|
|
|
|
const { getDb } = require('../dbPool');
|
|
const db = await getDb();
|
|
|
|
try {
|
|
// 사용자 존재 확인
|
|
const checkQuery = 'SELECT user_id, username, is_active FROM users WHERE user_id = ?';
|
|
const [users] = await db.execute(checkQuery, [id]);
|
|
|
|
if (users.length === 0) {
|
|
throw new NotFoundError('사용자를 찾을 수 없습니다');
|
|
}
|
|
|
|
if (users[0].is_active === 0) {
|
|
throw new ValidationError('이미 비활성화된 사용자입니다');
|
|
}
|
|
|
|
// Soft Delete (is_active = 0)
|
|
const query = 'UPDATE users SET is_active = 0, updated_at = NOW() WHERE user_id = ?';
|
|
await db.execute(query, [id]);
|
|
|
|
logger.info('사용자 비활성화 성공', {
|
|
userId: id,
|
|
username: users[0].username,
|
|
deletedBy: req.user.username
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: { user_id: id },
|
|
message: '사용자가 성공적으로 비활성화되었습니다'
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof NotFoundError || error instanceof ValidationError) {
|
|
throw error;
|
|
}
|
|
logger.error('사용자 비활성화 실패', { userId: id, error: error.message });
|
|
throw new DatabaseError('사용자를 비활성화하는데 실패했습니다');
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 사용자 영구 삭제 (Hard Delete)
|
|
*/
|
|
const permanentDeleteUser = asyncHandler(async (req, res) => {
|
|
checkAdminPermission(req.user);
|
|
|
|
const { id } = req.params;
|
|
|
|
if (!id || isNaN(id)) {
|
|
throw new ValidationError('유효하지 않은 사용자 ID입니다');
|
|
}
|
|
|
|
// 자기 자신 삭제 방지
|
|
if (req.user && req.user.user_id == id) {
|
|
throw new ValidationError('자기 자신은 삭제할 수 없습니다');
|
|
}
|
|
|
|
logger.info('사용자 영구 삭제 요청', { userId: id });
|
|
|
|
const { getDb } = require('../dbPool');
|
|
const db = await getDb();
|
|
|
|
try {
|
|
// 사용자 존재 확인
|
|
const checkQuery = 'SELECT user_id, username FROM users WHERE user_id = ?';
|
|
const [users] = await db.execute(checkQuery, [id]);
|
|
|
|
if (users.length === 0) {
|
|
throw new NotFoundError('사용자를 찾을 수 없습니다');
|
|
}
|
|
|
|
const username = users[0].username;
|
|
|
|
// 관련 데이터 삭제 (외래 키 제약 조건 때문에 순서 중요)
|
|
// 1. 로그인 로그 삭제
|
|
await db.execute('DELETE FROM login_logs WHERE user_id = ?', [id]);
|
|
|
|
// 2. 페이지 접근 권한 삭제
|
|
await db.execute('DELETE FROM user_page_access WHERE user_id = ?', [id]);
|
|
|
|
// 3. 사용자 삭제
|
|
await db.execute('DELETE FROM users WHERE user_id = ?', [id]);
|
|
|
|
logger.info('사용자 영구 삭제 성공', {
|
|
userId: id,
|
|
username: username,
|
|
deletedBy: req.user.username
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: { user_id: id },
|
|
message: `사용자 "${username}"이(가) 영구적으로 삭제되었습니다`
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof NotFoundError || error instanceof ValidationError) {
|
|
throw error;
|
|
}
|
|
logger.error('사용자 영구 삭제 실패', { userId: id, error: error.message });
|
|
throw new DatabaseError('사용자를 영구 삭제하는데 실패했습니다');
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 사용자의 페이지 접근 권한 조회
|
|
*/
|
|
const getUserPageAccess = asyncHandler(async (req, res) => {
|
|
const { id } = req.params;
|
|
|
|
if (!id || isNaN(id)) {
|
|
throw new ValidationError('유효하지 않은 사용자 ID입니다');
|
|
}
|
|
|
|
logger.info('사용자 페이지 권한 조회 요청', { userId: id });
|
|
|
|
const { getDb } = require('../dbPool');
|
|
const db = await getDb();
|
|
|
|
try {
|
|
// 권한 조회: user_page_access에 명시적 권한이 있으면 사용, 없으면 is_default_accessible 사용
|
|
const query = `
|
|
SELECT
|
|
p.id as page_id,
|
|
p.page_key,
|
|
p.page_name,
|
|
p.page_path,
|
|
p.category,
|
|
p.is_default_accessible,
|
|
COALESCE(upa.can_access, p.is_default_accessible) as can_access
|
|
FROM pages p
|
|
LEFT JOIN user_page_access upa ON p.id = upa.page_id AND upa.user_id = ?
|
|
ORDER BY p.category, p.display_order
|
|
`;
|
|
|
|
const [pageAccess] = await db.execute(query, [id]);
|
|
|
|
logger.info('사용자 페이지 권한 조회 성공', { userId: id, pageCount: pageAccess.length });
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
pageAccess
|
|
},
|
|
message: '페이지 권한 조회 성공'
|
|
});
|
|
} catch (error) {
|
|
logger.error('사용자 페이지 권한 조회 실패', { userId: id, error: error.message });
|
|
throw new DatabaseError('페이지 권한을 조회하는데 실패했습니다');
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 사용자의 페이지 접근 권한 업데이트
|
|
*/
|
|
const updateUserPageAccess = asyncHandler(async (req, res) => {
|
|
checkAdminPermission(req.user);
|
|
|
|
const { id } = req.params;
|
|
const { pageAccess } = req.body;
|
|
|
|
if (!id || isNaN(id)) {
|
|
throw new ValidationError('유효하지 않은 사용자 ID입니다');
|
|
}
|
|
|
|
if (!Array.isArray(pageAccess)) {
|
|
throw new ValidationError('pageAccess는 배열이어야 합니다');
|
|
}
|
|
|
|
logger.info('사용자 페이지 권한 업데이트 요청', {
|
|
userId: id,
|
|
pageCount: pageAccess.length,
|
|
updatedBy: req.user.username
|
|
});
|
|
|
|
const { getDb } = require('../dbPool');
|
|
const db = await getDb();
|
|
|
|
try {
|
|
// 트랜잭션 시작
|
|
await db.query('START TRANSACTION');
|
|
|
|
// 기존 권한 삭제
|
|
await db.execute('DELETE FROM user_page_access WHERE user_id = ?', [id]);
|
|
|
|
// 새 권한 삽입
|
|
if (pageAccess.length > 0) {
|
|
const values = pageAccess.map(p => [id, p.page_id, p.can_access]);
|
|
const placeholders = values.map(() => '(?, ?, ?)').join(', ');
|
|
const flatValues = values.flat();
|
|
|
|
await db.execute(
|
|
`INSERT INTO user_page_access (user_id, page_id, can_access) VALUES ${placeholders}`,
|
|
flatValues
|
|
);
|
|
}
|
|
|
|
// 커밋
|
|
await db.query('COMMIT');
|
|
|
|
logger.info('사용자 페이지 권한 업데이트 성공', {
|
|
userId: id,
|
|
pageCount: pageAccess.length,
|
|
updatedBy: req.user.username
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: { user_id: id },
|
|
message: '페이지 권한이 성공적으로 업데이트되었습니다'
|
|
});
|
|
} catch (error) {
|
|
// 롤백
|
|
await db.query('ROLLBACK');
|
|
logger.error('사용자 페이지 권한 업데이트 실패', { userId: id, error: error.message });
|
|
throw new DatabaseError('페이지 권한을 업데이트하는데 실패했습니다');
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 사용자 비밀번호 초기화 (000000)
|
|
*/
|
|
const resetUserPassword = asyncHandler(async (req, res) => {
|
|
checkAdminPermission(req.user);
|
|
|
|
const { id } = req.params;
|
|
|
|
if (!id || isNaN(id)) {
|
|
throw new ValidationError('유효하지 않은 사용자 ID입니다');
|
|
}
|
|
|
|
const { getDb } = require('../dbPool');
|
|
const db = await getDb();
|
|
|
|
try {
|
|
// 사용자 존재 확인
|
|
const [existing] = await db.execute('SELECT user_id, username FROM users WHERE user_id = ?', [id]);
|
|
|
|
if (existing.length === 0) {
|
|
throw new NotFoundError('사용자를 찾을 수 없습니다');
|
|
}
|
|
|
|
// 비밀번호를 000000으로 초기화
|
|
const hashedPassword = await bcrypt.hash('000000', 10);
|
|
await db.execute(
|
|
'UPDATE users SET password = ?, password_changed_at = NULL, updated_at = NOW() WHERE user_id = ?',
|
|
[hashedPassword, id]
|
|
);
|
|
|
|
logger.info('사용자 비밀번호 초기화 성공', {
|
|
userId: id,
|
|
username: existing[0].username,
|
|
resetBy: req.user.username
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '비밀번호가 000000으로 초기화되었습니다'
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof NotFoundError || error instanceof ValidationError) {
|
|
throw error;
|
|
}
|
|
logger.error('비밀번호 초기화 실패', { userId: id, error: error.message });
|
|
throw new DatabaseError('비밀번호 초기화에 실패했습니다');
|
|
}
|
|
});
|
|
|
|
module.exports = {
|
|
getAllUsers,
|
|
getUserById,
|
|
createUser,
|
|
updateUser,
|
|
updateUserStatus,
|
|
deleteUser,
|
|
permanentDeleteUser,
|
|
getUserPageAccess,
|
|
updateUserPageAccess,
|
|
resetUserPassword
|
|
};
|