/** * TASKMATE - Reminder Service * =========================== * Service für Erinnerungsbenachrichtigungen und Scheduling */ const { getDb } = require('../database'); const notificationService = require('./notificationService'); const logger = require('../utils/logger'); class ReminderService { constructor(io = null) { this.io = io; this.intervalId = null; this.isRunning = false; } /** * Startet den Reminder-Check-Service * Läuft alle 5 Minuten (kann für Produktion auf 1 Stunde erhöht werden) */ start() { if (this.isRunning) { logger.warn('Reminder Service ist bereits gestartet'); return; } this.isRunning = true; // Sofort prüfen this.checkDueReminders(); // Dann alle 5 Minuten (300000 ms) // In Produktion könnte das auf 1 Stunde (3600000 ms) erhöht werden this.intervalId = setInterval(() => { this.checkDueReminders(); }, 300000); // 5 Minuten logger.info('Reminder Service gestartet - prüft alle 5 Minuten'); } /** * Stoppt den Reminder-Check-Service */ stop() { if (!this.isRunning) { return; } if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; } this.isRunning = false; logger.info('Reminder Service gestoppt'); } /** * Prüft fällige Erinnerungen und sendet Benachrichtigungen */ async checkDueReminders() { try { const db = getDb(); const today = new Date().toISOString().split('T')[0]; // Finde alle fälligen Benachrichtigungen die noch nicht gesendet wurden const dueNotifications = db.prepare(` SELECT rn.*, r.id as reminder_id, r.title as reminder_title, r.description as reminder_description, r.reminder_date, r.reminder_time, r.color, r.project_id, r.created_by, r.advance_days FROM reminder_notifications rn JOIN reminders r ON rn.reminder_id = r.id WHERE rn.notification_date <= ? AND rn.sent = 0 AND r.is_active = 1 ORDER BY rn.notification_date ASC, r.reminder_time ASC `).all(today); if (dueNotifications.length === 0) { logger.debug('Keine fälligen Erinnerungen gefunden'); return; } logger.info(`${dueNotifications.length} fällige Erinnerung(en) gefunden`); // Verarbeite jede fällige Benachrichtigung for (const notification of dueNotifications) { await this.processReminderNotification(notification); } } catch (error) { logger.error('Fehler beim Prüfen fälliger Erinnerungen:', error); } } /** * Verarbeitet eine einzelne fällige Erinnerungs-Benachrichtigung */ async processReminderNotification(notification) { try { const db = getDb(); // Berechne wie viele Tage im Voraus diese Benachrichtigung ist const reminderDate = new Date(notification.reminder_date); const notificationDate = new Date(notification.notification_date); const daysDiff = Math.ceil((reminderDate - notificationDate) / (1000 * 60 * 60 * 24)); const reminder = { id: notification.reminder_id, title: notification.reminder_title, description: notification.reminder_description, project_id: notification.project_id, created_by: notification.created_by, color: notification.color }; // Erstelle Benachrichtigung const createdNotification = notificationService.createReminderNotification( reminder, daysDiff, this.io ); if (createdNotification) { // Markiere als gesendet db.prepare(` UPDATE reminder_notifications SET sent = 1 WHERE id = ? `).run(notification.id); logger.info(`Reminder-Benachrichtigung gesendet: "${notification.reminder_title}" (${daysDiff} Tage vorher)`); } } catch (error) { logger.error(`Fehler beim Verarbeiten der Reminder-Benachrichtigung ${notification.id}:`, error); } } /** * Erstellt Benachrichtigungstermine für eine neue Erinnerung */ createNotificationSchedule(reminderId, reminderDate, advanceDays) { try { const db = getDb(); const baseDate = new Date(reminderDate); // Lösche alte Termine falls vorhanden db.prepare('DELETE FROM reminder_notifications WHERE reminder_id = ?').run(reminderId); // Erstelle neue Termine für jeden advance day advanceDays.forEach(days => { const notificationDate = new Date(baseDate); notificationDate.setDate(notificationDate.getDate() - parseInt(days)); const notificationDateStr = notificationDate.toISOString().split('T')[0]; // Nur zukünftige Termine erstellen const today = new Date().toISOString().split('T')[0]; if (notificationDateStr >= today) { db.prepare(` INSERT OR IGNORE INTO reminder_notifications (reminder_id, notification_date) VALUES (?, ?) `).run(reminderId, notificationDateStr); } }); logger.debug(`Benachrichtigungstermine erstellt für Reminder ${reminderId}`); } catch (error) { logger.error(`Fehler beim Erstellen der Benachrichtigungstermine für Reminder ${reminderId}:`, error); } } /** * Manuelle Prüfung für API-Endpoint */ async manualCheck() { logger.info('Manuelle Reminder-Prüfung ausgelöst'); return await this.checkDueReminders(); } /** * Statistiken für Debugging */ getStats() { try { const db = getDb(); const stats = { isRunning: this.isRunning, activeReminders: db.prepare('SELECT COUNT(*) as count FROM reminders WHERE is_active = 1').get().count, pendingNotifications: db.prepare('SELECT COUNT(*) as count FROM reminder_notifications WHERE sent = 0').get().count, nextDueDate: db.prepare(` SELECT MIN(notification_date) as next_date FROM reminder_notifications WHERE sent = 0 AND notification_date >= date('now') `).get().next_date }; return stats; } catch (error) { logger.error('Fehler beim Abrufen der Reminder-Statistiken:', error); return { error: error.message }; } } /** * Socket.io Instanz setzen/aktualisieren */ setSocketIO(io) { this.io = io; logger.debug('Socket.IO Instanz für Reminder Service aktualisiert'); } } // Singleton Export let instance = null; module.exports = { getInstance(io = null) { if (!instance) { instance = new ReminderService(io); } else if (io) { instance.setSocketIO(io); } return instance; }, // Für Tests und Debugging createInstance(io = null) { return new ReminderService(io); } };