/** * Geofence Notification Service * Handles sending email notifications for geofence events */ import { emailService } from './email-service'; import { renderGeofenceEnterEmail, renderGeofenceExitEmail, GeofenceEmailData, } from './email-renderer'; import { geofenceDb } from './geofence-db'; import type { GeofenceEvent } from './types'; import Database from 'better-sqlite3'; import path from 'path'; const dbPath = path.join(process.cwd(), 'data', 'database.sqlite'); interface DeviceInfo { id: string; name: string; ownerId: string; } interface UserInfo { id: string; username: string; email: string | null; } interface GeofenceInfo { id: string; name: string; owner_id: string; } /** * Get device information from database */ function getDevice(deviceId: string): DeviceInfo | null { const db = new Database(dbPath); try { const stmt = db.prepare('SELECT id, name, ownerId FROM Device WHERE id = ?'); return stmt.get(deviceId) as DeviceInfo | null; } finally { db.close(); } } /** * Get user information from database */ function getUser(userId: string): UserInfo | null { const db = new Database(dbPath); try { const stmt = db.prepare('SELECT id, username, email FROM User WHERE id = ?'); return stmt.get(userId) as UserInfo | null; } finally { db.close(); } } /** * Send notification for a single geofence event */ async function sendEventNotification(event: GeofenceEvent): Promise { try { // Get geofence details const geofence = geofenceDb.findById(event.geofence_id); if (!geofence) { throw new Error(`Geofence not found: ${event.geofence_id}`); } // Get device details const device = getDevice(event.device_id); if (!device) { throw new Error(`Device not found: ${event.device_id}`); } // Get owner details const owner = getUser(geofence.owner_id); if (!owner || !owner.email) { console.log(`[GeofenceNotification] No email for owner ${geofence.owner_id}, skipping notification`); geofenceDb.markNotificationSent(event.id!, true); // Mark as "sent" (no email needed) return; } // Prepare email data const emailData: GeofenceEmailData = { username: owner.username, deviceName: device.name, geofenceName: geofence.name, timestamp: event.timestamp, latitude: event.latitude, longitude: event.longitude, distanceFromCenter: event.distance_from_center || 0, // Optional: Add map URL later // mapUrl: `${process.env.NEXT_PUBLIC_URL}/map?lat=${event.latitude}&lon=${event.longitude}` }; // Render and send email let html: string; let subject: string; if (event.event_type === 'enter') { html = await renderGeofenceEnterEmail(emailData); subject = `${device.name} hat ${geofence.name} betreten`; } else { html = await renderGeofenceExitEmail(emailData); subject = `${device.name} hat ${geofence.name} verlassen`; } // Send via existing email service await emailService['sendEmail'](owner.email, subject, html); // Mark notification as sent geofenceDb.markNotificationSent(event.id!, true); console.log(`[GeofenceNotification] Sent ${event.event_type} notification for geofence ${geofence.name} to ${owner.email}`); } catch (error) { console.error('[GeofenceNotification] Failed to send notification:', error); // Mark notification as failed const errorMessage = error instanceof Error ? error.message : 'Unknown error'; geofenceDb.markNotificationSent(event.id!, false, errorMessage); throw error; } } /** * Send notifications for multiple geofence events * Processes events sequentially to avoid overwhelming SMTP server */ export async function sendGeofenceNotifications(events: GeofenceEvent[]): Promise { if (events.length === 0) { return; } console.log(`[GeofenceNotification] Processing ${events.length} geofence event(s)`); for (const event of events) { try { await sendEventNotification(event); } catch (error) { // Log error but continue with other notifications console.error(`[GeofenceNotification] Failed to send notification for event ${event.id}:`, error); } } }