Support uploading structured JSON to create courses, classes, and students without wiping existing data, and merge user name fields while adding gender and registration details from source sheets.
139 lines
4.5 KiB
JavaScript
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 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 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
|
|
};
|