/** * 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};