fix: 캘린더 모달 중복 카드 문제 및 삭제 권한 개선

- monthly_worker_status 조회 시 GROUP BY로 중복 데이터 합산
- 작업보고서 삭제 권한을 그룹장 이상으로 제한 (admin, system, group_leader)
- 중복 데이터 정리를 위한 마이그레이션 SQL 추가 (009_fix_duplicate_monthly_status.sql)
- synology_deployment 버전에도 동일 수정 적용
This commit is contained in:
Hyungi Ahn
2025-12-02 13:08:44 +09:00
parent beaffcad49
commit a9bce9d20b
419 changed files with 275129 additions and 394 deletions

View File

@@ -343,6 +343,293 @@ app.use('/api/performance', performanceRoutes);
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', uploadBgRoutes);