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

@@ -0,0 +1,9 @@
// utils/access.js
exports.requireAccess = (...allowed) => {
return (req, res, next) => {
if (!req.user || !allowed.includes(req.user.access_level)) {
return res.status(403).json({ error: '접근 권한이 없습니다' });
}
next();
};
};

View File

@@ -0,0 +1,33 @@
// middlewares/accessMiddleware.js
// 권한 레벨 정의
const ACCESS_LEVELS = {
worker: 1,
group_leader: 2,
support_team: 3,
admin: 4,
system: 5
};
const requireAccess = (requiredLevel) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: '인증이 필요합니다.' });
}
const userLevel = ACCESS_LEVELS[req.user.access_level] || 0;
const required = ACCESS_LEVELS[requiredLevel] || 999;
if (userLevel < required) {
return res.status(403).json({
error: '접근 권한이 없습니다.',
required: requiredLevel,
current: req.user.access_level
});
}
next();
};
};
module.exports = { requireAccess, ACCESS_LEVELS };

View File

@@ -0,0 +1,20 @@
// 📁 middlewares/auth.js
const jwt = require('jsonwebtoken');
require('dotenv').config();
module.exports = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader?.split(' ')[1];
if (!token) {
return res.status(401).json({ message: 'Access token required' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded; // 다른 미들웨어에서 사용할 수 있게 설정
next();
} catch (err) {
return res.status(403).json({ message: 'Invalid token' });
}
};

View File

@@ -0,0 +1,89 @@
const jwt = require('jsonwebtoken');
exports.verifyToken = (req, res, next) => {
try {
const authHeader = req.headers['authorization'];
if (!authHeader) {
return res.status(401).json({ error: '토큰 없음' });
}
const token = authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: '토큰 누락' });
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next(); // ✅ 반드시 next 호출
} catch (err) {
console.error('[verifyToken 오류]', err.message);
return res.status(403).json({ error: '토큰 검증 실패', detail: err.message });
}
};
/**
* Admin 등급 이상 권한 체크 미들웨어
*/
exports.requireAdmin = (req, res, next) => {
try {
if (!req.user) {
return res.status(401).json({
error: '인증 필요',
message: '먼저 로그인해주세요.'
});
}
const userRole = req.user.role;
const adminRoles = ['admin', 'system'];
if (!adminRoles.includes(userRole)) {
return res.status(403).json({
error: '권한 부족',
message: '관리자 권한이 필요합니다.',
required: 'admin 또는 system',
current: userRole
});
}
console.log(`✅ Admin 권한 확인: ${req.user.username} (${userRole})`);
next();
} catch (err) {
console.error('[requireAdmin 오류]', err.message);
return res.status(500).json({
error: '권한 확인 중 오류 발생',
detail: err.message
});
}
};
/**
* System 등급 권한 체크 미들웨어
*/
exports.requireSystem = (req, res, next) => {
try {
if (!req.user) {
return res.status(401).json({
error: '인증 필요',
message: '먼저 로그인해주세요.'
});
}
if (req.user.role !== 'system') {
return res.status(403).json({
error: '권한 부족',
message: '시스템 관리자 권한이 필요합니다.',
required: 'system',
current: req.user.role
});
}
console.log(`✅ System 권한 확인: ${req.user.username}`);
next();
} catch (err) {
console.error('[requireSystem 오류]', err.message);
return res.status(500).json({
error: '권한 확인 중 오류 발생',
detail: err.message
});
}
};

View File

@@ -0,0 +1,16 @@
// middlewares/errorHandler.js
exports.errorHandler = (err, req, res, next) => {
console.error('Error:', err);
if (process.env.NODE_ENV === 'development') {
res.status(500).json({
error: '서버 오류가 발생했습니다.',
details: err.message,
stack: err.stack
});
} else {
res.status(500).json({
error: '서버 오류가 발생했습니다.'
});
}
};