@ideal-photography/shared
Version:
Shared MongoDB and utility logic for Ideal Photography PWAs: users, products, services, bookings, orders/cart, galleries, reviews, notifications, campaigns, settings, audit logs, minimart items/orders, and push notification subscriptions.
107 lines (93 loc) • 4.53 kB
JavaScript
import { models } from '../mongoDB/index.js';
import { sendNotificationToUsers, sendNotificationToAdmins, sendNotificationToAllUsers, shouldSendPushNotification } from '../utils/notifications.js';
import EmailNotificationService from './EmailNotificationService.js';
/**
* NotificationService handles persistence of Notification documents
* and dispatching of channel-specific deliveries (currently push only).
*/
class NotificationService {
/**
* Persist a notification document and enqueue channel dispatches.
*
* @param {object} alertMeta Output from a handler: { title, message, type, recipients, channels, priority }
* @param {object} opts Additional options passed from emitAlert (e.g., override channels, priority)
*/
static async createAndSend(alertMeta, opts = {}) {
const meta = { ...alertMeta };
// Apply option overrides
if (opts.channels) meta.channels = { ...meta.channels, ...opts.channels };
if (opts.priority) meta.priority = opts.priority;
// Persist notification (status draft initially)
const Notification = models.Notification || (await import('../models/Notification.js')).default;
const notificationDoc = await Notification.create({
...meta,
status: 'sent', // We send immediately for now
trigger: opts.trigger || null,
sentAt: new Date(),
createdBy: meta.createdBy || null // should be set by handler if system user
});
// Dispatch push notifications if enabled
if (shouldSendPushNotification(meta)) {
const payload = {
title: meta.title,
body: meta.message,
tag: meta.type,
url: meta.url || '/',
priority: meta.priority,
data: meta.data || {}
};
// Broadcast to all users
if (meta.recipients?.broadcast) {
await sendNotificationToAllUsers({ ...payload, tag: meta.type });
}
// Target specific users
const userIds = Array.isArray(meta.recipients?.users) ? meta.recipients.users : [];
if (userIds.length) {
await sendNotificationToUsers(userIds, payload);
}
// Target admins by roles
const roles = Array.isArray(meta.recipients?.roles) ? meta.recipients.roles : [];
const adminRoles = ['admin', 'manager', 'super_admin'];
if (roles.some(r => adminRoles.includes(r))) {
const admins = await models.Admin.find({ role: { $in: adminRoles }, isActive: true }).select('_id');
const adminIds = admins.map(a => a._id);
if (adminIds.length) {
await sendNotificationToAdmins(adminIds, payload);
}
}
}
// Dispatch email if enabled
if (meta.channels?.email) {
const recipientEmails = [];
// 1. User-specific emails
if (Array.isArray(meta.recipients?.users) && meta.recipients.users.length) {
const users = await models.User.find({ _id: { $in: meta.recipients.users } }).select('email name');
users.forEach(u => {
if (u?.email) {
recipientEmails.push({ email: u.email, name: u.name });
}
});
}
// 2. Admin/broadcast emails – use SMTP_USER env variable
const wantsAdminEmail = meta.recipients?.admins || meta.recipients?.roles?.includes?.('admin');
if (wantsAdminEmail) {
const adminEmail = process.env.SMTP_USER; // Single source of truth
if (adminEmail) {
recipientEmails.push({ email: adminEmail, name: 'Admin' });
}
}
if (recipientEmails.length) {
const sendPromises = recipientEmails.map(({ email, name }) =>
EmailNotificationService.send(
meta.emailTemplate || meta.type,
{ name, ...meta.emailData },
[email]
)
);
await Promise.allSettled(sendPromises);
}
}
return notificationDoc;
}
}
export default NotificationService;