feat: 다수 기능 개선 - 순찰, 출근, 작업분석, 모바일 UI 등
- 순찰/점검 기능 개선 (zone-detail 페이지 추가) - 출근/근태 시스템 개선 (연차 조회, 근무현황) - 작업분석 대분류 그룹화 및 마이그레이션 스크립트 - 모바일 네비게이션 UI 추가 - NAS 배포 도구 및 문서 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
378
deploy/tkfb-package/api.hyungi.net/routes/systemRoutes.js
Normal file
378
deploy/tkfb-package/api.hyungi.net/routes/systemRoutes.js
Normal file
@@ -0,0 +1,378 @@
|
||||
// 시스템 관리 라우트
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const systemController = require('../controllers/systemController');
|
||||
const { requireAuth, requireRole } = require('../middlewares/auth');
|
||||
|
||||
// 모든 라우트에 인증 및 시스템 권한 확인 적용
|
||||
router.use(requireAuth);
|
||||
router.use(requireRole('system'));
|
||||
|
||||
// ===== 시스템 상태 관련 =====
|
||||
|
||||
/**
|
||||
* GET /api/system/status
|
||||
* 시스템 전체 상태 확인
|
||||
*/
|
||||
router.get('/status', systemController.getSystemStatus);
|
||||
|
||||
/**
|
||||
* GET /api/system/db-status
|
||||
* 데이터베이스 상태 확인
|
||||
*/
|
||||
router.get('/db-status', systemController.getDatabaseStatus);
|
||||
|
||||
/**
|
||||
* GET /api/system/alerts
|
||||
* 시스템 알림 조회
|
||||
*/
|
||||
router.get('/alerts', systemController.getSystemAlerts);
|
||||
|
||||
/**
|
||||
* GET /api/system/recent-activities
|
||||
* 최근 시스템 활동 조회
|
||||
*/
|
||||
router.get('/recent-activities', systemController.getRecentActivities);
|
||||
|
||||
// ===== 사용자 관리 관련 =====
|
||||
|
||||
/**
|
||||
* GET /api/system/users/stats
|
||||
* 사용자 통계 조회
|
||||
*/
|
||||
router.get('/users/stats', systemController.getUserStats);
|
||||
|
||||
/**
|
||||
* GET /api/system/users
|
||||
* 모든 사용자 목록 조회
|
||||
*/
|
||||
router.get('/users', systemController.getAllUsers);
|
||||
|
||||
/**
|
||||
* POST /api/system/users
|
||||
* 새 사용자 생성
|
||||
*/
|
||||
router.post('/users', systemController.createUser);
|
||||
|
||||
/**
|
||||
* PUT /api/system/users/:id
|
||||
* 사용자 정보 수정
|
||||
*/
|
||||
router.put('/users/:id', systemController.updateUser);
|
||||
|
||||
/**
|
||||
* DELETE /api/system/users/:id
|
||||
* 사용자 삭제
|
||||
*/
|
||||
router.delete('/users/:id', systemController.deleteUser);
|
||||
|
||||
/**
|
||||
* POST /api/system/users/:id/reset-password
|
||||
* 사용자 비밀번호 재설정
|
||||
*/
|
||||
router.post('/users/:id/reset-password', systemController.resetUserPassword);
|
||||
|
||||
// ===== 시스템 로그 관련 =====
|
||||
|
||||
/**
|
||||
* GET /api/system/logs/login
|
||||
* 로그인 로그 조회
|
||||
*/
|
||||
router.get('/logs/login', async (req, res) => {
|
||||
try {
|
||||
const { getDb } = require('../dbPool');
|
||||
const db = await getDb();
|
||||
|
||||
const { page = 1, limit = 50, status, user_id, start_date, end_date } = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
let whereClause = '1=1';
|
||||
const params = [];
|
||||
|
||||
if (status) {
|
||||
whereClause += ' AND ll.login_status = ?';
|
||||
params.push(status);
|
||||
}
|
||||
|
||||
if (user_id) {
|
||||
whereClause += ' AND ll.user_id = ?';
|
||||
params.push(user_id);
|
||||
}
|
||||
|
||||
if (start_date) {
|
||||
whereClause += ' AND ll.login_time >= ?';
|
||||
params.push(start_date);
|
||||
}
|
||||
|
||||
if (end_date) {
|
||||
whereClause += ' AND ll.login_time <= ?';
|
||||
params.push(end_date);
|
||||
}
|
||||
|
||||
const [logs] = await db.query(`
|
||||
SELECT
|
||||
ll.log_id,
|
||||
ll.user_id,
|
||||
u.username,
|
||||
u.name,
|
||||
ll.login_time,
|
||||
ll.ip_address,
|
||||
ll.user_agent,
|
||||
ll.login_status,
|
||||
ll.failure_reason
|
||||
FROM login_logs ll
|
||||
LEFT JOIN Users u ON ll.user_id = u.user_id
|
||||
WHERE ${whereClause}
|
||||
ORDER BY ll.login_time DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`, [...params, parseInt(limit), parseInt(offset)]);
|
||||
|
||||
const [totalCount] = await db.query(`
|
||||
SELECT COUNT(*) as count
|
||||
FROM login_logs ll
|
||||
WHERE ${whereClause}
|
||||
`, params);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
logs,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: totalCount[0].count,
|
||||
pages: Math.ceil(totalCount[0].count / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('로그인 로그 조회 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: '로그인 로그를 조회할 수 없습니다.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/system/logs/password-changes
|
||||
* 비밀번호 변경 로그 조회
|
||||
*/
|
||||
router.get('/logs/password-changes', async (req, res) => {
|
||||
try {
|
||||
const { getDb } = require('../dbPool');
|
||||
const db = await getDb();
|
||||
|
||||
const { page = 1, limit = 50 } = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const [logs] = await db.query(`
|
||||
SELECT
|
||||
pcl.log_id,
|
||||
pcl.user_id,
|
||||
u.username,
|
||||
u.name,
|
||||
pcl.changed_by_user_id,
|
||||
admin.username as changed_by_username,
|
||||
admin.name as changed_by_name,
|
||||
pcl.changed_at,
|
||||
pcl.change_type,
|
||||
pcl.ip_address
|
||||
FROM password_change_logs pcl
|
||||
LEFT JOIN Users u ON pcl.user_id = u.user_id
|
||||
LEFT JOIN Users admin ON pcl.changed_by_user_id = admin.user_id
|
||||
ORDER BY pcl.changed_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`, [parseInt(limit), parseInt(offset)]);
|
||||
|
||||
const [totalCount] = await db.query('SELECT COUNT(*) as count FROM password_change_logs');
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
logs,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: totalCount[0].count,
|
||||
pages: Math.ceil(totalCount[0].count / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('비밀번호 변경 로그 조회 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: '비밀번호 변경 로그를 조회할 수 없습니다.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/system/migrations/fix-work-type-id
|
||||
* TBM 기반 작업보고서의 work_type_id를 task_id로 수정
|
||||
*/
|
||||
router.post('/migrations/fix-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
|
||||
`);
|
||||
|
||||
if (checkResult.length === 0) {
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '수정할 데이터가 없습니다.',
|
||||
data: { affected_rows: 0, samples: [] }
|
||||
});
|
||||
}
|
||||
|
||||
// 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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/system/logs/activity
|
||||
* 활동 로그 조회 (activity_logs 테이블이 있는 경우)
|
||||
*/
|
||||
router.get('/logs/activity', async (req, res) => {
|
||||
try {
|
||||
const { getDb } = require('../dbPool');
|
||||
const db = await getDb();
|
||||
|
||||
const { page = 1, limit = 50, activity_type, user_id } = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
let whereClause = '1=1';
|
||||
const params = [];
|
||||
|
||||
if (activity_type) {
|
||||
whereClause += ' AND al.activity_type = ?';
|
||||
params.push(activity_type);
|
||||
}
|
||||
|
||||
if (user_id) {
|
||||
whereClause += ' AND al.user_id = ?';
|
||||
params.push(user_id);
|
||||
}
|
||||
|
||||
const [logs] = await db.query(`
|
||||
SELECT
|
||||
al.log_id,
|
||||
al.user_id,
|
||||
u.username,
|
||||
u.name,
|
||||
al.activity_type,
|
||||
al.table_name,
|
||||
al.record_id,
|
||||
al.action,
|
||||
al.ip_address,
|
||||
al.user_agent,
|
||||
al.created_at
|
||||
FROM activity_logs al
|
||||
LEFT JOIN Users u ON al.user_id = u.user_id
|
||||
WHERE ${whereClause}
|
||||
ORDER BY al.created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`, [...params, parseInt(limit), parseInt(offset)]);
|
||||
|
||||
const [totalCount] = await db.query(`
|
||||
SELECT COUNT(*) as count
|
||||
FROM activity_logs al
|
||||
WHERE ${whereClause}
|
||||
`, params);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
logs,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: totalCount[0].count,
|
||||
pages: Math.ceil(totalCount[0].count / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('활동 로그 조회 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: '활동 로그를 조회할 수 없습니다.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user