- Phase 1: 모든 서비스 헤더에 알림 벨 UI 추가 (notification-bell.js) - Phase 2: VAPID Web Push 구독/전송 (push-sw.js, pushSubscription API) - Phase 3: 내부 알림 API + notifyHelper로 서비스간 알림 연동 - tksafety: 출입 승인/반려, 안전교육 완료, 방문자 체크인 - tkpurchase: 일용공 신청, 작업보고서 제출 - system2-report: 신고 접수/확인/처리완료 - Phase 4: 30일 이상 알림 자동 정리 cron, Redis 캐싱 - CORS에 tkuser/tkpurchase/tksafety 서브도메인 추가 - HTML cache busting 버전 갱신 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
204 lines
5.5 KiB
JavaScript
204 lines
5.5 KiB
JavaScript
// controllers/notificationController.js
|
|
const notificationModel = require('../models/notificationModel');
|
|
|
|
const notificationController = {
|
|
// 읽지 않은 알림 조회
|
|
async getUnread(req, res) {
|
|
try {
|
|
const userId = req.user?.id || null;
|
|
const notifications = await notificationModel.getUnread(userId);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: notifications
|
|
});
|
|
} catch (error) {
|
|
console.error('읽지 않은 알림 조회 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '알림 조회 중 오류가 발생했습니다.'
|
|
});
|
|
}
|
|
},
|
|
|
|
// 전체 알림 조회
|
|
async getAll(req, res) {
|
|
try {
|
|
const userId = req.user?.id || null;
|
|
const page = parseInt(req.query.page) || 1;
|
|
const limit = parseInt(req.query.limit) || 20;
|
|
|
|
const result = await notificationModel.getAll(userId, page, limit);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result.notifications,
|
|
pagination: {
|
|
total: result.total,
|
|
page: result.page,
|
|
limit: result.limit,
|
|
totalPages: Math.ceil(result.total / result.limit)
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('알림 목록 조회 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '알림 조회 중 오류가 발생했습니다.'
|
|
});
|
|
}
|
|
},
|
|
|
|
// 읽지 않은 알림 개수
|
|
async getUnreadCount(req, res) {
|
|
try {
|
|
const userId = req.user?.id || null;
|
|
const count = await notificationModel.getUnreadCount(userId);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: { count }
|
|
});
|
|
} catch (error) {
|
|
console.error('알림 개수 조회 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '알림 개수 조회 중 오류가 발생했습니다.'
|
|
});
|
|
}
|
|
},
|
|
|
|
// 알림 읽음 처리
|
|
async markAsRead(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
const success = await notificationModel.markAsRead(id);
|
|
|
|
res.json({
|
|
success,
|
|
message: success ? '알림을 읽음 처리했습니다.' : '알림을 찾을 수 없습니다.'
|
|
});
|
|
} catch (error) {
|
|
console.error('알림 읽음 처리 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '알림 처리 중 오류가 발생했습니다.'
|
|
});
|
|
}
|
|
},
|
|
|
|
// 모든 알림 읽음 처리
|
|
async markAllAsRead(req, res) {
|
|
try {
|
|
const userId = req.user?.id || null;
|
|
const count = await notificationModel.markAllAsRead(userId);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: `${count}개의 알림을 읽음 처리했습니다.`,
|
|
data: { count }
|
|
});
|
|
} catch (error) {
|
|
console.error('전체 읽음 처리 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '알림 처리 중 오류가 발생했습니다.'
|
|
});
|
|
}
|
|
},
|
|
|
|
// 알림 삭제
|
|
async delete(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
const success = await notificationModel.delete(id);
|
|
|
|
res.json({
|
|
success,
|
|
message: success ? '알림을 삭제했습니다.' : '알림을 찾을 수 없습니다.'
|
|
});
|
|
} catch (error) {
|
|
console.error('알림 삭제 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '알림 삭제 중 오류가 발생했습니다.'
|
|
});
|
|
}
|
|
},
|
|
|
|
// 알림 생성 (시스템용)
|
|
async create(req, res) {
|
|
try {
|
|
const { type, title, message, link_url, user_id } = req.body;
|
|
|
|
if (!title) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: '알림 제목은 필수입니다.'
|
|
});
|
|
}
|
|
|
|
const notificationId = await notificationModel.create({
|
|
user_id,
|
|
type,
|
|
title,
|
|
message,
|
|
link_url,
|
|
created_by: req.user?.id
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '알림이 생성되었습니다.',
|
|
data: { notification_id: notificationId }
|
|
});
|
|
} catch (error) {
|
|
console.error('알림 생성 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '알림 생성 중 오류가 발생했습니다.'
|
|
});
|
|
}
|
|
},
|
|
|
|
// 내부 서비스용 알림 생성 (X-Internal-Service-Key 인증)
|
|
async createInternal(req, res) {
|
|
try {
|
|
const serviceKey = req.headers['x-internal-service-key'];
|
|
if (!serviceKey || serviceKey !== process.env.INTERNAL_SERVICE_KEY) {
|
|
return res.status(403).json({ success: false, message: '권한이 없습니다.' });
|
|
}
|
|
|
|
const { type, title, message, link_url, reference_type, reference_id, created_by } = req.body;
|
|
|
|
if (!title) {
|
|
return res.status(400).json({ success: false, message: '알림 제목은 필수입니다.' });
|
|
}
|
|
|
|
const results = await notificationModel.createTypedNotification({
|
|
type: type || 'system',
|
|
title,
|
|
message,
|
|
link_url,
|
|
reference_type,
|
|
reference_id,
|
|
created_by
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '알림이 생성되었습니다.',
|
|
data: { notification_ids: Array.isArray(results) ? results : [results] }
|
|
});
|
|
} catch (error) {
|
|
console.error('내부 알림 생성 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '알림 생성 중 오류가 발생했습니다.'
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
module.exports = notificationController;
|