refactor: 보안 취약점 제거 + 데드코드 정리 + 프론트엔드 중복 통합

- 인증 없는 임시 엔드포인트 삭제 (index.js, healthRoutes.js, publicPaths)
- skipAuth 우회 라우트 삭제 (workAnalysis.js)
- 하드코딩 유저 백도어 삭제 (routes/auth.js)
- 안전체크 CRUD에 admin 권한 추가 (tbmRoutes.js)
- deprecated shim 3개 삭제 + 8개 소비 파일 import 정리 (auth.js 직접 참조)
- 미사용 pageAccessController, db.js, common/security.js 삭제
- escapeHtml() 5곳 로컬 중복 제거 → api-base.js 전역 사용
- userPageAccess_v2_v2 캐시 키 버그 수정 (app-init.js)
- system3 .bak 파일 삭제, PROGRESS.md 업데이트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-02-25 08:19:01 +09:00
parent 7637be33f3
commit 93edf9529a
28 changed files with 136 additions and 2192 deletions

View File

@@ -9,7 +9,7 @@
const swaggerUi = require('swagger-ui-express');
const swaggerSpec = require('./swagger');
const { verifyToken } = require('../middlewares/authMiddleware');
const { verifyToken } = require('../middlewares/auth');
const { activityLogger } = require('../middlewares/activityLogger');
const logger = require('../utils/logger');
@@ -107,10 +107,7 @@ function setupRoutes(app) {
'/api/setup/migrate-existing-data',
'/api/setup/check-data-status',
'/api/monthly-status/calendar',
'/api/monthly-status/daily-details',
'/api/migrate-work-type-id', // 임시 마이그레이션 - 실행 후 삭제!
'/api/diagnose-work-type-id', // 임시 진단 - 실행 후 삭제!
'/api/test-analysis' // 임시 분석 테스트 - 실행 후 삭제!
'/api/monthly-status/daily-details'
];
// 인증 미들웨어 - 공개 경로를 제외한 모든 API (rate limiter보다 먼저 실행)

View File

