Files
gameno-api/components/notifications/notificationService.js
T
kavehhn f04c797be6 Initial commit: teaching institution management API.
Express/MongoDB backend with JWT auth, RBAC, S3 uploads, notifications, and scheduled jobs.
2026-08-09 04:18:08 +02:00

139 lines
4.5 KiB
JavaScript

// /components/notifications/notificationService.js
const Notification = require('./notificationModel');
const User = require('../users/userModel');
const AppError = require('../../utils/AppError');
const { sendEmail } = require('../../utils/senders/emailSender');
const { sendSMS } = require('../../utils/senders/smsSender');
const { sendBaleMessage } = require('../../utils/senders/baleBotSender');
const { parsePaginationAndSort, buildFilterQuery, calculateMeta } = require('../../utils/pagination');
const createNotification = async (data) => {
const user = await User.findById(data.user);
if (!user) throw new AppError('USER_NOT_FOUND');
const notification = await Notification.create(data);
// Attempt sending immediately
try {
if (data.channel === 'email' && user.email) {
await sendEmail({ to: user.email, subject: data.subject, body: data.body });
} else if (data.channel === 'baleBot') {
await sendBaleMessage({ chatId: user.phoneNumber, body: data.body });
} else {
await sendSMS({ phoneNumber: user.phoneNumber, body: data.body });
}
notification.status = 'sent';
notification.sentAt = new Date();
await notification.save();
} catch (err) {
notification.status = 'failed';
notification.lastError = err.message;
notification.retryCount = 1;
await notification.save();
}
return notification;
};
const getNotificationById = async (id) => {
const notification = await Notification.findById(id).populate('user', 'name surname username email phoneNumber');
if (!notification) {
throw new AppError('NOTIFICATION_NOT_FOUND');
}
return notification;
};
const getAllNotifications = async (queryParams) => {
const { page, limit, skip, sort } = parsePaginationAndSort(queryParams);
const filter = buildFilterQuery(queryParams, ['subject', 'body']);
const [notifications, totalCount] = await Promise.all([
Notification.find(filter).populate('user', 'name surname username').sort(sort).skip(skip).limit(limit),
Notification.countDocuments(filter)
]);
const meta = calculateMeta(totalCount, page, limit);
return { data: notifications, meta };
};
const updateNotification = async (id, updateData) => {
const notification = await Notification.findById(id);
if (!notification) {
throw new AppError('NOTIFICATION_NOT_FOUND');
}
Object.assign(notification, updateData);
await notification.save();
return notification;
};
const deleteNotification = async (id) => {
const notification = await Notification.findById(id);
if (!notification) {
throw new AppError('NOTIFICATION_NOT_FOUND');
}
await Notification.findByIdAndDelete(id);
return null;
};
const searchNotifications = async (queryParams) => {
return getAllNotifications(queryParams);
};
const retryNotification = async (id) => {
const notification = await Notification.findById(id).populate('user');
if (!notification) {
throw new AppError('NOTIFICATION_NOT_FOUND');
}
const user = notification.user;
if (!user) throw new AppError('USER_NOT_FOUND');
notification.status = 'retrying';
notification.retryCount += 1;
await notification.save();
try {
if (notification.channel === 'email' && user.email) {
await sendEmail({ to: user.email, subject: notification.subject, body: notification.body });
} else if (notification.channel === 'baleBot') {
await sendBaleMessage({ chatId: user.phoneNumber, body: notification.body });
} else {
await sendSMS({ phoneNumber: user.phoneNumber, body: notification.body });
}
notification.status = 'sent';
notification.sentAt = new Date();
await notification.save();
return { success: true, notification };
} catch (err) {
notification.status = 'failed';
notification.lastError = err.message;
await notification.save();
throw new Error(`Retry attempt ${notification.retryCount} failed: ${err.message}`);
}
};
const getMyNotifications = async (userId, queryParams) => {
const filter = { user: userId, ...buildFilterQuery(queryParams) };
const { page, limit, skip, sort } = parsePaginationAndSort(queryParams);
const [notifications, totalCount] = await Promise.all([
Notification.find(filter).sort(sort).skip(skip).limit(limit),
Notification.countDocuments(filter)
]);
const meta = calculateMeta(totalCount, page, limit);
return { data: notifications, meta };
};
module.exports = {
createNotification,
getNotificationById,
getAllNotifications,
updateNotification,
deleteNotification,
searchNotifications,
retryNotification,
getMyNotifications
};