/** * Fonction schedulée : Envoie quotidienne d'un résumé des alertes non lues * S'exécute tous les jours à 8h00 (Europe/Paris) */ const admin = require('firebase-admin'); const logger = require('firebase-functions/logger'); const nodemailer = require('nodemailer'); const { getSmtpConfig } = require('./utils/emailConfig'); /** * Fonction principale : envoie le digest quotidien */ async function sendDailyDigest() { const db = admin.firestore(); logger.info('[sendDailyDigest] ===== DÉBUT ENVOI DIGEST QUOTIDIEN ====='); try { // 1. Récupérer tous les utilisateurs avec email activé const usersSnapshot = await db.collection('users').get(); const eligibleUsers = []; usersSnapshot.forEach((doc) => { const user = doc.data(); const prefs = user.notificationPreferences || {}; // Vérifier si l'utilisateur a activé les emails if (prefs.emailEnabled !== false && user.email) { eligibleUsers.push({ uid: doc.id, email: user.email, firstName: user.firstName || 'Utilisateur', lastName: user.lastName || '', }); } }); logger.info(`[sendDailyDigest] ${eligibleUsers.length} utilisateurs éligibles`); // 2. Pour chaque utilisateur, récupérer ses alertes non lues des dernières 24h const now = admin.firestore.Timestamp.now(); const yesterday = admin.firestore.Timestamp.fromMillis(now.toMillis() - 24 * 60 * 60 * 1000); const transporter = nodemailer.createTransport(getSmtpConfig()); let emailsSent = 0; for (const user of eligibleUsers) { try { // Récupérer les alertes non lues de l'utilisateur créées dans les dernières 24h const alertsSnapshot = await db.collection('alerts') .where('assignedTo', 'array-contains', user.uid) .where('isRead', '==', false) .where('createdAt', '>=', yesterday) .orderBy('createdAt', 'desc') .get(); if (alertsSnapshot.empty) { continue; // Pas d'alertes non lues pour cet utilisateur } const alerts = []; alertsSnapshot.forEach((doc) => { alerts.push({ id: doc.id, ...doc.data() }); }); logger.info(`[sendDailyDigest] ${user.email}: ${alerts.length} alertes non lues`); // 3. Envoyer l'email de digest const sent = await sendDigestEmail(transporter, user, alerts); if (sent) { emailsSent++; } } catch (error) { logger.error(`[sendDailyDigest] Erreur pour ${user.email}:`, error); } } logger.info(`[sendDailyDigest] ✓ ${emailsSent}/${eligibleUsers.length} emails envoyés`); logger.info('[sendDailyDigest] ===== FIN DIGEST QUOTIDIEN ====='); return { success: true, emailsSent }; } catch (error) { logger.error('[sendDailyDigest] Erreur globale:', error); throw error; } } /** * Envoie l'email de digest à un utilisateur */ async function sendDigestEmail(transporter, user, alerts) { try { // Grouper les alertes par sévérité const criticalAlerts = alerts.filter(a => a.severity === 'CRITICAL'); const warningAlerts = alerts.filter(a => a.severity === 'WARNING'); const infoAlerts = alerts.filter(a => a.severity === 'INFO'); // Construire le HTML const html = buildDigestHtml(user, { critical: criticalAlerts, warning: warningAlerts, info: infoAlerts, }); // Envoyer l'email await transporter.sendMail({ from: `"EM2RP Notifications" <${process.env.SMTP_USER}>`, to: user.email, subject: `📬 ${alerts.length} nouvelle(s) alerte(s) EM2RP`, html, }); logger.info(`[sendDigestEmail] ✓ Email envoyé à ${user.email}`); return true; } catch (error) { logger.error(`[sendDigestEmail] Erreur pour ${user.email}:`, error); return false; } } /** * Construit le HTML du digest */ function buildDigestHtml(user, alertsByType) { const totalAlerts = alertsByType.critical.length + alertsByType.warning.length + alertsByType.info.length; let alertsHtml = ''; // Alertes critiques if (alertsByType.critical.length > 0) { alertsHtml += `

🔴 Alertes critiques (${alertsByType.critical.length})

${alertsByType.critical.map(alert => formatAlertItem(alert)).join('')}
`; } // Alertes warning if (alertsByType.warning.length > 0) { alertsHtml += `

⚠️ Avertissements (${alertsByType.warning.length})

${alertsByType.warning.map(alert => formatAlertItem(alert)).join('')}
`; } // Alertes info if (alertsByType.info.length > 0) { alertsHtml += `

ℹ️ Informations (${alertsByType.info.length})

${alertsByType.info.map(alert => formatAlertItem(alert)).join('')}
`; } return `

📬 Résumé quotidien

Bonjour ${user.firstName},

Vous avez ${totalAlerts} nouvelle(s) alerte(s) dans les dernières 24 heures.

${alertsHtml}
Voir toutes les alertes

EM2RP - Gestion d'événements

Gérer mes préférences de notification

`; } /** * Formate un item d'alerte pour l'email */ function formatAlertItem(alert) { const date = alert.createdAt?.toDate ? new Date(alert.createdAt.toDate()).toLocaleString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }) : 'Date inconnue'; // Type d'alerte en français const typeLabels = { 'EQUIPMENT_MISSING': 'Équipement manquant', 'LOST': 'Équipement perdu', 'DAMAGED': 'Équipement endommagé', 'QUANTITY_MISMATCH': 'Écart de quantité', 'EVENT_CREATED': 'Événement créé', 'EVENT_MODIFIED': 'Événement modifié', 'WORKFORCE_ADDED': 'Ajout à la workforce', }; const typeLabel = typeLabels[alert.type] || alert.type; return `
${typeLabel} ${date}

${alert.message || 'Aucun message'}

`; } /** * Retourne la couleur selon la sévérité */ function getSeverityColor(severity) { switch (severity) { case 'CRITICAL': return '#dc2626'; case 'WARNING': return '#f59e0b'; case 'INFO': return '#3b82f6'; default: return '#6b7280'; } } module.exports = { sendDailyDigest };