import { getDb } from "./db"; import { logsRappels, InsertLogRappel, LogRappel } from "../drizzle/schema"; import { eq, and } from "drizzle-orm"; /** * Enregistre l'envoi d'un rappel dans les logs */ export async function logRappelEnvoye(data: { rappelId: number; sequenceId: number; apprenantId: number; emailDestinataire: string; typeRappel: string; statut: "success" | "failed"; messageErreur?: string; dateSequence: Date; }): Promise { const db = await getDb(); if (!db) { console.warn("[LogsRappels] Base de données non disponible"); return; } try { await db.insert(logsRappels).values({ rappelId: data.rappelId, sequenceId: data.sequenceId, apprenantId: data.apprenantId, emailDestinataire: data.emailDestinataire, typeRappel: data.typeRappel, dateEnvoi: new Date(), statut: data.statut, messageErreur: data.messageErreur || null, dateSequence: data.dateSequence, } as InsertLogRappel); } catch (error) { console.error("[LogsRappels] Erreur lors de l'enregistrement du log:", error); } } /** * Vérifie si un rappel a déjà été envoyé à un apprenant pour une séquence donnée */ export async function rappelDejaEnvoye( rappelId: number, sequenceId: number, apprenantId: number ): Promise { const db = await getDb(); if (!db) { console.warn("[LogsRappels] Base de données non disponible"); return false; } try { const logs = await db .select() .from(logsRappels) .where( and( eq(logsRappels.rappelId, rappelId), eq(logsRappels.sequenceId, sequenceId), eq(logsRappels.apprenantId, apprenantId), eq(logsRappels.statut, "success") ) ) .limit(1); return logs.length > 0; } catch (error) { console.error("[LogsRappels] Erreur lors de la vérification des doublons:", error); return false; } } /** * Récupère tous les logs de rappels pour une séquence donnée */ export async function getLogsRappelsBySequence(sequenceId: number): Promise { const db = await getDb(); if (!db) return []; try { return await db .select() .from(logsRappels) .where(eq(logsRappels.sequenceId, sequenceId)) .orderBy(logsRappels.dateEnvoi); } catch (error) { console.error("[LogsRappels] Erreur lors de la récupération des logs:", error); return []; } } /** * Récupère tous les logs de rappels échoués récents (dernières 24h) */ export async function getLogsRappelsEchoues(): Promise { const db = await getDb(); if (!db) return []; try { const hier = new Date(); hier.setDate(hier.getDate() - 1); return await db .select() .from(logsRappels) .where( and( eq(logsRappels.statut, "failed"), // Note: Drizzle ne supporte pas directement les comparaisons de dates // On récupère tous les échecs et on filtre en JS ) ) .orderBy(logsRappels.dateEnvoi); } catch (error) { console.error("[LogsRappels] Erreur lors de la récupération des échecs:", error); return []; } } /** * Compte le nombre de rappels envoyés avec succès pour une séquence */ export async function countRappelsEnvoyesSequence(sequenceId: number): Promise { const db = await getDb(); if (!db) return 0; try { const logs = await db .select() .from(logsRappels) .where( and( eq(logsRappels.sequenceId, sequenceId), eq(logsRappels.statut, "success") ) ); return logs.length; } catch (error) { console.error("[LogsRappels] Erreur lors du comptage:", error); return 0; } } /** * Récupère les logs de rappels avec filtres */ export async function getLogsRappelsWithFilters(filters: { dateDebut?: Date; dateFin?: Date; sequenceId?: number; statut?: "success" | "failed"; email?: string; limit?: number; offset?: number; }): Promise { const db = await getDb(); if (!db) return []; try { let query = db.select().from(logsRappels); const conditions = []; if (filters.sequenceId) { conditions.push(eq(logsRappels.sequenceId, filters.sequenceId)); } if (filters.statut) { conditions.push(eq(logsRappels.statut, filters.statut)); } if (filters.email) { // Note: Drizzle ne supporte pas LIKE directement, on filtre en JS } if (conditions.length > 0) { query = query.where(and(...conditions)) as any; } let logs = await query.orderBy(logsRappels.dateEnvoi).limit(filters.limit || 100).offset(filters.offset || 0); // Filtrer par email si nécessaire if (filters.email) { logs = logs.filter(log => log.emailDestinataire.toLowerCase().includes(filters.email!.toLowerCase())); } // Filtrer par date si nécessaire if (filters.dateDebut) { logs = logs.filter(log => new Date(log.dateEnvoi) >= filters.dateDebut!); } if (filters.dateFin) { logs = logs.filter(log => new Date(log.dateEnvoi) <= filters.dateFin!); } return logs; } catch (error) { console.error("[LogsRappels] Erreur lors de la récupération avec filtres:", error); return []; } } /** * Calcule les statistiques des rappels */ export async function getStatsRappels(): Promise<{ totalEnvoyes: number; totalSucces: number; totalEchecs: number; tauxSucces: number; emailsProblematiques: Array<{ email: string; nbEchecs: number }>; }> { const db = await getDb(); if (!db) { return { totalEnvoyes: 0, totalSucces: 0, totalEchecs: 0, tauxSucces: 0, emailsProblematiques: [], }; } try { const logs = await db.select().from(logsRappels); const totalEnvoyes = logs.length; const totalSucces = logs.filter(l => l.statut === "success").length; const totalEchecs = logs.filter(l => l.statut === "failed").length; const tauxSucces = totalEnvoyes > 0 ? (totalSucces / totalEnvoyes) * 100 : 0; // Compter les échecs par email const echecsParEmail = new Map(); logs.filter(l => l.statut === "failed").forEach(log => { const count = echecsParEmail.get(log.emailDestinataire) || 0; echecsParEmail.set(log.emailDestinataire, count + 1); }); const emailsProblematiques = Array.from(echecsParEmail.entries()) .map(([email, nbEchecs]) => ({ email, nbEchecs })) .sort((a, b) => b.nbEchecs - a.nbEchecs) .slice(0, 10); // Top 10 return { totalEnvoyes, totalSucces, totalEchecs, tauxSucces, emailsProblematiques, }; } catch (error) { console.error("[LogsRappels] Erreur lors du calcul des statistiques:", error); return { totalEnvoyes: 0, totalSucces: 0, totalEchecs: 0, tauxSucces: 0, emailsProblematiques: [], }; } } /** * Récupère l'évolution des envois par jour */ export async function getEvolutionEnvois(): Promise> { const db = await getDb(); if (!db) return []; try { const logs = await db.select().from(logsRappels).orderBy(logsRappels.dateEnvoi); // Grouper par jour const parJour = new Map(); logs.forEach(log => { const dateStr = new Date(log.dateEnvoi).toISOString().split('T')[0]; const stats = parJour.get(dateStr) || { nbEnvoyes: 0, nbSucces: 0, nbEchecs: 0 }; stats.nbEnvoyes++; if (log.statut === "success") stats.nbSucces++; else stats.nbEchecs++; parJour.set(dateStr, stats); }); return Array.from(parJour.entries()) .map(([date, stats]) => ({ date, ...stats })) .sort((a, b) => a.date.localeCompare(b.date)); } catch (error) { console.error("[LogsRappels] Erreur lors de la récupération de l'évolution:", error); return []; } } /** * Récupère les logs échoués à réessayer */ export async function getLogsAReessayer(): Promise { const db = await getDb(); if (!db) return []; try { const maintenant = new Date(); const logs = await db .select() .from(logsRappels) .where( and( eq(logsRappels.statut, "failed"), // Limiter à 3 tentatives maximum ) ); // Filtrer en JS pour les conditions complexes return logs.filter(log => { if (log.nbTentatives >= 3) return false; if (!log.prochainEssai) return true; // Premier essai return new Date(log.prochainEssai) <= maintenant; }); } catch (error) { console.error("[LogsRappels] Erreur lors de la récupération des logs à réessayer:", error); return []; } }