- 4단계 상태 플로우: pending → grouped → purchased → received - 한국어 스마트 검색: 초성 매칭(ㅁㅈㄱ→면장갑), 별칭 테이블, 인메모리 캐시 - 모바일 전용 신청 페이지: 바텀시트 UI, FAB, 카드 리스트, 스크롤 페이지네이션 - 인라인 품목 등록: 미등록 품목 검색→등록→신청 단일 트랜잭션 - 관리자 그룹화: 체크박스 다중 선택, 구매 그룹(batch) 생성/일괄 구매/입고 - 입고 처리: 사진+보관위치 등록, 부분 입고 허용, batch 자동 상태 전환 - 알림: notifyHelper에 target_user_ids 추가, 구매진행중/입고완료 시 신청자 ntfy+push Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
219 lines
6.0 KiB
JavaScript
219 lines
6.0 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, target_user_ids } = req.body;
|
|
|
|
if (!title) {
|
|
return res.status(400).json({ success: false, message: '알림 제목은 필수입니다.' });
|
|
}
|
|
|
|
let results;
|
|
if (target_user_ids && Array.isArray(target_user_ids) && target_user_ids.length > 0) {
|
|
// 특정 사용자 직접 알림 (type 기반 브로드캐스트 대신)
|
|
results = await notificationModel.createTargetedNotification({
|
|
type: type || 'system',
|
|
title,
|
|
message,
|
|
link_url,
|
|
reference_type,
|
|
reference_id,
|
|
created_by,
|
|
target_user_ids
|
|
});
|
|
} else {
|
|
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;
|