diff --git a/user-management/api/controllers/notificationRecipientController.js b/user-management/api/controllers/notificationRecipientController.js index d656a7a..e8ec1c6 100644 --- a/user-management/api/controllers/notificationRecipientController.js +++ b/user-management/api/controllers/notificationRecipientController.js @@ -83,6 +83,52 @@ const notificationRecipientController = { } }, + // === ntfy 구독 관리 === + + // ntfy 구독자 목록 + getNtfySubscribers: async (req, res) => { + try { + const subscribers = await notificationRecipientModel.getNtfySubscribers(); + res.json({ success: true, data: subscribers }); + } catch (error) { + console.error('ntfy 구독자 조회 오류:', error); + res.status(500).json({ success: false, error: 'ntfy 구독자 조회 실패' }); + } + }, + + // ntfy 구독 등록 (관리자) + addNtfySubscription: async (req, res) => { + try { + const { userId } = req.params; + if (!userId) { + return res.status(400).json({ success: false, error: '사용자 ID가 필요합니다.' }); + } + if (!await checkNrPermission(req.user)) { + return res.status(403).json({ success: false, error: '알림 수신자 관리 권한이 없습니다.' }); + } + await notificationRecipientModel.addNtfySubscription(Number(userId)); + res.json({ success: true, message: 'ntfy 구독이 등록되었습니다.' }); + } catch (error) { + console.error('ntfy 구독 등록 오류:', error); + res.status(500).json({ success: false, error: 'ntfy 구독 등록 실패' }); + } + }, + + // ntfy 구독 해제 (관리자) + removeNtfySubscription: async (req, res) => { + try { + const { userId } = req.params; + if (!await checkNrPermission(req.user)) { + return res.status(403).json({ success: false, error: '알림 수신자 관리 권한이 없습니다.' }); + } + await notificationRecipientModel.removeNtfySubscription(Number(userId)); + res.json({ success: true, message: 'ntfy 구독이 해제되었습니다.' }); + } catch (error) { + console.error('ntfy 구독 해제 오류:', error); + res.status(500).json({ success: false, error: 'ntfy 구독 해제 실패' }); + } + }, + // 유형별 수신자 일괄 설정 setRecipients: async (req, res) => { try { diff --git a/user-management/api/models/notificationRecipientModel.js b/user-management/api/models/notificationRecipientModel.js index 2d31edf..5db93ea 100644 --- a/user-management/api/models/notificationRecipientModel.js +++ b/user-management/api/models/notificationRecipientModel.js @@ -138,6 +138,29 @@ const notificationRecipientModel = { return !!row; }, + // === ntfy 구독 관리 === + + async getNtfySubscribers() { + const db = getPool(); + const [rows] = await db.query(` + SELECT ns.user_id, ns.created_at, u.username, u.name as user_name + FROM ntfy_subscriptions ns + JOIN sso_users u ON u.user_id = ns.user_id + ORDER BY u.name + `); + return rows; + }, + + async addNtfySubscription(userId) { + const db = getPool(); + await db.query('INSERT IGNORE INTO ntfy_subscriptions (user_id) VALUES (?)', [userId]); + }, + + async removeNtfySubscription(userId) { + const db = getPool(); + await db.query('DELETE FROM ntfy_subscriptions WHERE user_id = ?', [userId]); + }, + // 유형별 수신자 일괄 설정 async setRecipients(notificationType, userIds, createdBy = null) { const db = getPool(); diff --git a/user-management/api/routes/notificationRecipientRoutes.js b/user-management/api/routes/notificationRecipientRoutes.js index e536f94..6cac7c4 100644 --- a/user-management/api/routes/notificationRecipientRoutes.js +++ b/user-management/api/routes/notificationRecipientRoutes.js @@ -13,6 +13,11 @@ router.get('/types', controller.getTypes); // 전체 수신자 목록 (유형별 그룹화) router.get('/', controller.getAll); +// ntfy 구독 관리 (/:type보다 위에 배치해야 "ntfy"를 type으로 잡지 않음) +router.get('/ntfy', controller.getNtfySubscribers); +router.post('/ntfy/:userId', controller.addNtfySubscription); +router.delete('/ntfy/:userId', controller.removeNtfySubscription); + // 유형별 수신자 조회 router.get('/:type', controller.getByType); diff --git a/user-management/web/index.html b/user-management/web/index.html index d884492..a1538cc 100644 --- a/user-management/web/index.html +++ b/user-management/web/index.html @@ -1582,6 +1582,24 @@ + +
등록된 사용자가 실제로 알림을 받으려면 ntfy 앱을 설치하고 본인 토픽을 구독해야 합니다. 앱 설정은 각 사용자가 알림 벨의 ntfy 버튼을 통해 안내받을 수 있습니다.
+