157 lines
4.9 KiB
JavaScript
157 lines
4.9 KiB
JavaScript
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;
|