Analytics de fréquentation (/api/analytics)
backend/src/controllers/analytics.controller.js + analytics.routes.js + table visitor_logs + frontend/src/views/backoffice/AnalyticsView.vue.
Vérifié le 2026-08-24, pour la partie enrichie par le commit "Analytics Big Data metrics, IP Anonymization, SBA mock" (9a31b00) uniquement — getDashboardStats/GET /api/analytics/dashboard (tableau de bord backoffice préexistant) n'a pas été touché par ce commit et n'a pas été audité en détail ici.
Ne pas confondre avec /api/tracking — deux systèmes distincts malgré le vocabulaire proche : tracking sert à un client à retrouver le statut de sa propre demande (prêt/compte) via ticket + téléphone ; analytics enregistre anonymement la fréquentation du site (pages vues, appareil, provenance) à des fins statistiques backoffice. Confirmé par diff : tracking.controller.js/tracking.routes.js ne sont touchés par aucun commit de cette période.
1. Schéma — visitor_logs
model visitor_logs {
id BigInt @id @default(autoincrement())
session_id String? @db.VarChar(100)
ip_address String? @db.VarChar(50)
user_agent String? @db.VarChar(255)
page_visited String @db.VarChar(255)
referrer String? @db.VarChar(500)
screen_resolution String? @db.VarChar(50)
language String? @db.VarChar(10)
visited_at DateTime @default(now())
}
referrer, screen_resolution, language sont les trois colonnes ajoutées par ce commit — le modèle existait déjà avant (session_id, ip_address, user_agent, page_visited).
2. Routes — toutes publiques, aucune protégée
POST /api/analytics/visit (logVisit)
GET /api/analytics/stats (getStats)
GET /api/analytics/dashboard (getDashboardStats — préexistant, hors périmètre de cette page)
Le code lui-même documente l'incertitude : le commentaire au-dessus de GET /stats dit littéralement "Get stats (admin only ideally, but we'll leave it open for now or add auth middleware if exist)". En l'état, n'importe qui peut lire les statistiques de fréquentation (pages les plus vues, répartition par appareil, referrers) sans authentification — moins sensible que les autres endpoits non protégés déjà recensés dans Bugs & failles actives connues (pas de données personnelles ici), mais même famille de défaut : une protection prévue "un jour" mais jamais ajoutée.
3. logVisit — enregistrement d'une visite
Body accepté : { page, session_id, referrer, screen_resolution, language }. Ignore les visites sur /admin, /login, /auth (pas d'intérêt statistique, et évite de tracker le personnel backoffice lui-même).
Anonymisation IP — zérotage du dernier octet, IPv4 seulement
// "Basic IPv4 anonymization"
const ip_address = raw_ip.includes('.')
? raw_ip.substring(0, raw_ip.lastIndexOf('.')) + '.0' // 192.168.1.123 → 192.168.1.0
: raw_ip;
Le code lui-même qualifie ce mécanisme de "Basic IPv4 anonymization". Seules les adresses contenant un . (IPv4, y compris les formes mappées IPv4-in-IPv6) sont anonymisées ; une adresse IPv6 pure est stockée telle quelle, sans troncature ni hachage. Sur un réseau où les clients arrivent en IPv6 natif, visitor_logs.ip_address peut donc contenir une adresse complète et identifiante — à corriger si l'anonymisation IP est une exigence de conformité (RGPD/loi togolaise sur les données personnelles) plutôt qu'un geste partiel.
Champs tronqués avant écriture : session_id (100), ip_address (50), user_agent (255), referrer (500), screen_resolution (50), language (10) — correspond aux tailles VarChar du schéma, évite une erreur Prisma sur une valeur trop longue plutôt que de la gérer explicitement.
4. getStats — agrégations
Réponse : { success, data: { totalVisits, todayVisits, topPages, devices, topReferrers } }. totalVisits/todayVisits/topPages préexistaient ; ce commit ajoute :
devices: regroupe parscreen_resolution, extrait la largeur ("1920x1080".split('x')[0]), classe enOrdinateur(1024px et plus),Tablette(768 à 1023px),Mobile(moins de 768px).topReferrers: regroupe parreferrer, top 5 par nombre de visites.
referrer n'exclut pas les valeurs null, seulement les chaînes videsLe filtre Prisma est écrit referrer: { not: null, not: '' } — un objet JS avec une clé not dupliquée. En JavaScript, la seconde occurrence écrase silencieusement la première : cet objet s'évalue en réalité à { not: '' } seul. Conséquence : les visites sans referrer (null — navigation directe, favori, appli) ne sont pas exclues du classement malgré l'intention affichée dans le code, seules les chaînes vides le sont. Un topReferrers avec beaucoup de trafic direct peut donc afficher une entrée null/vide en tête sans que ce soit un signal utile. À corriger côté code (referrer: { not: null, notIn: [''] } ou équivalent) plutôt que dans la doc.
Consommé par deux nouvelles sections du tableau de bord backoffice (AnalyticsView.vue) : "Répartition par Appareils" et "Sources de trafic (Referrers)".
5. Pièges connus pour un futur développeur
GET /api/analytics/statsest public — le code le signale lui-même comme provisoire ("leave it open for now"). AjouterverifyTokensi ces statistiques ne doivent être visibles que du backoffice.- L'anonymisation IP ne couvre que l'IPv4 — ne pas la présenter comme une anonymisation complète sans corriger le cas IPv6.
- Le nom de commit ("Big Data metrics") survend la portée réelle — il s'agit d'un enrichissement incrémental d'un système de logs de visite déjà existant, pas d'une nouvelle infrastructure Big Data.