@@ -1,200 +0,0 @@
// controllers/pageAccessController.js
const PageAccessModel = require('../models/pageAccessModel');
const PageAccessController = {
// 사용자의 페이지 권한 조회
getUserPageAccess: (req, res) => {
const userId = parseInt(req.params.userId);
if (isNaN(userId)) {
return res.status(400).json({
success: false,
message: '유효하지 않은 사용자 ID입니다.'
});
}
PageAccessModel.getUserPageAccess(userId, (err, results) => {
if (err) {
console.error('페이지 권한 조회 오류:', err);
return res.status(500).json({
success: false,
message: '페이지 권한 조회 중 오류가 발생했습니다.',
error: err.message
});
}
res.json({
success: true,
data: results
});
});
},
// 모든 페이지 목록 조회
getAllPages: (req, res) => {
PageAccessModel.getAllPages((err, results) => {
if (err) {
console.error('페이지 목록 조회 오류:', err);
return res.status(500).json({
success: false,
message: '페이지 목록 조회 중 오류가 발생했습니다.',
error: err.message
});
}
res.json({
success: true,
data: results
});
});
},
// 페이지 권한 부여
grantPageAccess: (req, res) => {
const userId = parseInt(req.params.userId);
const { pageId } = req.body;
const grantedBy = req.user.user_id;
if (isNaN(userId) || !pageId) {
return res.status(400).json({
success: false,
message: '필수 파라미터가 누락되었습니다.'
});
}
PageAccessModel.grantPageAccess(userId, pageId, grantedBy, (err, result) => {
if (err) {
console.error('페이지 권한 부여 오류:', err);
return res.status(500).json({
success: false,
message: '페이지 권한 부여 중 오류가 발생했습니다.',
error: err.message
});
}
res.json({
success: true,
message: '페이지 권한이 부여되었습니다.',
data: result
});
});
},
// 페이지 권한 회수
revokePageAccess: (req, res) => {
const userId = parseInt(req.params.userId);
const pageId = parseInt(req.params.pageId);
if (isNaN(userId) || isNaN(pageId)) {
return res.status(400).json({
success: false,
message: '유효하지 않은 파라미터입니다.'
});
}
PageAccessModel.revokePageAccess(userId, pageId, (err, result) => {
if (err) {
console.error('페이지 권한 회수 오류:', err);
return res.status(500).json({
success: false,
message: '페이지 권한 회수 중 오류가 발생했습니다.',
error: err.message
});
}
res.json({
success: true,
message: '페이지 권한이 회수되었습니다.',
data: result
});
});
},
// 사용자 페이지 권한 일괄 설정
setUserPageAccess: (req, res) => {
const userId = parseInt(req.params.userId);
const { pageIds } = req.body;
const grantedBy = req.user.user_id;
if (isNaN(userId)) {
return res.status(400).json({
success: false,
message: '유효하지 않은 사용자 ID입니다.'
});
}
if (!Array.isArray(pageIds)) {
return res.status(400).json({
success: false,
message: 'pageIds는 배열이어야 합니다.'
});
}
PageAccessModel.setUserPageAccess(userId, pageIds, grantedBy, (err, result) => {
if (err) {
console.error('페이지 권한 설정 오류:', err);
return res.status(500).json({
success: false,
message: '페이지 권한 설정 중 오류가 발생했습니다.',
error: err.message
});
}
res.json({
success: true,
message: '페이지 권한이 설정되었습니다.',
data: result
});
});
},
// 특정 페이지 접근 권한 확인
checkPageAccess: (req, res) => {
const userId = req.user.user_id;
const { pageKey } = req.params;
if (!pageKey) {
return res.status(400).json({
success: false,
message: '페이지 키가 필요합니다.'
});
}
PageAccessModel.checkPageAccess(userId, pageKey, (err, result) => {
if (err) {
console.error('페이지 접근 권한 확인 오류:', err);
return res.status(500).json({
success: false,
message: '페이지 접근 권한 확인 중 오류가 발생했습니다.',
error: err.message
});
}
res.json({
success: true,
data: result
});
});
},
// 계정이 있는 사용자 목록 조회 (권한 관리용)
getUsersWithAccounts: (req, res) => {
PageAccessModel.getUsersWithAccounts((err, results) => {
if (err) {
console.error('사용자 목록 조회 오류:', err);
return res.status(500).json({
success: false,
message: '사용자 목록 조회 중 오류가 발생했습니다.',
error: err.message
});
}
res.json({
success: true,
data: results
});
});
}
};
module.exports = PageAccessController;

View File

