refactor: Phase 2-2 - 사용자 관리 모듈화 및 설정 파일 분리
사용자 관리 API를 컨트롤러/라우터 패턴으로 리팩토링하고, CORS 및 보안 설정을 별도 파일로 분리하여 코드 구조 개선 주요 변경사항: - userController.js: 새로운 에러 핸들링 및 로깅 시스템 적용 * ValidationError, NotFoundError, ConflictError 등 커스텀 에러 사용 * logger 유틸리티로 구조화된 로깅 * 관리자 권한 검증 헬퍼 함수 추가 - index.js: 인라인 사용자 관리 라우트 제거 (888 → 605 lines) * 283줄 감소로 코드 가독성 대폭 향상 * userRoutes 모듈 import 및 사용 - userRoutes.js: 문서화 및 로깅 개선 * JSDoc 헤더 추가 * adminOnly 미들웨어에 로깅 추가 신규 파일: - config/cors.js: CORS 정책 설정 (허용 origin, 메소드, 헤더) - config/security.js: Helmet 보안 헤더 설정 (CSP, HSTS, XSS 방지) - middlewares/activityLogger.js: HTTP 요청/응답 활동 로깅 파일 통계: - 3개 파일 수정, 3개 파일 추가 - +437 -480 (net -43 lines) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -204,6 +204,7 @@ const workAnalysisRoutes = require('./routes/workAnalysisRoutes');
|
||||
const analysisRoutes = require('./routes/analysisRoutes');
|
||||
const systemRoutes = require('./routes/systemRoutes'); // 새로운 분석 라우트
|
||||
const performanceRoutes = require('./routes/performanceRoutes'); // 성능 모니터링 라우트
|
||||
const userRoutes = require('./routes/userRoutes'); // 사용자 관리 라우트
|
||||
|
||||
// 🔒 인증 미들웨어 가져오기
|
||||
const { verifyToken } = require('./middlewares/authMiddleware');
|
||||
@@ -344,291 +345,7 @@ app.use('/api/projects', projectRoutes);
|
||||
app.use('/api/tools', toolsRoute);
|
||||
|
||||
// 👤 사용자 관리 API (관리자 전용)
|
||||
app.get('/api/users', async (req, res) => {
|
||||
try {
|
||||
// 관리자 권한 확인
|
||||
if (!req.user || !['admin', 'system'].includes(req.user.access_level)) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: '관리자 권한이 필요합니다.'
|
||||
});
|
||||
}
|
||||
|
||||
const { getDb } = require('./dbPool');
|
||||
const db = await getDb();
|
||||
const [users] = await db.execute(`
|
||||
SELECT user_id, username, name, role, access_level, is_active, created_at, worker_id
|
||||
FROM users
|
||||
WHERE is_active = 1
|
||||
ORDER BY user_id
|
||||
`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '사용자 목록 조회 성공',
|
||||
data: users
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('사용자 목록 조회 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: '사용자 목록을 불러올 수 없습니다.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 👤 사용자 정보 수정 API (관리자 전용)
|
||||
app.put('/api/users/:id', async (req, res) => {
|
||||
try {
|
||||
// 관리자 권한 확인
|
||||
if (!req.user || !['admin', 'system'].includes(req.user.access_level)) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: '관리자 권한이 필요합니다.'
|
||||
});
|
||||
}
|
||||
|
||||
const userId = req.params.id;
|
||||
const { username, name, role, access_level, is_active } = req.body;
|
||||
|
||||
// undefined 값을 null로 변환
|
||||
const safeUsername = username !== undefined ? username : null;
|
||||
const safeName = name !== undefined ? name : null;
|
||||
const safeRole = role !== undefined ? role : null;
|
||||
const safeAccessLevel = access_level !== undefined ? access_level : null;
|
||||
const safeIsActive = is_active !== undefined ? is_active : null;
|
||||
|
||||
const { getDb } = require('./dbPool');
|
||||
const db = await getDb();
|
||||
|
||||
const [result] = await db.execute(`
|
||||
UPDATE users
|
||||
SET username = ?, name = ?, role = ?, access_level = ?, is_active = ?, updated_at = NOW()
|
||||
WHERE user_id = ?
|
||||
`, [safeUsername, safeName, safeRole, safeAccessLevel, safeIsActive, userId]);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: '사용자를 찾을 수 없습니다.'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '사용자 정보가 성공적으로 수정되었습니다.'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('사용자 정보 수정 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: '사용자 정보 수정에 실패했습니다.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 👤 사용자 삭제 API (관리자 전용)
|
||||
app.delete('/api/users/:id', async (req, res) => {
|
||||
try {
|
||||
// 관리자 권한 확인
|
||||
if (!req.user || !['admin', 'system'].includes(req.user.access_level)) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: '관리자 권한이 필요합니다.'
|
||||
});
|
||||
}
|
||||
|
||||
const userId = req.params.id;
|
||||
|
||||
// 자기 자신은 삭제할 수 없도록 방지
|
||||
if (parseInt(userId) === req.user.user_id) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '자기 자신은 삭제할 수 없습니다.'
|
||||
});
|
||||
}
|
||||
|
||||
const { getDb } = require('./dbPool');
|
||||
const db = await getDb();
|
||||
|
||||
// 사용자 존재 여부 확인
|
||||
const [existingUser] = await db.execute(`
|
||||
SELECT user_id, username FROM users WHERE user_id = ?
|
||||
`, [userId]);
|
||||
|
||||
if (existingUser.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: '사용자를 찾을 수 없습니다.'
|
||||
});
|
||||
}
|
||||
|
||||
// 사용자 삭제 (실제로는 비활성화)
|
||||
const [result] = await db.execute(`
|
||||
UPDATE users
|
||||
SET is_active = 0, updated_at = NOW()
|
||||
WHERE user_id = ?
|
||||
`, [userId]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `사용자 '${existingUser[0].username}'가 성공적으로 비활성화되었습니다.`
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('사용자 삭제 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: '사용자 삭제에 실패했습니다.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 👤 사용자 생성 API (관리자 전용)
|
||||
app.post('/api/users', async (req, res) => {
|
||||
try {
|
||||
// 관리자 권한 확인
|
||||
if (!req.user || !['admin', 'system'].includes(req.user.access_level)) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: '관리자 권한이 필요합니다.'
|
||||
});
|
||||
}
|
||||
|
||||
const { username, name, role, access_level, password } = req.body;
|
||||
|
||||
// 필수 필드 검증
|
||||
if (!username || !name || !password) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '사용자명, 이름, 비밀번호는 필수 입력 항목입니다.'
|
||||
});
|
||||
}
|
||||
|
||||
const { getDb } = require('./dbPool');
|
||||
const db = await getDb();
|
||||
|
||||
// 중복 사용자명 확인
|
||||
const [existingUser] = await db.execute(`
|
||||
SELECT user_id FROM users WHERE username = ?
|
||||
`, [username]);
|
||||
|
||||
if (existingUser.length > 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '이미 존재하는 사용자명입니다.'
|
||||
});
|
||||
}
|
||||
|
||||
// 비밀번호 해시화
|
||||
const bcrypt = require('bcryptjs');
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
// undefined 값을 null로 변환 및 role에 따른 access_level 자동 설정
|
||||
const safeRole = role !== undefined ? role : null;
|
||||
|
||||
// role에 따라 access_level 자동 설정
|
||||
let safeAccessLevel;
|
||||
if (access_level !== undefined) {
|
||||
safeAccessLevel = access_level;
|
||||
} else if (safeRole === 'admin') {
|
||||
safeAccessLevel = 'admin';
|
||||
} else if (safeRole === 'leader' || safeRole === 'group_leader') {
|
||||
safeAccessLevel = 'group_leader';
|
||||
} else {
|
||||
safeAccessLevel = 'worker';
|
||||
}
|
||||
|
||||
// 사용자 생성
|
||||
const [result] = await db.execute(`
|
||||
INSERT INTO users (username, name, password, role, access_level, is_active, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, 1, NOW(), NOW())
|
||||
`, [username, name, hashedPassword, safeRole, safeAccessLevel]);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: `사용자 '${username}'가 성공적으로 생성되었습니다.`,
|
||||
data: {
|
||||
user_id: result.insertId,
|
||||
username: username,
|
||||
name: name,
|
||||
role: safeRole,
|
||||
access_level: safeAccessLevel,
|
||||
is_active: true
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('사용자 생성 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: '사용자 생성에 실패했습니다.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 👤 사용자 상태 변경 API (관리자 전용)
|
||||
app.put('/api/users/:id/status', async (req, res) => {
|
||||
try {
|
||||
// 관리자 권한 확인
|
||||
if (!req.user || !['admin', 'system'].includes(req.user.access_level)) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: '관리자 권한이 필요합니다.'
|
||||
});
|
||||
}
|
||||
|
||||
const userId = req.params.id;
|
||||
const { is_active } = req.body;
|
||||
|
||||
// 자기 자신의 상태는 변경할 수 없도록 방지
|
||||
if (parseInt(userId) === req.user.user_id) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '자기 자신의 상태는 변경할 수 없습니다.'
|
||||
});
|
||||
}
|
||||
|
||||
const { getDb } = require('./dbPool');
|
||||
const db = await getDb();
|
||||
|
||||
// 사용자 존재 여부 확인
|
||||
const [existingUser] = await db.execute(`
|
||||
SELECT user_id, username, is_active FROM users WHERE user_id = ?
|
||||
`, [userId]);
|
||||
|
||||
if (existingUser.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: '사용자를 찾을 수 없습니다.'
|
||||
});
|
||||
}
|
||||
|
||||
// 상태 변경
|
||||
const newStatus = is_active !== undefined ? is_active : !existingUser[0].is_active;
|
||||
const [result] = await db.execute(`
|
||||
UPDATE users
|
||||
SET is_active = ?, updated_at = NOW()
|
||||
WHERE user_id = ?
|
||||
`, [newStatus, userId]);
|
||||
|
||||
const statusText = newStatus ? '활성화' : '비활성화';
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `사용자 '${existingUser[0].username}'가 성공적으로 ${statusText}되었습니다.`,
|
||||
data: {
|
||||
user_id: parseInt(userId),
|
||||
username: existingUser[0].username,
|
||||
is_active: newStatus
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('사용자 상태 변경 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: '사용자 상태 변경에 실패했습니다.'
|
||||
});
|
||||
}
|
||||
});
|
||||
app.use('/api/users', userRoutes);
|
||||
|
||||
// 📤 파일 업로드
|
||||
app.use('/api', uploadBgRoutes);
|
||||
|
||||
Reference in New Issue
Block a user