// 시스템 관리 컨트롤러 const { getDb } = require('../dbPool'); const bcrypt = require('bcryptjs'); const { ApiError, asyncHandler, handleDatabaseError } = require('../utils/errorHandler'); const { validateSchema, schemas } = require('../utils/validator'); /** * 시스템 상태 확인 */ exports.getSystemStatus = asyncHandler(async (req, res) => { try { const db = await getDb(); // 데이터베이스 연결 상태 확인 const [dbStatus] = await db.query('SELECT 1 as status'); // 시스템 상태 정보 const systemStatus = { server: 'online', database: dbStatus.length > 0 ? 'online' : 'offline' }; res.health('healthy', systemStatus); } catch (error) { handleDatabaseError(error, '시스템 상태 확인'); } }); /** * 데이터베이스 상태 확인 */ exports.getDatabaseStatus = asyncHandler(async (req, res) => { try { const db = await getDb(); // 데이터베이스 연결 수 확인 const [connections] = await db.query('SHOW STATUS LIKE "Threads_connected"'); const [maxConnections] = await db.query('SHOW VARIABLES LIKE "max_connections"'); // 데이터베이스 크기 확인 const [dbSize] = await db.query(` SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS size_mb FROM information_schema.tables WHERE table_schema = DATABASE() `); const dbStatus = { status: 'online', connections: parseInt(connections[0]?.Value || 0), max_connections: parseInt(maxConnections[0]?.Value || 0), size_mb: dbSize[0]?.size_mb || 0 }; res.success(dbStatus, '데이터베이스 상태 조회 성공'); } catch (error) { handleDatabaseError(error, '데이터베이스 상태 확인'); } }); /** * 시스템 알림 조회 */ exports.getSystemAlerts = async (req, res) => { try { const db = await getDb(); // 최근 실패한 로그인 시도 const [failedLogins] = await db.query(` SELECT COUNT(*) as count FROM login_logs WHERE login_status = 'failed' AND login_time > DATE_SUB(NOW(), INTERVAL 1 HOUR) `); // 비활성 사용자 수 const [inactiveusers] = await db.query(` SELECT COUNT(*) as count FROM users WHERE is_active = 0 `); const alerts = []; if (failedLogins[0]?.count > 5) { alerts.push({ type: 'security', level: 'warning', message: `최근 1시간 동안 ${failedLogins[0].count}회의 로그인 실패가 발생했습니다.`, timestamp: new Date().toISOString() }); } if (inactiveusers[0]?.count > 0) { alerts.push({ type: 'user', level: 'info', message: `${inactiveusers[0].count}명의 비활성 사용자가 있습니다.`, timestamp: new Date().toISOString() }); } res.json({ success: true, alerts: alerts }); } catch (error) { console.error('시스템 알림 조회 오류:', error); res.status(500).json({ success: false, error: '시스템 알림을 조회할 수 없습니다.' }); } }; /** * 최근 시스템 활동 조회 */ exports.getRecentActivities = async (req, res) => { try { const db = await getDb(); // 최근 로그인 활동 const [loginActivities] = await db.query(` SELECT ll.login_time as created_at, u.name as user_name, ll.login_status, ll.ip_address, 'login' as activity_type FROM login_logs ll LEFT JOIN users u ON ll.user_id = u.user_id ORDER BY ll.login_time DESC LIMIT 10 `); // 비밀번호 변경 활동 const [passwordActivities] = await db.query(` SELECT pcl.changed_at as created_at, u.name as user_name, pcl.change_type, 'password_change' as activity_type FROM password_change_logs pcl LEFT JOIN users u ON pcl.user_id = u.user_id ORDER BY pcl.changed_at DESC LIMIT 5 `); // 활동 통합 및 정렬 const activities = [ ...loginActivities.map(activity => ({ type: activity.login_status === 'success' ? 'login' : 'login_failed', title: activity.login_status === 'success' ? `${activity.user_name || '알 수 없는 사용자'} 로그인` : `로그인 실패 (${activity.ip_address})`, description: activity.login_status === 'success' ? `IP: ${activity.ip_address}` : `사용자: ${activity.user_name || '알 수 없음'}`, created_at: activity.created_at })), ...passwordActivities.map(activity => ({ type: 'password_change', title: `${activity.user_name || '알 수 없는 사용자'} 비밀번호 변경`, description: `변경 유형: ${activity.change_type}`, created_at: activity.created_at })) ]; // 시간순 정렬 activities.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); res.json({ success: true, data: activities.slice(0, 15) }); } catch (error) { console.error('최근 활동 조회 오류:', error); res.status(500).json({ success: false, error: '최근 활동을 조회할 수 없습니다.' }); } }; /** * 사용자 통계 조회 */ exports.getUserStats = async (req, res) => { try { const db = await getDb(); // 전체 사용자 수 const [totalusers] = await db.query('SELECT COUNT(*) as count FROM users'); // 활성 사용자 수 const [activeusers] = await db.query('SELECT COUNT(*) as count FROM users WHERE is_active = 1'); // 최근 24시간 로그인 사용자 수 const [recentLogins] = await db.query(` SELECT COUNT(DISTINCT user_id) as count FROM login_logs WHERE login_status = 'success' AND login_time > DATE_SUB(NOW(), INTERVAL 24 HOUR) `); // 권한별 사용자 수 const [roleStats] = await db.query(` SELECT role, COUNT(*) as count FROM users WHERE is_active = 1 GROUP BY role `); res.json({ success: true, data: { total: totalusers[0]?.count || 0, active: activeusers[0]?.count || 0, recent_logins: recentLogins[0]?.count || 0, by_role: roleStats } }); } catch (error) { console.error('사용자 통계 조회 오류:', error); res.status(500).json({ success: false, error: '사용자 통계를 조회할 수 없습니다.' }); } }; /** * 모든 사용자 목록 조회 (시스템 관리자용) */ exports.getAllUsers = asyncHandler(async (req, res) => { try { const db = await getDb(); const [users] = await db.query(` SELECT user_id, username, name, email, role, access_level, worker_id, is_active, last_login_at, failed_login_attempts, locked_until, created_at, updated_at FROM users ORDER BY created_at DESC `); res.list(users, '사용자 목록 조회 성공'); } catch (error) { handleDatabaseError(error, '사용자 목록 조회'); } }); /** * 사용자 생성 */ exports.createUser = asyncHandler(async (req, res) => { const { username, password, name, email, role, access_level, worker_id } = req.body; // 스키마 기반 유효성 검사 validateSchema(req.body, schemas.createUser); try { const db = await getDb(); // 사용자명 중복 확인 const [existing] = await db.query('SELECT user_id FROM users WHERE username = ?', [username]); if (existing.length > 0) { throw new ApiError('이미 존재하는 사용자명입니다.', 409); } // 이메일 중복 확인 (이메일이 제공된 경우) if (email) { const [existingEmail] = await db.query('SELECT user_id FROM users WHERE email = ?', [email]); if (existingEmail.length > 0) { throw new ApiError('이미 사용 중인 이메일입니다.', 409); } } // 비밀번호 해시화 const hashedPassword = await bcrypt.hash(password, 10); // 사용자 생성 const [result] = await db.query(` INSERT INTO users (username, password, name, email, role, access_level, worker_id, is_active, created_at, password_changed_at) VALUES (?, ?, ?, ?, ?, ?, ?, 1, NOW(), NOW()) `, [username, hashedPassword, name, email || null, role, access_level || role, worker_id || null]); // 비밀번호 변경 로그 기록 await db.query(` INSERT INTO password_change_logs (user_id, changed_by_user_id, changed_at, change_type) VALUES (?, ?, NOW(), 'initial') `, [result.insertId, req.user.user_id]); res.created({ user_id: result.insertId }, '사용자가 성공적으로 생성되었습니다.'); } catch (error) { handleDatabaseError(error, '사용자 생성'); } }); /** * 사용자 수정 */ exports.updateUser = async (req, res) => { try { const { id } = req.params; const { name, email, role, access_level, is_active, worker_id } = req.body; const db = await getDb(); // 사용자 존재 확인 const [user] = await db.query('SELECT user_id FROM users WHERE user_id = ?', [id]); if (user.length === 0) { return res.status(404).json({ success: false, error: '해당 사용자를 찾을 수 없습니다.' }); } // 이메일 중복 확인 (다른 사용자가 사용 중인지) if (email) { const [existingEmail] = await db.query( 'SELECT user_id FROM users WHERE email = ? AND user_id != ?', [email, id] ); if (existingEmail.length > 0) { return res.status(409).json({ success: false, error: '이미 사용 중인 이메일입니다.' }); } } // 사용자 정보 업데이트 await db.query(` UPDATE users SET name = ?, email = ?, role = ?, access_level = ?, is_active = ?, worker_id = ?, updated_at = NOW() WHERE user_id = ? `, [name, email || null, role, access_level || role, is_active ? 1 : 0, worker_id || null, id]); res.json({ success: true, message: '사용자 정보가 성공적으로 업데이트되었습니다.' }); } catch (error) { console.error('사용자 수정 오류:', error); res.status(500).json({ success: false, error: '사용자 수정 중 오류가 발생했습니다.' }); } }; /** * 사용자 삭제 */ exports.deleteUser = async (req, res) => { try { const { id } = req.params; const db = await getDb(); // 자기 자신 삭제 방지 if (parseInt(id) === req.user.user_id) { return res.status(400).json({ success: false, error: '자기 자신은 삭제할 수 없습니다.' }); } // 사용자 존재 확인 const [user] = await db.query('SELECT user_id, username FROM users WHERE user_id = ?', [id]); if (user.length === 0) { return res.status(404).json({ success: false, error: '해당 사용자를 찾을 수 없습니다.' }); } // 사용자 삭제 (관련 로그는 유지) await db.query('DELETE FROM users WHERE user_id = ?', [id]); res.json({ success: true, message: `사용자 '${user[0].username}'가 성공적으로 삭제되었습니다.` }); } catch (error) { console.error('사용자 삭제 오류:', error); res.status(500).json({ success: false, error: '사용자 삭제 중 오류가 발생했습니다.' }); } }; /** * 사용자 비밀번호 재설정 */ exports.resetUserPassword = async (req, res) => { try { const { id } = req.params; const { new_password } = req.body; const db = await getDb(); if (!new_password || new_password.length < 6) { return res.status(400).json({ success: false, error: '비밀번호는 최소 6자 이상이어야 합니다.' }); } // 사용자 존재 확인 const [user] = await db.query('SELECT user_id, username FROM users WHERE user_id = ?', [id]); if (user.length === 0) { return res.status(404).json({ success: false, error: '해당 사용자를 찾을 수 없습니다.' }); } // 비밀번호 해시화 const hashedPassword = await bcrypt.hash(new_password, 10); // 비밀번호 업데이트 await db.query(` UPDATE users SET password = ?, password_changed_at = NOW(), failed_login_attempts = 0, locked_until = NULL WHERE user_id = ? `, [hashedPassword, id]); // 비밀번호 변경 로그 기록 await db.query(` INSERT INTO password_change_logs (user_id, changed_by_user_id, changed_at, change_type) VALUES (?, ?, NOW(), 'admin') `, [id, req.user.user_id]); res.json({ success: true, message: `사용자 '${user[0].username}'의 비밀번호가 재설정되었습니다.` }); } catch (error) { console.error('비밀번호 재설정 오류:', error); res.status(500).json({ success: false, error: '비밀번호 재설정 중 오류가 발생했습니다.' }); } };