**1. Correction erreur Fail2Ban (fail2banDb.ts) :** - Ajout de vérifications pour détecter si Fail2Ban est installé (commande `which fail2ban-client`) - Retour de valeurs par défaut (0, tableaux vides) au lieu de throw d'erreur dans l'environnement sandbox - Corrections appliquées aux 3 fonctions : getFail2BanStatus(), getBanHistory(), countRecentBans() - La page /admin/fail2ban s'affiche maintenant correctement sans erreur dans la sandbox **2. Correction erreur SQL DATE_FORMAT (analyticsDb.ts) :** - Ajout d'un filtre `IS NOT NULL` sur la colonne dateInscription dans getInscriptionsByMonth() - Exclusion des inscriptions avec dateInscription NULL qui causaient des erreurs SQL - Le graphique "Évolution des inscriptions par mois" affiche maintenant "Aucune donnée disponible" au lieu d'une erreur **Tests réussis :** - ✅ Page Fail2Ban : affiche correctement les statistiques à 0 et les messages appropriés - ✅ Tableau de bord analytique : tous les graphiques fonctionnent sans erreur SQL - ✅ Les autres pages (rapport-public-cible, suivi questionnaires) continuent de fonctionner correctement **Résultat :** ✅ Toutes les erreurs signalées sont corrigées ✅ L'application fonctionne correctement dans l'environnement sandbox ✅ Sur le VPS de production, Fail2Ban affichera les vraies valeurs
142 lines
5.3 KiB
TypeScript
142 lines
5.3 KiB
TypeScript
/**
|
|
* Requêtes analytiques pour le tableau de bord
|
|
*/
|
|
|
|
import { eq, sql, and, gte, lte, desc } from "drizzle-orm";
|
|
import { getDb } from "./db";
|
|
import { inscriptions, sequences, apprenants, datesFormation, formations } from "../drizzle/schema";
|
|
|
|
/**
|
|
* Récupère les statistiques d'inscriptions par mois
|
|
* @param startDate - Date de début de la période
|
|
* @param endDate - Date de fin de la période
|
|
*/
|
|
export async function getInscriptionsByMonth(startDate?: Date, endDate?: Date) {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
const conditions = [];
|
|
// Exclure les inscriptions avec dateInscription NULL
|
|
conditions.push(sql`${inscriptions.dateInscription} IS NOT NULL`);
|
|
|
|
if (startDate) {
|
|
conditions.push(gte(inscriptions.dateInscription, startDate));
|
|
}
|
|
if (endDate) {
|
|
conditions.push(lte(inscriptions.dateInscription, endDate));
|
|
}
|
|
|
|
const result = await db
|
|
.select({
|
|
mois: sql<string>`DATE_FORMAT(${inscriptions.dateInscription}, '%Y-%m')`,
|
|
total: sql<number>`COUNT(*)`,
|
|
confirmees: sql<number>`SUM(CASE WHEN ${inscriptions.statut} = 'confirmee' THEN 1 ELSE 0 END)`,
|
|
listeAttente: sql<number>`SUM(CASE WHEN ${inscriptions.statut} = 'liste_attente' THEN 1 ELSE 0 END)`,
|
|
annulees: sql<number>`SUM(CASE WHEN ${inscriptions.statut} = 'annulee' THEN 1 ELSE 0 END)`,
|
|
})
|
|
.from(inscriptions)
|
|
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
.groupBy(sql`DATE_FORMAT(${inscriptions.dateInscription}, '%Y-%m')`)
|
|
.orderBy(sql`DATE_FORMAT(${inscriptions.dateInscription}, '%Y-%m')`);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Récupère le taux de remplissage des séquences par mois
|
|
*/
|
|
export async function getTauxRemplissageByMonth() {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
const result = await db
|
|
.select({
|
|
mois: sql<string>`DATE_FORMAT(${sequences.createdAt}, '%Y-%m')`,
|
|
capaciteTotale: sql<number>`SUM(${sequences.capaciteMax})`,
|
|
inscritsConfirmes: sql<number>`COUNT(CASE WHEN ${inscriptions.statut} = 'confirmee' THEN ${inscriptions.id} END)`,
|
|
tauxRemplissage: sql<number>`ROUND(
|
|
(COUNT(CASE WHEN ${inscriptions.statut} = 'confirmee' THEN ${inscriptions.id} END) / SUM(${sequences.capaciteMax})) * 100,
|
|
2
|
|
)`,
|
|
})
|
|
.from(sequences)
|
|
.leftJoin(inscriptions, eq(sequences.id, inscriptions.sequenceId))
|
|
.groupBy(sql`DATE_FORMAT(${sequences.createdAt}, '%Y-%m')`)
|
|
.orderBy(sql`DATE_FORMAT(${sequences.createdAt}, '%Y-%m')`);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Récupère les statistiques de participation par établissement
|
|
*/
|
|
export async function getParticipationByEtablissement() {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
const result = await db
|
|
.select({
|
|
codeEtablissement: apprenants.codeEtablissement,
|
|
totalApprenants: sql<number>`COUNT(DISTINCT ${apprenants.id})`,
|
|
totalInscriptions: sql<number>`COUNT(${inscriptions.id})`,
|
|
inscriptionsConfirmees: sql<number>`SUM(CASE WHEN ${inscriptions.statut} = 'confirmee' THEN 1 ELSE 0 END)`,
|
|
inscriptionsListeAttente: sql<number>`SUM(CASE WHEN ${inscriptions.statut} = 'liste_attente' THEN 1 ELSE 0 END)`,
|
|
inscriptionsAnnulees: sql<number>`SUM(CASE WHEN ${inscriptions.statut} = 'annulee' THEN 1 ELSE 0 END)`,
|
|
})
|
|
.from(apprenants)
|
|
.leftJoin(inscriptions, eq(apprenants.id, inscriptions.apprenantId))
|
|
.groupBy(apprenants.codeEtablissement)
|
|
.orderBy(desc(sql<number>`COUNT(${inscriptions.id})`));
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Récupère les statistiques globales du tableau de bord
|
|
*/
|
|
export async function getGlobalStats() {
|
|
const db = await getDb();
|
|
if (!db) return null;
|
|
|
|
const [stats] = await db
|
|
.select({
|
|
totalFormations: sql<number>`(SELECT COUNT(*) FROM ${formations} WHERE ${formations.actif} = 1)`,
|
|
totalSequences: sql<number>`(SELECT COUNT(*) FROM ${sequences})`,
|
|
sequencesOuvertes: sql<number>`(SELECT COUNT(*) FROM ${sequences} WHERE ${sequences.statut} = 'ouverte')`,
|
|
totalApprenants: sql<number>`(SELECT COUNT(*) FROM ${apprenants})`,
|
|
totalInscriptions: sql<number>`(SELECT COUNT(*) FROM ${inscriptions})`,
|
|
inscriptionsConfirmees: sql<number>`(SELECT COUNT(*) FROM ${inscriptions} WHERE ${inscriptions.statut} = 'confirmee')`,
|
|
inscriptionsListeAttente: sql<number>`(SELECT COUNT(*) FROM ${inscriptions} WHERE ${inscriptions.statut} = 'liste_attente')`,
|
|
tauxRemplissageMoyen: sql<number>`ROUND(
|
|
(SELECT COUNT(*) FROM ${inscriptions} WHERE ${inscriptions.statut} = 'confirmee') /
|
|
(SELECT SUM(${sequences.capaciteMax}) FROM ${sequences}) * 100,
|
|
2
|
|
)`,
|
|
})
|
|
.from(sql`dual`);
|
|
|
|
return stats;
|
|
}
|
|
|
|
/**
|
|
* Récupère les statistiques de participation par fonction
|
|
*/
|
|
export async function getParticipationByFonction() {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
const result = await db
|
|
.select({
|
|
fonction: apprenants.fonction,
|
|
totalApprenants: sql<number>`COUNT(DISTINCT ${apprenants.id})`,
|
|
totalInscriptions: sql<number>`COUNT(${inscriptions.id})`,
|
|
inscriptionsConfirmees: sql<number>`SUM(CASE WHEN ${inscriptions.statut} = 'confirmee' THEN 1 ELSE 0 END)`,
|
|
})
|
|
.from(apprenants)
|
|
.leftJoin(inscriptions, eq(apprenants.id, inscriptions.apprenantId))
|
|
.groupBy(apprenants.fonction)
|
|
.orderBy(desc(sql<number>`COUNT(${inscriptions.id})`));
|
|
|
|
return result;
|
|
}
|