feat: implement comprehensive Firebase Functions backend for equipment management and migrate core repository services
This commit is contained in:
@@ -3,10 +3,10 @@
|
||||
* 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');
|
||||
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
|
||||
@@ -14,11 +14,11 @@ const { getSmtpConfig } = require('./utils/emailConfig');
|
||||
async function sendDailyDigest() {
|
||||
const db = admin.firestore();
|
||||
|
||||
logger.info('[sendDailyDigest] ===== DÉBUT ENVOI DIGEST QUOTIDIEN =====');
|
||||
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 usersSnapshot = await db.collection("users").get();
|
||||
const eligibleUsers = [];
|
||||
|
||||
usersSnapshot.forEach((doc) => {
|
||||
@@ -30,8 +30,8 @@ async function sendDailyDigest() {
|
||||
eligibleUsers.push({
|
||||
uid: doc.id,
|
||||
email: user.email,
|
||||
firstName: user.firstName || 'Utilisateur',
|
||||
lastName: user.lastName || '',
|
||||
firstName: user.firstName || "Utilisateur",
|
||||
lastName: user.lastName || "",
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -48,12 +48,12 @@ async function sendDailyDigest() {
|
||||
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();
|
||||
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
|
||||
@@ -61,7 +61,7 @@ async function sendDailyDigest() {
|
||||
|
||||
const alerts = [];
|
||||
alertsSnapshot.forEach((doc) => {
|
||||
alerts.push({ id: doc.id, ...doc.data() });
|
||||
alerts.push({id: doc.id, ...doc.data()});
|
||||
});
|
||||
|
||||
logger.info(`[sendDailyDigest] ${user.email}: ${alerts.length} alertes non lues`);
|
||||
@@ -77,11 +77,11 @@ async function sendDailyDigest() {
|
||||
}
|
||||
|
||||
logger.info(`[sendDailyDigest] ✓ ${emailsSent}/${eligibleUsers.length} emails envoyés`);
|
||||
logger.info('[sendDailyDigest] ===== FIN DIGEST QUOTIDIEN =====');
|
||||
logger.info("[sendDailyDigest] ===== FIN DIGEST QUOTIDIEN =====");
|
||||
|
||||
return { success: true, emailsSent };
|
||||
return {success: true, emailsSent};
|
||||
} catch (error) {
|
||||
logger.error('[sendDailyDigest] Erreur globale:', error);
|
||||
logger.error("[sendDailyDigest] Erreur globale:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -92,9 +92,9 @@ async function sendDailyDigest() {
|
||||
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');
|
||||
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, {
|
||||
@@ -125,7 +125,7 @@ async function sendDigestEmail(transporter, user, alerts) {
|
||||
function buildDigestHtml(user, alertsByType) {
|
||||
const totalAlerts = alertsByType.critical.length + alertsByType.warning.length + alertsByType.info.length;
|
||||
|
||||
let alertsHtml = '';
|
||||
let alertsHtml = "";
|
||||
|
||||
// Alertes critiques
|
||||
if (alertsByType.critical.length > 0) {
|
||||
@@ -134,7 +134,7 @@ function buildDigestHtml(user, alertsByType) {
|
||||
<h3 style="color: #dc2626; margin: 0 0 12px 0;">
|
||||
🔴 Alertes critiques (${alertsByType.critical.length})
|
||||
</h3>
|
||||
${alertsByType.critical.map(alert => formatAlertItem(alert)).join('')}
|
||||
${alertsByType.critical.map((alert) => formatAlertItem(alert)).join("")}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -146,7 +146,7 @@ function buildDigestHtml(user, alertsByType) {
|
||||
<h3 style="color: #f59e0b; margin: 0 0 12px 0;">
|
||||
⚠️ Avertissements (${alertsByType.warning.length})
|
||||
</h3>
|
||||
${alertsByType.warning.map(alert => formatAlertItem(alert)).join('')}
|
||||
${alertsByType.warning.map((alert) => formatAlertItem(alert)).join("")}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -158,7 +158,7 @@ function buildDigestHtml(user, alertsByType) {
|
||||
<h3 style="color: #3b82f6; margin: 0 0 12px 0;">
|
||||
ℹ️ Informations (${alertsByType.info.length})
|
||||
</h3>
|
||||
${alertsByType.info.map(alert => formatAlertItem(alert)).join('')}
|
||||
${alertsByType.info.map((alert) => formatAlertItem(alert)).join("")}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -216,24 +216,24 @@ function buildDigestHtml(user, alertsByType) {
|
||||
*/
|
||||
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'
|
||||
new Date(alert.createdAt.toDate()).toLocaleString("fr-FR", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}) :
|
||||
'Date inconnue';
|
||||
"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',
|
||||
"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;
|
||||
@@ -245,7 +245,7 @@ function formatAlertItem(alert) {
|
||||
<span style="color: #6b7280; font-size: 13px;">${date}</span>
|
||||
</div>
|
||||
<p style="color: #4b5563; margin: 0; font-size: 14px; line-height: 1.5;">
|
||||
${alert.message || 'Aucun message'}
|
||||
${alert.message || "Aucun message"}
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
@@ -256,12 +256,12 @@ function formatAlertItem(alert) {
|
||||
*/
|
||||
function getSeverityColor(severity) {
|
||||
switch (severity) {
|
||||
case 'CRITICAL': return '#dc2626';
|
||||
case 'WARNING': return '#f59e0b';
|
||||
case 'INFO': return '#3b82f6';
|
||||
default: return '#6b7280';
|
||||
case "CRITICAL": return "#dc2626";
|
||||
case "WARNING": return "#f59e0b";
|
||||
case "INFO": return "#3b82f6";
|
||||
default: return "#6b7280";
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { sendDailyDigest };
|
||||
module.exports = {sendDailyDigest};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user