feat: 안전 코드 tksafety 이관 + 사용자 관리 정리 + UI Tailwind 전환
Phase 1: tksafety에 출입신청/체크리스트 API·웹 추가, tkfb 안전 코드 삭제
Phase 2: 사용자 관리 페이지 삭제, API 축소, 알림 수신자 tkuser 이관
Phase 3: tkuser 권한 페이지 정의 업데이트
Phase 4: 전체 34개 페이지 Tailwind CSS + tkfb-core.js 전환,
미사용 CSS 20개·인프라 JS 10개·템플릿·컴포넌트 삭제
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
// controllers/notificationRecipientController.js
|
||||
const notificationRecipientModel = require('../models/notificationRecipientModel');
|
||||
|
||||
const notificationRecipientController = {
|
||||
// 알림 유형 목록
|
||||
getTypes: async (req, res) => {
|
||||
try {
|
||||
const types = notificationRecipientModel.getTypes();
|
||||
res.json({ success: true, data: types });
|
||||
} catch (error) {
|
||||
console.error('알림 유형 조회 오류:', error);
|
||||
res.status(500).json({ success: false, error: '알림 유형 조회 실패' });
|
||||
}
|
||||
},
|
||||
|
||||
// 전체 수신자 목록 (유형별 그룹화)
|
||||
getAll: async (req, res) => {
|
||||
try {
|
||||
const recipients = await notificationRecipientModel.getAll();
|
||||
res.json({ success: true, data: recipients });
|
||||
} catch (error) {
|
||||
console.error(' 수신자 목록 조회 오류:', error.message);
|
||||
console.error(' 스택:', error.stack);
|
||||
res.status(500).json({ success: false, error: '수신자 목록 조회 실패', detail: error.message });
|
||||
}
|
||||
},
|
||||
|
||||
// 유형별 수신자 조회
|
||||
getByType: async (req, res) => {
|
||||
try {
|
||||
const { type } = req.params;
|
||||
const recipients = await notificationRecipientModel.getByType(type);
|
||||
res.json({ success: true, data: recipients });
|
||||
} catch (error) {
|
||||
console.error('수신자 조회 오류:', error);
|
||||
res.status(500).json({ success: false, error: '수신자 조회 실패' });
|
||||
}
|
||||
},
|
||||
|
||||
// 수신자 추가
|
||||
add: async (req, res) => {
|
||||
try {
|
||||
const { notification_type, user_id } = req.body;
|
||||
|
||||
if (!notification_type || !user_id) {
|
||||
return res.status(400).json({ success: false, error: '알림 유형과 사용자 ID가 필요합니다.' });
|
||||
}
|
||||
|
||||
await notificationRecipientModel.add(notification_type, user_id, req.user?.user_id);
|
||||
res.json({ success: true, message: '수신자가 추가되었습니다.' });
|
||||
} catch (error) {
|
||||
console.error('수신자 추가 오류:', error);
|
||||
res.status(500).json({ success: false, error: '수신자 추가 실패' });
|
||||
}
|
||||
},
|
||||
|
||||
// 수신자 제거
|
||||
remove: async (req, res) => {
|
||||
try {
|
||||
const { type, userId } = req.params;
|
||||
|
||||
await notificationRecipientModel.remove(type, userId);
|
||||
res.json({ success: true, message: '수신자가 제거되었습니다.' });
|
||||
} catch (error) {
|
||||
console.error('수신자 제거 오류:', error);
|
||||
res.status(500).json({ success: false, error: '수신자 제거 실패' });
|
||||
}
|
||||
},
|
||||
|
||||
// 유형별 수신자 일괄 설정
|
||||
setRecipients: async (req, res) => {
|
||||
try {
|
||||
const { type } = req.params;
|
||||
const { user_ids } = req.body;
|
||||
|
||||
if (!Array.isArray(user_ids)) {
|
||||
return res.status(400).json({ success: false, error: 'user_ids 배열이 필요합니다.' });
|
||||
}
|
||||
|
||||
await notificationRecipientModel.setRecipients(type, user_ids, req.user?.user_id);
|
||||
res.json({ success: true, message: '수신자가 설정되었습니다.' });
|
||||
} catch (error) {
|
||||
console.error('수신자 설정 오류:', error);
|
||||
res.status(500).json({ success: false, error: '수신자 설정 실패' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = notificationRecipientController;
|
||||
@@ -18,6 +18,7 @@ const equipmentRoutes = require('./routes/equipmentRoutes');
|
||||
const taskRoutes = require('./routes/taskRoutes');
|
||||
const vacationRoutes = require('./routes/vacationRoutes');
|
||||
const partnerRoutes = require('./routes/partnerRoutes');
|
||||
const notificationRecipientRoutes = require('./routes/notificationRecipientRoutes');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
@@ -58,6 +59,7 @@ app.use('/api/equipments', equipmentRoutes);
|
||||
app.use('/api/tasks', taskRoutes);
|
||||
app.use('/api/vacations', vacationRoutes);
|
||||
app.use('/api/partners', partnerRoutes);
|
||||
app.use('/api/notification-recipients', notificationRecipientRoutes);
|
||||
|
||||
// 404
|
||||
app.use((req, res) => {
|
||||
|
||||
144
user-management/api/models/notificationRecipientModel.js
Normal file
144
user-management/api/models/notificationRecipientModel.js
Normal file
@@ -0,0 +1,144 @@
|
||||
// models/notificationRecipientModel.js
|
||||
const { getPool } = require('./userModel');
|
||||
|
||||
const NOTIFICATION_TYPES = {
|
||||
repair: '설비 수리',
|
||||
safety: '안전 신고',
|
||||
nonconformity: '부적합 신고',
|
||||
equipment: '설비 관련',
|
||||
maintenance: '정기점검',
|
||||
system: '시스템'
|
||||
};
|
||||
|
||||
const notificationRecipientModel = {
|
||||
// 알림 유형 목록 가져오기
|
||||
getTypes() {
|
||||
return NOTIFICATION_TYPES;
|
||||
},
|
||||
|
||||
// 유형별 수신자 목록 조회
|
||||
async getByType(notificationType) {
|
||||
const db = getPool();
|
||||
const [rows] = await db.query(
|
||||
`SELECT nr.*, u.username, u.name as user_name
|
||||
FROM notification_recipients nr
|
||||
JOIN sso_users u ON nr.user_id = u.user_id
|
||||
WHERE nr.notification_type = ? AND nr.is_active = 1
|
||||
ORDER BY u.name`,
|
||||
[notificationType]
|
||||
);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// 전체 수신자 목록 조회 (유형별 그룹화)
|
||||
async getAll() {
|
||||
const db = getPool();
|
||||
const [rows] = await db.query(
|
||||
`SELECT nr.*, u.username, u.name as user_name
|
||||
FROM notification_recipients nr
|
||||
JOIN sso_users u ON nr.user_id = u.user_id
|
||||
WHERE nr.is_active = 1
|
||||
ORDER BY nr.notification_type, u.name`
|
||||
);
|
||||
|
||||
// 유형별로 그룹화
|
||||
const grouped = {};
|
||||
for (const type in NOTIFICATION_TYPES) {
|
||||
grouped[type] = {
|
||||
label: NOTIFICATION_TYPES[type],
|
||||
recipients: []
|
||||
};
|
||||
}
|
||||
|
||||
rows.forEach(row => {
|
||||
if (grouped[row.notification_type]) {
|
||||
grouped[row.notification_type].recipients.push(row);
|
||||
}
|
||||
});
|
||||
|
||||
return grouped;
|
||||
},
|
||||
|
||||
// 수신자 추가
|
||||
async add(notificationType, userId, createdBy = null) {
|
||||
const db = getPool();
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO notification_recipients (notification_type, user_id, created_by)
|
||||
VALUES (?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE is_active = 1`,
|
||||
[notificationType, userId, createdBy]
|
||||
);
|
||||
return result.insertId || result.affectedRows > 0;
|
||||
},
|
||||
|
||||
// 수신자 제거 (soft delete)
|
||||
async remove(notificationType, userId) {
|
||||
const db = getPool();
|
||||
const [result] = await db.query(
|
||||
`UPDATE notification_recipients SET is_active = 0
|
||||
WHERE notification_type = ? AND user_id = ?`,
|
||||
[notificationType, userId]
|
||||
);
|
||||
return result.affectedRows > 0;
|
||||
},
|
||||
|
||||
// 수신자 완전 삭제
|
||||
async delete(notificationType, userId) {
|
||||
const db = getPool();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM notification_recipients
|
||||
WHERE notification_type = ? AND user_id = ?`,
|
||||
[notificationType, userId]
|
||||
);
|
||||
return result.affectedRows > 0;
|
||||
},
|
||||
|
||||
// 유형별 수신자 user_id 목록만 가져오기 (알림 생성용)
|
||||
async getRecipientIds(notificationType) {
|
||||
const db = getPool();
|
||||
const [rows] = await db.query(
|
||||
`SELECT user_id FROM notification_recipients
|
||||
WHERE notification_type = ? AND is_active = 1`,
|
||||
[notificationType]
|
||||
);
|
||||
return rows.map(r => r.user_id);
|
||||
},
|
||||
|
||||
// 사용자가 특정 유형의 수신자인지 확인
|
||||
async isRecipient(notificationType, userId) {
|
||||
const db = getPool();
|
||||
const [[row]] = await db.query(
|
||||
`SELECT 1 FROM notification_recipients
|
||||
WHERE notification_type = ? AND user_id = ? AND is_active = 1`,
|
||||
[notificationType, userId]
|
||||
);
|
||||
return !!row;
|
||||
},
|
||||
|
||||
// 유형별 수신자 일괄 설정
|
||||
async setRecipients(notificationType, userIds, createdBy = null) {
|
||||
const db = getPool();
|
||||
|
||||
// 기존 수신자 비활성화
|
||||
await db.query(
|
||||
`UPDATE notification_recipients SET is_active = 0
|
||||
WHERE notification_type = ?`,
|
||||
[notificationType]
|
||||
);
|
||||
|
||||
// 새 수신자 추가
|
||||
if (userIds && userIds.length > 0) {
|
||||
const values = userIds.map(userId => [notificationType, userId, createdBy]);
|
||||
await db.query(
|
||||
`INSERT INTO notification_recipients (notification_type, user_id, created_by)
|
||||
VALUES ?
|
||||
ON DUPLICATE KEY UPDATE is_active = 1`,
|
||||
[values]
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = notificationRecipientModel;
|
||||
28
user-management/api/routes/notificationRecipientRoutes.js
Normal file
28
user-management/api/routes/notificationRecipientRoutes.js
Normal file
@@ -0,0 +1,28 @@
|
||||
// routes/notificationRecipientRoutes.js
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const controller = require('../controllers/notificationRecipientController');
|
||||
const { requireAuth, requireAdmin } = require('../middleware/auth');
|
||||
|
||||
// 모든 라우트에 인증 필요
|
||||
router.use(requireAuth);
|
||||
|
||||
// 알림 유형 목록
|
||||
router.get('/types', controller.getTypes);
|
||||
|
||||
// 전체 수신자 목록 (유형별 그룹화)
|
||||
router.get('/', controller.getAll);
|
||||
|
||||
// 유형별 수신자 조회
|
||||
router.get('/:type', controller.getByType);
|
||||
|
||||
// 수신자 추가 (관리자만)
|
||||
router.post('/', requireAdmin, controller.add);
|
||||
|
||||
// 유형별 수신자 일괄 설정 (관리자만)
|
||||
router.put('/:type', requireAdmin, controller.setRecipients);
|
||||
|
||||
// 수신자 제거 (관리자만)
|
||||
router.delete('/:type/:userId', requireAdmin, controller.remove);
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user