const { Notification, User } = require('../models'); const logger = require('../config/logger'); const { Op } = require('sequelize'); /** * Admin Notification Management Service */ class AdminNotificationService { /** * Get all notifications with pagination and filtering (admin view) */ static async getNotifications({ page = 1, limit = 20, type, userId, isRead, search }) { try { const offset = (page - 1) * limit; const where = {}; if (type) where.type = type; if (userId) where.userId = userId; if (isRead !== undefined && isRead !== '') { where.isRead = isRead === 'true' || isRead === true; } if (search) { where[Op.or] = [ { titleZh: { [Op.like]: `%${search}%` } }, { titleEn: { [Op.like]: `%${search}%` } }, { contentZh: { [Op.like]: `%${search}%` } }, ]; } const { count, rows } = await Notification.findAndCountAll({ where, include: [{ model: User, as: 'user', attributes: ['id', 'nickname', 'phone'] }], order: [['createdAt', 'DESC']], limit: parseInt(limit), offset: parseInt(offset), }); return { data: rows.map(n => n.toJSON()), pagination: { page: parseInt(page), limit: parseInt(limit), total: count, totalPages: Math.ceil(count / limit) }, }; } catch (error) { logger.error('Error fetching notifications (admin):', error); throw error; } } /** * Send notification to specific user (supports both userId and uid) */ static async sendToUser(userId, notificationData) { try { let user; // Support both UUID and UID (6-digit number) if (/^\d{6}$/.test(userId)) { user = await User.findOne({ where: { uid: userId } }); } else { user = await User.findByPk(userId); } if (!user) { const error = new Error('User not found'); error.statusCode = 404; throw error; } const notification = await Notification.create({ userId: user.id, type: notificationData.type || 'system', titleZh: notificationData.titleZh, titleEn: notificationData.titleEn || notificationData.titleZh, titleEs: notificationData.titleEs || notificationData.titleZh, contentZh: notificationData.contentZh, contentEn: notificationData.contentEn || notificationData.contentZh, contentEs: notificationData.contentEs || notificationData.contentZh, isRead: false, }); logger.info(`Admin sent notification to user ${user.id} (uid: ${user.uid})`); return notification.toJSON(); } catch (error) { logger.error('Error sending notification:', error); throw error; } } /** * Send notification to all users (broadcast) */ static async broadcast(notificationData) { try { const users = await User.findAll({ attributes: ['id'] }); const notifications = users.map(user => ({ userId: user.id, type: notificationData.type || 'system', titleZh: notificationData.titleZh, titleEn: notificationData.titleEn || notificationData.titleZh, titleEs: notificationData.titleEs || notificationData.titleZh, contentZh: notificationData.contentZh, contentEn: notificationData.contentEn || notificationData.contentZh, contentEs: notificationData.contentEs || notificationData.contentZh, isRead: false, })); await Notification.bulkCreate(notifications); logger.info(`Admin broadcast notification to ${users.length} users`); return { count: users.length }; } catch (error) { logger.error('Error broadcasting notification:', error); throw error; } } /** * Delete notification by admin */ static async deleteNotification(notificationId) { try { const notification = await Notification.findByPk(notificationId); if (!notification) { const error = new Error('Notification not found'); error.statusCode = 404; throw error; } await notification.destroy(); logger.info(`Admin deleted notification ${notificationId}`); return true; } catch (error) { logger.error(`Error deleting notification ${notificationId}:`, error); throw error; } } /** * Get notification statistics */ static async getStatistics() { try { const [total, unread, byType] = await Promise.all([ Notification.count(), Notification.count({ where: { isRead: false } }), Notification.findAll({ attributes: ['type', [require('sequelize').fn('COUNT', '*'), 'count']], group: ['type'], raw: true, }), ]); return { total, unread, read: total - unread, byType }; } catch (error) { logger.error('Error getting notification statistics:', error); throw error; } } } module.exports = AdminNotificationService;