@@ -1,35 +0,0 @@
require('dotenv').config();
const mysql = require('mysql2/promise');
const retry = require('async-retry');
// 초기화된 pool을 export 하기 위한 변수
let pool = null;
const initPool = async () => {
if (pool) return pool; // 이미 초기화된 경우 재사용
await retry(async () => {
pool = mysql.createPool({
host: process.env.DB_HOST,
port: process.env.DB_PORT || 3306,
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
const conn = await pool.getConnection();
await conn.query('SET FOREIGN_KEY_CHECKS = 1');
console.log(`✅ MariaDB 연결 성공: ${process.env.DB_HOST}:${process.env.DB_PORT || 3306}/${process.env.DB_NAME}`);
conn.release();
}, {
retries: 10,
minTimeout: 3000
});
return pool;
};
module.exports = initPool;

View File

@@ -22,222 +22,6 @@ const PORT = process.env.PORT || 20005;
// Trust proxy for accurate IP addresses
app.set('trust proxy', 1);
// JSON body parser 미리 적용 (마이그레이션용)
app.use(express.json());
// 임시 분석 테스트 엔드포인트 - 실행 후 삭제!
app.get('/api/test-analysis', async (req, res) => {
try {
const { getDb } = require('./dbPool');
const db = await getDb();
// 수정된 COALESCE 로직 테스트 (tasks 우선)
const [results] = await db.query(`
SELECT
dwr.id,
w.worker_name,
dwr.report_date,
dwr.work_type_id as original_work_type_id,
COALESCE(
CASE WHEN t.task_id IS NOT NULL THEN t.work_type_id ELSE NULL END,
wt.id
) as resolved_work_type_id,
COALESCE(
CASE WHEN t.task_id IS NOT NULL THEN wt2.name ELSE NULL END,
wt.name
) as work_type_name,
t.task_name,
wt.name as direct_match_work_type,
wt2.name as task_work_type
FROM daily_work_reports dwr
LEFT JOIN workers w ON dwr.worker_id = w.worker_id
LEFT JOIN work_types wt ON dwr.work_type_id = wt.id
LEFT JOIN tasks t ON dwr.work_type_id = t.task_id
LEFT JOIN work_types wt2 ON t.work_type_id = wt2.id
WHERE w.worker_name LIKE '%조승민%' OR w.worker_name LIKE '%최광욱%'
ORDER BY dwr.report_date DESC
LIMIT 20
`);
res.json({
success: true,
message: 'tasks 테이블 우선 조회 결과',
data: results.map(r => ({
id: r.id,
worker: r.worker_name,
date: r.report_date,
original_id: r.original_work_type_id,
resolved_work_type: r.work_type_name,
task: r.task_name,
note: `원래 ID ${r.original_work_type_id}${r.work_type_name}`
}))
});
} catch (error) {
console.error('테스트 실패:', error);
res.status(500).json({ success: false, error: error.message });
}
});
// 임시 진단 엔드포인트 - 실행 후 삭제!
app.get('/api/diagnose-work-type-id', async (req, res) => {
try {
const { getDb } = require('./dbPool');
const db = await getDb();
// 1. 전체 작업보고서 현황
const [totalStats] = await db.query(`
SELECT
COUNT(*) as total_reports,
COUNT(tbm_assignment_id) as tbm_reports,
COUNT(CASE WHEN tbm_assignment_id IS NULL THEN 1 END) as non_tbm_reports
FROM daily_work_reports
`);
// 2. work_type_id 값 분포 (상위 20개)
const [workTypeDistribution] = await db.query(`
SELECT
dwr.work_type_id,
COUNT(*) as count,
wt.name as if_work_type,
t.task_name as if_task,
wt2.name as task_work_type
FROM daily_work_reports dwr
LEFT JOIN work_types wt ON dwr.work_type_id = wt.id
LEFT JOIN tasks t ON dwr.work_type_id = t.task_id
LEFT JOIN work_types wt2 ON t.work_type_id = wt2.id
GROUP BY dwr.work_type_id
ORDER BY count DESC
LIMIT 20
`);
// 3. 특정 작업자 데이터 확인 (조승민, 최광욱)
const [workerSamples] = await db.query(`
SELECT
dwr.id,
w.worker_name,
dwr.work_type_id,
dwr.tbm_assignment_id,
wt.name as direct_work_type,
t.task_name,
wt2.name as task_work_type,
dwr.report_date
FROM daily_work_reports dwr
LEFT JOIN workers w ON dwr.worker_id = w.worker_id
LEFT JOIN work_types wt ON dwr.work_type_id = wt.id
LEFT JOIN tasks t ON dwr.work_type_id = t.task_id
LEFT JOIN work_types wt2 ON t.work_type_id = wt2.id
WHERE w.worker_name LIKE '%조승민%' OR w.worker_name LIKE '%최광욱%'
ORDER BY dwr.report_date DESC
LIMIT 20
`);
res.json({
success: true,
data: {
total_stats: totalStats[0],
work_type_distribution: workTypeDistribution,
worker_samples: workerSamples
}
});
} catch (error) {
console.error('진단 실패:', error);
res.status(500).json({ success: false, error: error.message });
}
});
// 임시 마이그레이션 엔드포인트 (인증 없이 실행) - 실행 후 삭제!
app.post('/api/migrate-work-type-id', async (req, res) => {
try {
const { getDb } = require('./dbPool');
const db = await getDb();
console.log('🔄 TBM 기반 작업보고서 work_type_id 수정 시작...');
// 1. 수정 대상 확인
const [checkResult] = await db.query(`
SELECT
dwr.id,
dwr.work_type_id as current_work_type_id,
ta.task_id as correct_task_id,
w.worker_name,
dwr.report_date
FROM daily_work_reports dwr
INNER JOIN tbm_team_assignments ta ON dwr.tbm_assignment_id = ta.assignment_id
INNER JOIN workers w ON dwr.worker_id = w.worker_id
WHERE dwr.tbm_assignment_id IS NOT NULL
AND ta.task_id IS NOT NULL
AND dwr.work_type_id != ta.task_id
ORDER BY dwr.report_date DESC
`);
console.log('📊 수정 대상:', checkResult.length, '개 레코드');
if (checkResult.length === 0) {
return res.json({
success: true,
message: '수정할 데이터가 없습니다.',
data: { affected_rows: 0 }
});
}
// 2. 업데이트 실행
const [updateResult] = await db.query(`
UPDATE daily_work_reports dwr
INNER JOIN tbm_team_assignments ta ON dwr.tbm_assignment_id = ta.assignment_id
SET dwr.work_type_id = ta.task_id
WHERE dwr.tbm_assignment_id IS NOT NULL
AND ta.task_id IS NOT NULL
AND dwr.work_type_id != ta.task_id
`);
console.log('✅ 업데이트 완료:', updateResult.affectedRows, '개 레코드 수정됨');
// 3. 수정 후 확인
const [samples] = await db.query(`
SELECT
dwr.id,
dwr.work_type_id,
t.task_name,
wt.name as work_type_name,
w.worker_name,
dwr.report_date
FROM daily_work_reports dwr
INNER JOIN tbm_team_assignments ta ON dwr.tbm_assignment_id = ta.assignment_id
LEFT JOIN tasks t ON dwr.work_type_id = t.task_id
LEFT JOIN work_types wt ON t.work_type_id = wt.id
LEFT JOIN workers w ON dwr.worker_id = w.worker_id
WHERE dwr.tbm_assignment_id IS NOT NULL
ORDER BY dwr.report_date DESC
LIMIT 10
`);
res.json({
success: true,
message: updateResult.affectedRows + '개 레코드가 수정되었습니다.',
data: {
affected_rows: updateResult.affectedRows,
before_count: checkResult.length,
samples: samples.map(s => ({
id: s.id,
worker: s.worker_name,
date: s.report_date,
task: s.task_name,
work_type: s.work_type_name
}))
}
});
} catch (error) {
console.error('마이그레이션 실패:', error);
res.status(500).json({
success: false,
error: '마이그레이션 실패: ' + error.message
});
}
});
// 미들웨어 설정
setupMiddlewares(app);

View File

@@ -1,9 +0,0 @@
// 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

@@ -1,29 +0,0 @@
/**
* @deprecated 이 파일은 하위 호환성을 위해 유지됩니다.
* 새로운 코드에서는 '../middlewares/auth'의 requireMinLevel을 사용하세요.
*
* @example
* // 이전 방식 (deprecated)
* const { requireAccess, ACCESS_LEVELS } = require('../middlewares/accessMiddleware');
* router.get('/admin', requireAccess('admin'), handler);
*
* // 새로운 방식 (권장)
* const { requireMinLevel, ACCESS_LEVELS } = require('../middlewares/auth');
* router.get('/admin', requireAuth, requireMinLevel('admin'), handler);
*/
const { requireMinLevel, ACCESS_LEVELS } = require('./auth');
/**
* @deprecated requireMinLevel을 사용하세요
*/
const requireAccess = (requiredLevel) => {
return requireMinLevel(requiredLevel);
};
module.exports = {
requireAccess,
ACCESS_LEVELS,
// 새로운 API
requireMinLevel
};

View File

@@ -1,36 +0,0 @@
/**
* @deprecated 이 파일은 하위 호환성을 위해 유지됩니다.
* 새로운 코드에서는 './auth'를 직접 import하세요.
*
* @example
* // 이전 방식 (deprecated)
* const { verifyToken, requireAdmin } = require('../middlewares/authMiddleware');
*
* // 새로운 방식 (권장)
* const { requireAuth, requireRole } = require('../middlewares/auth');
*/
const {
requireAuth,
requireRole,
requireMinLevel,
requireOwnerOrAdmin,
verifyToken,
requireAdmin,
requireSystem,
ACCESS_LEVELS
} = require('./auth');
module.exports = {
// 레거시 별칭 (하위 호환성)
verifyToken,
requireAdmin,
requireSystem,
// 새로운 API (권장)
requireAuth,
requireRole,
requireMinLevel,
requireOwnerOrAdmin,
ACCESS_LEVELS
};

View File

@@ -2,7 +2,7 @@
const express = require('express');
const router = express.Router();
const { getAnalysisData } = require('../controllers/analysisController');
const { verifyToken } = require('../middlewares/authMiddleware'); // 인증 미들웨어 추가
const { verifyToken } = require('../middlewares/auth');
// GET /api/analysis?startDate=...&endDate=...
router.get('/', verifyToken, getAnalysisData);

View File

@@ -1,7 +1,7 @@
const express = require('express');
const router = express.Router();
const AttendanceController = require('../controllers/attendanceController');
const { verifyToken } = require('../middlewares/authMiddleware');
const { verifyToken } = require('../middlewares/auth');
// 모든 라우트에 인증 미들웨어 적용
router.use(verifyToken);

View File

@@ -1,215 +0,0 @@
// routes/auth.js
const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const { requireAuth, requireRole } = require('../middlewares/auth');
const router = express.Router();
// 임시 사용자 데이터 (실제 운영 시 DB 사용 필수)
// 비밀번호 해시는 bcrypt.hash('password', 10)으로 생성됨
let users = [
{
user_id: 1,
username: 'admin',
password: '$2b$10$N9qo8uLOickgx2ZMRZoMyeIjZRGdjGj/n3.w7VtL.r8yR.nTfC7Hy', // 'password' 해시
name: '관리자',
access_level: 'admin',
worker_id: null,
created_at: new Date()
},
{
user_id: 2,
username: 'group_leader1',
password: '$2b$10$N9qo8uLOickgx2ZMRZoMyeIjZRGdjGj/n3.w7VtL.r8yR.nTfC7Hy', // 'password' 해시
name: '김그룹장',
access_level: 'group_leader',
worker_id: 1,
created_at: new Date()
}
];
// 보안 경고: 운영 환경에서는 반드시 .env의 JWT_SECRET을 설정해야 합니다
if (!process.env.JWT_SECRET) {
console.warn('⚠️ WARNING: JWT_SECRET이 설정되지 않았습니다. 운영 환경에서는 반드시 설정하세요!');
}
/**
* 로그인
*/
router.post('/login', async (req, res) => {
try {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ error: '사용자명과 비밀번호를 입력해주세요.' });
}
const user = users.find(u => u.username === username);
if (!user) {
return res.status(401).json({ error: '사용자를 찾을 수 없습니다.' });
}
// 비밀번호 확인 (bcrypt.compare 사용)
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
return res.status(401).json({ error: '비밀번호가 올바르지 않습니다.' });
}
// JWT 토큰 생성
const token = jwt.sign(
{
user_id: user.user_id,
username: user.username,
access_level: user.access_level,
worker_id: user.worker_id
},
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
res.json({
success: true,
token,
user: {
user_id: user.user_id,
username: user.username,
name: user.name,
access_level: user.access_level,
worker_id: user.worker_id
}
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
}
});
/**
* 현재 사용자 정보 조회
*/
router.get('/me', requireAuth, (req, res) => {
try {
const userId = req.user.user_id;
const user = users.find(u => u.user_id === userId);
if (!user) {
return res.status(404).json({ error: '사용자를 찾을 수 없습니다.' });
}
res.json({
user_id: user.user_id,
username: user.username,
name: user.name,
access_level: user.access_level,
worker_id: user.worker_id
});
} catch (error) {
console.error('Get current user error:', error);
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
}
});
/**
* 사용자 등록 (관리자만)
*/
router.post('/register', requireAuth, requireRole('admin', 'system'), async (req, res) => {
try {
const { username, password, name, access_level, worker_id } = req.body;
if (!username || !password || !name || !access_level) {
return res.status(400).json({ error: '필수 항목을 모두 입력해주세요.' });
}
// 사용자명 중복 체크
const existingUser = users.find(u => u.username === username);
if (existingUser) {
return res.status(409).json({ error: '이미 존재하는 사용자명입니다.' });
}
// 비밀번호 해시
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = {
user_id: users.length + 1,
username,
password: hashedPassword,
name,
access_level,
worker_id: worker_id || null,
created_at: new Date()
};
users.push(newUser);
res.json({
success: true,
message: '사용자가 성공적으로 등록되었습니다.',
user: {
user_id: newUser.user_id,
username: newUser.username,
name: newUser.name,
access_level: newUser.access_level,
worker_id: newUser.worker_id
}
});
} catch (error) {
console.error('Register error:', error);
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
}
});
/**
* 사용자 목록 조회 (관리자만)
*/
router.get('/users', requireAuth, requireRole('admin', 'system'), (req, res) => {
try {
const userList = users.map(user => ({
user_id: user.user_id,
username: user.username,
name: user.name,
access_level: user.access_level,
worker_id: user.worker_id,
created_at: user.created_at
}));
res.json(userList);
} catch (error) {
console.error('Get users error:', error);
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
}
});
/**
* 사용자 삭제 (관리자만)
*/
router.delete('/users/:id', requireAuth, requireRole('admin', 'system'), (req, res) => {
try {
const userId = parseInt(req.params.id);
// 자기 자신 삭제 방지
if (userId === req.user.user_id) {
return res.status(400).json({ error: '자기 자신은 삭제할 수 없습니다.' });
}
const userIndex = users.findIndex(u => u.user_id === userId);
if (userIndex === -1) {
return res.status(404).json({ error: '사용자를 찾을 수 없습니다.' });
}
users.splice(userIndex, 1);
res.json({
success: true,
message: '사용자가 성공적으로 삭제되었습니다.'
});
} catch (error) {
console.error('Delete user error:', error);
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
}
});
module.exports = router;

View File

@@ -10,7 +10,7 @@ const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const mysql = require('mysql2/promise');
const { verifyToken } = require('../middlewares/authMiddleware');
const { verifyToken } = require('../middlewares/auth');
const { validatePassword, getPasswordError } = require('../utils/passwordValidator');
const router = express.Router();
const authController = require('../controllers/authController');

View File

@@ -2,7 +2,7 @@
const express = require('express');
const router = express.Router();
const departmentController = require('../controllers/departmentController');
const { requireAuth, requireRole } = require('../middlewares/authMiddleware');
const { requireAuth, requireRole } = require('../middlewares/auth');
// 부서 목록 조회 (인증 필요)
router.get('/', requireAuth, departmentController.getAll);

View File

@@ -25,99 +25,4 @@ router.get('/detail', (req, res) => {
});
});
// 임시 마이그레이션 엔드포인트 - TBM work_type_id 수정
// 실행 후 이 코드를 삭제하세요!
router.post('/migrate-work-type-id', async (req, res) => {
try {
const { getDb } = require('../dbPool');
const db = await getDb();
console.log('🔄 TBM 기반 작업보고서 work_type_id 수정 시작...');
// 1. 수정 대상 확인
const [checkResult] = await db.query(`
SELECT
dwr.id,
dwr.work_type_id as current_work_type_id,
ta.task_id as correct_task_id,
w.worker_name,
dwr.report_date
FROM daily_work_reports dwr
INNER JOIN tbm_team_assignments ta ON dwr.tbm_assignment_id = ta.assignment_id
INNER JOIN workers w ON dwr.worker_id = w.worker_id
WHERE dwr.tbm_assignment_id IS NOT NULL
AND ta.task_id IS NOT NULL
AND dwr.work_type_id != ta.task_id
ORDER BY dwr.report_date DESC
`);
console.log(`📊 수정 대상: ${checkResult.length}개 레코드`);
if (checkResult.length === 0) {
return res.json({
success: true,
message: '수정할 데이터가 없습니다.',
data: { affected_rows: 0 }
});
}
// 수정 전 샘플 로깅
console.log('수정 전 샘플:', checkResult.slice(0, 5));
// 2. 업데이트 실행
const [updateResult] = await db.query(`
UPDATE daily_work_reports dwr
INNER JOIN tbm_team_assignments ta ON dwr.tbm_assignment_id = ta.assignment_id
SET dwr.work_type_id = ta.task_id
WHERE dwr.tbm_assignment_id IS NOT NULL
AND ta.task_id IS NOT NULL
AND dwr.work_type_id != ta.task_id
`);
console.log(`✅ 업데이트 완료: ${updateResult.affectedRows}개 레코드 수정됨`);
// 3. 수정 후 확인
const [samples] = await db.query(`
SELECT
dwr.id,
dwr.work_type_id,
t.task_name,
wt.name as work_type_name,
w.worker_name,
dwr.report_date
FROM daily_work_reports dwr
INNER JOIN tbm_team_assignments ta ON dwr.tbm_assignment_id = ta.assignment_id
LEFT JOIN tasks t ON dwr.work_type_id = t.task_id
LEFT JOIN work_types wt ON t.work_type_id = wt.id
LEFT JOIN workers w ON dwr.worker_id = w.worker_id
WHERE dwr.tbm_assignment_id IS NOT NULL
ORDER BY dwr.report_date DESC
LIMIT 10
`);
res.json({
success: true,
message: `${updateResult.affectedRows}개 레코드가 수정되었습니다.`,
data: {
affected_rows: updateResult.affectedRows,
before_count: checkResult.length,
samples: samples.map(s => ({
id: s.id,
worker: s.worker_name,
date: s.report_date,
task: s.task_name,
work_type: s.work_type_name
}))
}
});
} catch (error) {
console.error('마이그레이션 실패:', error);
res.status(500).json({
success: false,
error: '마이그레이션 실패: ' + error.message
});
}
});
module.exports = router;

