// server/src/services/notifications.ts import { createTransport } from 'nodemailer'; import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'; import { resolve } from 'path'; import { randomUUID } from 'crypto'; import type { MonitorUser, Notification, UserRole } from '../types/index.js'; // ─── Persistent JSON storage (simple file-based) ─── const DATA_DIR = resolve(process.cwd(), 'data'); const USERS_FILE = resolve(DATA_DIR, 'users.json'); const NOTIFICATIONS_FILE = resolve(DATA_DIR, 'notifications.json'); function ensureDataDir() { try { mkdirSync(DATA_DIR, { recursive: true }); } catch {} } function loadUsers(): MonitorUser[] { ensureDataDir(); if (!existsSync(USERS_FILE)) { // Seed with the existing config users const seed: MonitorUser[] = [ { id: randomUUID(), email: 'nafih@bshtechnologies.in', name: 'Nafih', role: 'server-admin', notifyOnError: true, createdAt: new Date().toISOString() }, { id: randomUUID(), email: 'vivek@bshtechnologies.in', name: 'Vivek', role: 'developer', notifyOnError: true, createdAt: new Date().toISOString() }, ]; writeFileSync(USERS_FILE, JSON.stringify(seed, null, 2)); return seed; } return JSON.parse(readFileSync(USERS_FILE, 'utf-8')); } function saveUsers(users: MonitorUser[]) { ensureDataDir(); writeFileSync(USERS_FILE, JSON.stringify(users, null, 2)); } function loadNotifications(): Notification[] { ensureDataDir(); if (!existsSync(NOTIFICATIONS_FILE)) { writeFileSync(NOTIFICATIONS_FILE, '[]'); return []; } return JSON.parse(readFileSync(NOTIFICATIONS_FILE, 'utf-8')); } function saveNotifications(notifs: Notification[]) { ensureDataDir(); // Keep only last 200 notifications const trimmed = notifs.slice(-200); writeFileSync(NOTIFICATIONS_FILE, JSON.stringify(trimmed, null, 2)); } // ─── SMTP Transport ─── const smtpTransport = createTransport({ host: process.env.SMTP_HOST ?? 'mail.bshtech.net', port: parseInt(process.env.SMTP_PORT ?? '587'), secure: false, auth: { user: process.env.SMTP_USER ?? 'noreply@bshtech.net', pass: process.env.SMTP_PASS ?? 'Ev3ntifyN0Reply2026', }, tls: { rejectUnauthorized: false }, }); // ─── Public API ─── export function getUsers(): MonitorUser[] { return loadUsers(); } export function addUser(email: string, name: string, role: UserRole, notifyOnError = true): MonitorUser { const users = loadUsers(); if (users.find(u => u.email === email)) { throw new Error(`User with email ${email} already exists`); } const user: MonitorUser = { id: randomUUID(), email, name, role, notifyOnError, createdAt: new Date().toISOString(), }; users.push(user); saveUsers(users); return user; } export function updateUser(id: string, updates: Partial>): MonitorUser { const users = loadUsers(); const idx = users.findIndex(u => u.id === id); if (idx < 0) throw new Error('User not found'); users[idx] = { ...users[idx], ...updates }; saveUsers(users); return users[idx]; } export function deleteUser(id: string): void { const users = loadUsers(); const filtered = users.filter(u => u.id !== id); if (filtered.length === users.length) throw new Error('User not found'); saveUsers(filtered); } export function getNotifications(limit = 50): Notification[] { return loadNotifications().reverse().slice(0, limit); } export function markNotificationRead(id: string): void { const notifs = loadNotifications(); const n = notifs.find(n => n.id === id); if (n) { n.read = true; saveNotifications(notifs); } } export function markAllRead(): void { const notifs = loadNotifications(); notifs.forEach(n => n.read = true); saveNotifications(notifs); } export function getUnreadCount(): number { return loadNotifications().filter(n => !n.read).length; } export async function createNotification( type: Notification['type'], title: string, message: string, source: string, ): Promise { const notif: Notification = { id: randomUUID(), type, title, message, source, timestamp: new Date().toISOString(), read: false, emailSent: false, }; // Save to file const notifs = loadNotifications(); notifs.push(notif); saveNotifications(notifs); // Send email to subscribed users if (type === 'error' || type === 'warning') { const users = loadUsers().filter(u => u.notifyOnError); if (users.length > 0) { try { await smtpTransport.sendMail({ from: '"Eventify Monitor" ', to: users.map(u => u.email).join(', '), subject: `[${type.toUpperCase()}] ${title}`, html: `

${type === 'error' ? 'Error Alert' : 'Warning'}: ${title}

${message}

Source: ${source}

Time: ${new Date().toLocaleString()}


View Dashboard | Eventify Server Monitor

`, }); notif.emailSent = true; // Update the saved notification const updated = loadNotifications(); const idx = updated.findIndex(n => n.id === notif.id); if (idx >= 0) { updated[idx].emailSent = true; saveNotifications(updated); } console.log(`[Notifications] Email sent to ${users.length} users for: ${title}`); } catch (err) { console.error(`[Notifications] Failed to send email:`, err); } } } return notif; }