View File

@@ -4,7 +4,7 @@
const express = require('express');
const router = express.Router();
const MonthlyStatusController = require('../controllers/monthlyStatusController');
const { verifyToken } = require('../middlewares/authMiddleware');
const { verifyToken } = require('../middlewares/auth');
// 모든 라우트에 인증 미들웨어 적용 (임시로 주석 처리 - 테스트용)
// router.use(verifyToken);

View File

@@ -2,7 +2,7 @@
const express = require('express');
const router = express.Router();
const notificationRecipientController = require('../controllers/notificationRecipientController');
const { verifyToken, requireMinLevel } = require('../middlewares/authMiddleware');
const { verifyToken, requireMinLevel } = require('../middlewares/auth');
// 모든 라우트에 인증 필요
router.use(verifyToken);

View File

@@ -2,7 +2,7 @@
const express = require('express');
const router = express.Router();
const TbmController = require('../controllers/tbmController');
const { requireAuth } = require('../middlewares/auth');
const { requireAuth, requireRole } = require('../middlewares/auth');
// ==================== TBM 세션 관련 ====================
@@ -56,13 +56,13 @@ router.delete('/sessions/:sessionId/team/:workerId', requireAuth, TbmController.
router.get('/safety-checks', requireAuth, TbmController.getAllSafetyChecks);
// 안전 체크 항목 생성 (관리자용)
router.post('/safety-checks', requireAuth, TbmController.createSafetyCheck);
router.post('/safety-checks', requireAuth, requireRole('admin', 'system'), TbmController.createSafetyCheck);
// 안전 체크 항목 수정 (관리자용)
router.put('/safety-checks/:checkId', requireAuth, TbmController.updateSafetyCheck);
router.put('/safety-checks/:checkId', requireAuth, requireRole('admin', 'system'), TbmController.updateSafetyCheck);
// 안전 체크 항목 삭제 (관리자용)
router.delete('/safety-checks/:checkId', requireAuth, TbmController.deleteSafetyCheck);
router.delete('/safety-checks/:checkId', requireAuth, requireRole('admin', 'system'), TbmController.deleteSafetyCheck);
// TBM 세션의 안전 체크 기록 조회
router.get('/sessions/:sessionId/safety', requireAuth, TbmController.getSafetyRecords);

View File

@@ -10,7 +10,7 @@
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
const { verifyToken } = require('../middlewares/authMiddleware');
const { verifyToken } = require('../middlewares/auth');
const logger = require('../utils/logger');
/**

View File

@@ -1,7 +1,7 @@
const express = require('express');
const router = express.Router();
const visitRequestController = require('../controllers/visitRequestController');
const { verifyToken } = require('../middlewares/authMiddleware');
const { verifyToken } = require('../middlewares/auth');
// 모든 라우트에 인증 미들웨어 적용
router.use(verifyToken);

View File

@@ -1,74 +0,0 @@
// routes/workAnalysis.js
const express = require('express');
const router = express.Router();
const workAnalysisController = require('../controllers/workAnalysisController');
// 🔒 분석 기능은 admin 또는 system 권한만 접근 가능
const requireAnalysisAccess = (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: '인증이 필요합니다.' });
}
const allowedLevels = ['admin', 'system'];
if (!allowedLevels.includes(req.user.access_level)) {
return res.status(403).json({
error: '분석 기능 접근 권한이 없습니다. 관리자 권한이 필요합니다.',
required: 'admin 또는 system',
current: req.user.access_level
});
}
console.log(`🔓 분석 기능 접근 허용: ${req.user.username} (${req.user.access_level})`);
next();
};
// 임시로 권한 체크 건너뛰기 (테스트용)
const skipAuth = (req, res, next) => {
console.log('⚠️ 임시로 권한 체크 건너뛰기');
next();
};
// 기본 통계 조회 - 임시로 권한 체크 비활성화
router.get('/stats', skipAuth, workAnalysisController.getStats);
// 일별 작업시간 추이 - 임시로 권한 체크 비활성화
router.get('/daily-trend', skipAuth, workAnalysisController.getDailyTrend);
// 작업자별 통계 - 임시로 권한 체크 비활성화
router.get('/worker-stats', skipAuth, workAnalysisController.getWorkerStats);
// 프로젝트별 통계 - 임시로 권한 체크 비활성화
router.get('/project-stats', skipAuth, workAnalysisController.getProjectStats);
// 작업유형별 통계 - 임시로 권한 체크 비활성화
router.get('/worktype-stats', skipAuth, workAnalysisController.getWorkTypeStats);
// 최근 작업 현황 - 임시로 권한 체크 비활성화
router.get('/recent-work', skipAuth, workAnalysisController.getRecentWork);
// 요일별 패턴 분석
router.get('/weekday-pattern', requireAnalysisAccess, workAnalysisController.getWeekdayPattern);
// 에러 분석
router.get('/error-analysis', requireAnalysisAccess, workAnalysisController.getErrorAnalysis);
// 월별 비교 분석
router.get('/monthly-comparison', requireAnalysisAccess, workAnalysisController.getMonthlyComparison);
// 작업자별 전문분야 분석
router.get('/worker-specialization', requireAnalysisAccess, workAnalysisController.getWorkerSpecialization);
// 대시보드용 종합 데이터 (한 번에 여러 데이터 조회)
router.get('/dashboard', requireAnalysisAccess, workAnalysisController.getDashboardData);
// 헬스체크 - 인증 없이 접근 가능
router.get('/health', (req, res) => {
res.status(200).json({
success: true,
message: 'Work Analysis API is running',
timestamp: new Date().toISOString()
});
});
module.exports = router;