Files
geo/app/README-APP.md
Pierre 5e255ebf5e feat: Implémentation authentification NIST SP 800-63B v3.0.8
- Ajout du service PasswordSecurityService conforme NIST SP 800-63B
- Vérification des mots de passe contre la base Have I Been Pwned
- Validation : minimum 8 caractères, maximum 64 caractères
- Pas d'exigences de composition obligatoires (conforme NIST)
- Intégration dans LoginController et UserController
- Génération de mots de passe sécurisés non compromis

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-15 15:31:23 +02:00

50 KiB
Executable File
Raw Blame History

GEOSECTOR v2.0

🚒 Application de gestion des distributions de calendriers par secteurs géographiques pour les amicales de pompiers


🎯 Vue d'ensemble

GEOSECTOR est une solution complète développée en Flutter qui révolutionne la gestion des campagnes de distribution de calendriers pour les amicales de pompiers. L'application combine géolocalisation, gestion multi-rôles et synchronisation en temps réel pour optimiser les tournées et maximiser l'efficacité des équipes.

🏆 Points forts de la v2.0

  • Architecture moderne sans Provider, basée sur l'injection de dépendances
  • Réactivité native avec ValueListenableBuilder et Hive
  • Interface adaptative selon les rôles utilisateur
  • Performance optimisée avec un ApiService singleton
  • Gestion avancée des permissions multi-niveaux
  • Gestion d'erreurs centralisée avec ApiException
  • Interface utilisateur unifiée avec UserFormDialog réutilisable

📋 Table des matières

  1. Fonctionnalités
  2. Architecture technique
  3. Installation
  4. Modèles de données
  5. Architecture des composants
  6. Gestion des rôles
  7. Interface utilisateur
  8. API et synchronisation
  9. Gestion des erreurs
  10. Cartes et géolocalisation

🚀 Fonctionnalités

🎯 Fonctionnalités métier

Pour les Membres (Rôle 1)

  • Visualisation des secteurs assignés sur carte interactive
  • Suivi GPS en temps réel des tournées
  • Enregistrement des passages avec géolocalisation
  • Gestion des stocks de calendriers
  • Historique des distributions
  • Chat intégré avec l'équipe

Pour les Admins Amicale (Rôle 2)

  • Gestion de leur amicale (informations, coordonnées)
  • Gestion des membres de l'amicale (création, modification, suppression)
  • Attribution des rôles aux membres (Membre/Administrateur)
  • Gestion du statut actif des comptes membres
  • Consultation des statistiques de l'amicale
  • Attribution des secteurs aux membres
  • Suivi des performances équipe

Pour les Super Admins (Rôle 3+)

  • Gestion globale multi-amicales
  • Administration des utilisateurs et permissions
  • Configuration des paramètres système
  • Analytics avancées et reporting
  • Gestion des secteurs géographiques

🔧 Fonctionnalités techniques

  • 🗺️ Cartographie avancée : Flutter Map avec tuiles Mapbox
  • 📍 Géolocalisation précise : Suivi GPS des équipes
  • 💾 Stockage hybride : Cache local Hive + synchronisation cloud avec optimisation des performances
  • 💬 Communication : Chat MQTT en temps réel
  • 🔐 Sécurité : Authentification JWT + gestion fine des permissions
  • 📱 Multi-plateforme : iOS, Android, Web
  • 🌐 Mode hors-ligne : Fonctionnement dégradé sans connexion
  • Gestion d'erreurs robuste : Extraction automatique des messages API
  • 🎨 Interface responsive : Adaptation automatique selon la taille d'écran

🏗️ Architecture technique

Stack technologique

Composant Technologie Version Usage
Framework Flutter 3.32+ Interface multi-plateforme
Langage Dart 3.0+ Logique applicative
Navigation GoRouter 12.1.3 Routing déclaratif
Stockage local Hive 2.2.3 Base de données NoSQL locale
Réactivité ValueListenableBuilder Native Écoute des changements Hive
HTTP Dio 5.4.0 Client HTTP avec intercepteurs
Cartes Flutter Map 6.1.0 Rendu cartographique
Géolocalisation Geolocator 10.1.0 Services de localisation
Chat MQTT5 Client 4.2.0 Messagerie temps réel
UI Material Design 3 Native Composants d'interface
Logging LoggerService Custom Logs conditionnels par env

🏛️ Architecture en couches

graph TD
    A[UI Layer - Widgets] --> B[Repository Layer - Business Logic]
    B --> C[Data Layer - Hive + API]

    A1[ValueListenableBuilder] --> A
    A2[Custom Widgets] --> A
    A3[UserFormDialog] --> A

    B1[UserRepository] --> B
    B2[AmicaleRepository] --> B
    B3[MembreRepository] --> B

    C1[Hive Boxes] --> C
    C2[API Service Singleton] --> C
    C3[ApiException Handler] --> C

📁 Structure du projet

app/
├── lib/
│   ├── core/                          # Couche centrale
│   │   ├── constants/                 # Constantes globales
│   │   │   ├── app_keys.dart         # Clés des Box Hive
│   │   │   └── api_endpoints.dart    # Endpoints API
│   │   ├── data/
│   │   │   └── models/               # Modèles Hive
│   │   │       ├── user_model.dart   # @HiveType(typeId: 3)
│   │   │       ├── amicale_model.dart # @HiveType(typeId: 4)
│   │   │       └── membre_model.dart  # @HiveType(typeId: 5)
│   │   ├── repositories/             # Logique métier
│   │   │   ├── user_repository.dart
│   │   │   ├── amicale_repository.dart
│   │   │   └── membre_repository.dart
│   │   ├── services/                 # Services externes
│   │   │   ├── api_service.dart      # HTTP Singleton
│   │   │   ├── chat_service.dart     # MQTT
│   │   │   └── location_service.dart # GPS
│   │   └── utils/                    # Utilitaires
│   │       ├── validators.dart
│   │       └── formatters.dart
│   ├── presentation/                  # Interface utilisateur
│   │   ├── admin/                    # Pages administrateur
│   │   │   ├── admin_dashboard_page.dart
│   │   │   ├── admin_amicale_page.dart
│   │   │   └── admin_statistics_page.dart
│   │   ├── user/                     # Pages utilisateur
│   │   │   ├── user_dashboard_page.dart
│   │   │   ├── map_page.dart
│   │   │   └── distribution_page.dart
│   │   ├── widgets/                  # Composants réutilisables
│   │   │   ├── tables/
│   │   │   │   ├── amicale_table_widget.dart
│   │   │   │   ├── amicale_row_widget.dart
│   │   │   │   ├── membre_table_widget.dart
│   │   │   │   └── membre_row_widget.dart
│   │   │   ├── forms/
│   │   │   │   ├── amicale_form.dart
│   │   │   │   └── custom_text_field.dart
│   │   │   └── common/
│   │   │       ├── dashboard_layout.dart
│   │   │       └── loading_widget.dart
│   │   └── theme/
│   │       └── app_theme.dart
│   ├── app.dart                      # Configuration app
│   └── main.dart                     # Point d'entrée
├── assets/                           # Ressources statiques
│   ├── images/
│   ├── icons/
│   └── fonts/
├── test/                            # Tests unitaires
├── integration_test/                # Tests d'intégration
└── docs/                           # Documentation


🚀 Installation et configuration

Prérequis système

  • Flutter SDK : 3.32 ou supérieur
  • Dart SDK : 3.0 ou supérieur
  • IDE : Android Studio, VS Code, ou IntelliJ
  • Environnement :
    • Android : SDK 21+ (Android 5.0+)
    • iOS : iOS 12.0+
    • Web : Navigateurs modernes

🔐 Configuration des clés API

Mapbox (Cartographie)

  1. Créer un compte sur Mapbox
  2. Générer un token d'accès
  3. Ajouter le token dans .env

Configuration MQTT (Chat)

  1. Configurer votre broker MQTT
  2. Créer les credentials
  3. Tester la connexion

🗄️ Modèles de données

Registres Hive des adaptateurs

// Modèles principaux
UserModelAdapter()           // typeId: 0
OperationModelAdapter()      // typeId: 1
SectorModelAdapter()         // typeId: 3
PassageModelAdapter()        // typeId: 4
MembreModelAdapter()         // typeId: 5
UserSectorModelAdapter()     // typeId: 6
RegionModelAdapter()         // typeId: 7
ClientModelAdapter()         // typeId: 10
AmicaleModelAdapter()        // typeId: 11

// Modèles de chat
ConversationModelAdapter()   // typeId: 20
MessageModelAdapter()        // typeId: 21
ParticipantModelAdapter()    // typeId: 22
AnonymousUserModelAdapter() // typeId: 23
AudienceTargetModelAdapter() // typeId: 24
NotificationSettingsAdapter() // typeId: 25

Clarification importante : UserModel vs MembreModel vs UserSectorModel

⚠️ ATTENTION : Il existe une distinction cruciale entre ces trois modèles :

UserModel (Box: users)

  • Représente uniquement l'utilisateur courant connecté (current user)
  • Stocké dans la box Hive users qui ne contient qu'un seul enregistrement
  • Utilisé pour l'authentification et la session de l'utilisateur actuel
  • Ne pas confondre avec les membres de l'amicale

MembreModel (Box: membres)

  • Représente tous les membres d'une amicale
  • Stocké dans la box Hive membres qui contient plusieurs enregistrements
  • Utilisé pour la gestion des équipes et l'attribution aux secteurs
  • Chaque membre a son propre ID unique

UserSectorModel (Box: user_sector)

  • Représente l'association entre un membre et un secteur
  • ⚠️ IMPORTANT : Le champ id dans UserSectorModel correspond à l'ID du membre (MembreModel.id), PAS à l'ID de l'utilisateur (UserModel.id)
  • Permet de savoir quels membres sont affectés à quels secteurs
  • Nom trompeur : devrait s'appeler "MemberSectorModel" pour éviter la confusion

Compatibilité entre modèles

  • UserModel ↔ MembreModel : Conversion bidirectionnelle via toUserModel() et fromUserModel()
  • Synchronisation : Maintien de la cohérence entre les deux représentations
  • Champs spécialisés : Préservation des données spécifiques à chaque modèle 🎨 Interface utilisateur Architecture des composants UserFormDialog - Modale unifiée Réutilisabilité : Même widget pour "Mon Compte" et "Gestion des Membres" Personnalisation contextuelle : Sélecteur de rôle (Membre/Administrateur) Checkbox statut actif/inactif Édition du nom d'utilisateur selon le contexte Gestion du nom de tournée (sectName) Interface responsive : Adaptation automatique selon la largeur d'écran UserForm - Formulaire intelligent Layout adaptatif :

900px : Champs groupés en lignes (username+email, prénom+nom, téléphones, dates) ≤ 900px : Champs empilés verticalement Validation conditionnelle : Au moins nom OU nom de tournée requis Champs dynamiques : Affichage selon les permissions et le contexte Indicateurs visuels : Points rouges sur les champs obligatoires Tableaux interactifs AmicaleTableWidget : Gestion des amicales avec édition inline MembreTableWidget : Gestion des membres avec actions contextuelles Alternance de couleurs : Amélioration de la lisibilité Clic sur ligne : Ouverture directe du formulaire d'édition

🔗 API et synchronisation

Principe "API First"

Flow de mise à jour

Validation API : Tentative de mise à jour sur le serveur Succès → Sauvegarde locale avec isSynced: true Erreur → Aucune modification locale + affichage de l'erreur

Avantages

Cohérence des données : Local toujours synchronisé avec le serveur Gestion d'erreurs propre : Pas de conflits entre données locales et distantes UX claire : Feedback immédiat sur les erreurs de validation

ApiService Singleton

  • Thread-safe : Initialisation sécurisée avec verrous
  • Auto-configuration : Détection automatique de l'environnement (DEV/REC/PROD)
  • Gestion de session : Headers d'authentification automatiques
  • Retry logic : Nouvelles tentatives pour les erreurs réseau

⚠️ Gestion des erreurs

🎯 Système ApiException intelligent

GEOSECTOR v2.0 utilise un système centralisé de gestion des messages qui s'adapte automatiquement au contexte d'affichage pour garantir une visibilité optimale des notifications utilisateur.

🧠 Détection automatique de contexte

L'ApiException détecte intelligemment si elle est appelée depuis une Dialog et adapte l'affichage :

  • 📱 Contexte normal : SnackBar standard en bas d'écran
  • 💬 Contexte Dialog : Overlay SnackBar positionné au-dessus de la Dialog estompée
  • 🌐 Mobile/Web : Adaptation automatique selon la plateforme
  • 🎨 Cohérence visuelle : Couleurs, icônes et comportements unifiés
// Même API partout - détection intelligente du contexte
void _handleValidation() {
  if (formInvalid) {
    // Dans une Dialog : overlay au-dessus, Dialog reste ouverte
    ApiException.showError(context, Exception("Champs requis manquants"));
    return;
  }
  
  // Succès : fermer Dialog puis afficher confirmation
  Navigator.pop(context);
  ApiException.showSuccess(context, "Données sauvegardées");
}

Avantages de l'approche unifiée

Aspect Avant Avec ApiException
Visibilité SnackBar masqué par Dialog Overlay visible au-dessus
Consistance Messages dispersés API unifiée dans toute l'app
Maintenance Code répétitif Système centralisé
UX Frustrant (messages cachés) Fluide et prévisible

Architecture centralisée

sequenceDiagram
    participant UI as dashboard_app_bar.dart
    participant UR as user_repository.dart
    participant AS as api_service.dart
    participant API as API Server
    participant AE as ApiException
    participant EU as ErrorUtils

    Note over UI: Utilisateur clique "Enregistrer"
    UI->>UR: updateUser(updatedUser)

    Note over UR: Tente la mise à jour
    UR->>AS: updateUser(user)

    Note over AS: Appel HTTP PUT
    AS->>API: PUT /users/123 {email: "test@test.com"}

    alt Succès API
        API-->>AS: 200 OK {user data}
        AS-->>UR: UserModel (mis à jour)
        UR-->>UI: UserModel (succès)
        UI->>EU: showSuccessSnackBar()
        Note over UI: ✅ "Profil mis à jour"

    else Erreur API (ex: email déjà utilisé)
        API-->>AS: 409 Conflict {"message": "Cet email est déjà utilisé"}

        Note over AS: Conversion en ApiException
        AS->>AE: ApiException.fromDioException(dioError)
        AE-->>AS: ApiException("Cet email est déjà utilisé")
        AS-->>UR: throw ApiException
        UR-->>UI: throw ApiException

        Note over UI: Gestion de l'erreur
        UI->>EU: showErrorSnackBar(context, exception)
        EU->>AE: extractErrorMessage(exception)
        AE-->>EU: "Cet email est déjà utilisé"
        Note over UI: ❌ "Erreur: Cet email est déjà utilisé"
        Note over UI: Dialog reste ouvert

    else Erreur réseau
        API-->>AS: Network Error / Timeout
        AS->>AE: ApiException.fromDioException(networkError)
        AE-->>AS: ApiException("Problème de connexion réseau")
        AS-->>UR: throw ApiException
        UR-->>UI: throw ApiException
        UI->>EU: showErrorSnackBar(context, exception)
        Note over UI: ❌ "Problème de connexion réseau"
    end

Composants de gestion d'erreurs

ApiException

Extraction intelligente : Messages spécifiques depuis la réponse API Codes HTTP standardisés : Mapping automatique des erreurs communes Types d'erreurs : Classification (validation, authentification, réseau, conflit) Méthodes d'affichage : showError() et showSuccess() intégrées

Responsabilités par couche

ApiService : Conversion des erreurs Dio en ApiException Repository : Propagation transparente des erreurs Interface : Affichage utilisateur via ApiException.showError()

Messages d'erreurs contextuels

409 Conflict : "Cet email est déjà utilisé par un autre utilisateur" 400 Bad Request : "Données invalides" 401 Unauthorized : "Non autorisé : veuillez vous reconnecter" 500 Server Error : "Erreur serveur interne" Network Errors : "Problème de connexion réseau" Timeout : "Délai d'attente dépassé"

🔧 Pattern de gestion des erreurs API dans les Repositories

🎯 Problème à résoudre

Les messages d'erreur spécifiques de l'API (comme "Cet email est déjà utilisé") n'étaient pas affichés à l'utilisateur. À la place, un message générique "Erreur inattendue" apparaissait.

📝 Modifications requises

1. ApiService - Conversion automatique des DioException

Toutes les méthodes HTTP génériques doivent convertir les DioException en ApiException :

// ✅ CORRECT - ApiService avec conversion automatique
Future<Response> put(String path, {dynamic data}) async {
  try {
    return await _dio.put(path, data: data);
  } on DioException catch (e) {
    throw ApiException.fromDioException(e); // ← Extraction automatique du message
  } catch (e) {
    if (e is ApiException) rethrow;
    throw ApiException('Erreur inattendue lors de la requête PUT', originalError: e);
  }
}

Appliquer le même pattern pour post(), get(), delete().

2. Repository - Simplification de la gestion des erreurs

// ❌ INCORRECT - Code inutile qui ne sera jamais exécuté
Future<bool> updateMembre(MembreModel membre) async {
  try {
    final response = await ApiService.instance.put('/users/${membre.id}', data: data);
    
    if (response.statusCode == 200) {
      await saveMembreBox(membre);
      return true;
    }
    
    // ⚠️ CE CODE NE SERA JAMAIS ATTEINT car Dio lance une exception pour les codes d'erreur
    if (response.data != null && response.data is Map<String, dynamic>) {
      final responseData = response.data as Map<String, dynamic>;
      if (responseData['status'] == 'error') {
        throw Exception(responseData['message']);
      }
    }
    return false;
  } catch (e) {
    rethrow;
  }
}
// ✅ CORRECT - Code simplifié et fonctionnel
Future<bool> updateMembre(MembreModel membre) async {
  try {
    final response = await ApiService.instance.put('/users/${membre.id}', data: data);
    
    // Si on arrive ici, c'est que la requête a réussi (200)
    await saveMembreBox(membre);
    return true;
    
  } catch (e) {
    // L'ApiException contient déjà le message extrait de l'API
    rethrow; // Propager l'exception pour affichage
  }
}

3. Amélioration des logs (avec LoggerService)

// ✅ CORRECT - Logs propres sans détails techniques
catch (e) {
  // Ne pas logger les détails techniques de DioException
  if (e is ApiException) {
    LoggerService.error('Erreur lors de la mise à jour: ${e.message}');
  } else {
    LoggerService.error('Erreur lors de la mise à jour');
  }
  rethrow;
}

N'oubliez pas d'importer ApiException :

import 'package:geosector_app/core/utils/api_exception.dart';

🔄 Flux d'erreur corrigé

sequenceDiagram
    participant API as API Server
    participant Dio as Dio Client  
    participant AS as ApiService
    participant AE as ApiException
    participant R as Repository
    participant UI as Interface

    UI->>R: updateData()
    R->>AS: put('/endpoint')
    AS->>Dio: HTTP PUT
    Dio->>API: Request
    
    alt Code d'erreur (409, 400, etc.)
        API-->>Dio: Error + JSON body
        Note over API: {"status": "error",<br/>"message": "Message spécifique"}
        
        Dio-->>AS: DioException
        AS->>AE: fromDioException()
        Note over AE: Extrait message<br/>depuis response.data
        AE-->>AS: ApiException("Message spécifique")
        AS-->>R: throw ApiException
        R-->>UI: throw ApiException
        UI->>UI: showError("Message spécifique")
    else Succès (200, 201)
        API-->>Dio: Success
        Dio-->>AS: Response
        AS-->>R: Response
        R->>R: Sauvegarde Hive
        R-->>UI: return true
    end

Checklist de migration

Pour chaque repository :

  • Vérifier que l'ApiService convertit les DioException en ApiException
  • Simplifier le code : supprimer les vérifications de statut après l'appel API
  • Propager les exceptions avec rethrow
  • Améliorer les logs pour ne pas afficher les détails techniques
  • Importer ApiException si nécessaire
  • Tester avec une erreur 409 pour vérifier l'affichage du message

📊 Résultat attendu

Avant Après
"Erreur inattendue" "Cet email est déjà utilisé par un autre utilisateur"
Logs avec stack trace Dio Message d'erreur simple et clair
Code complexe avec vérifications inutiles Code simplifié et maintenable

Cette approche garantit que tous les messages d'erreur de l'API sont correctement affichés à l'utilisateur, améliorant ainsi l'expérience utilisateur et facilitant le débogage.

📝 Service de Logging Intelligent

🎯 Vue d'ensemble

GEOSECTOR v2.0 implémente un LoggerService centralisé qui désactive automatiquement les logs de debug en production, optimisant ainsi les performances et la sécurité tout en facilitant le développement.

🔍 Détection automatique de l'environnement

Le LoggerService détecte automatiquement l'environnement d'exécution :

// Détection basée sur l'URL pour le web
if (currentUrl.contains('dapp.geosector.fr'))  DEV
if (currentUrl.contains('rapp.geosector.fr'))  REC
Sinon  PROD

// Pour mobile/desktop : utilise kReleaseMode de Flutter

Comportement par environnement :

Environnement Logs Debug Logs Erreur Stack Traces
DEV Activés Activés Complètes
REC Activés Activés Complètes
PROD Désactivés Activés Masquées

🛠️ API du LoggerService

Méthodes principales

// Remplacement direct de debugPrint
LoggerService.log('Message simple');

// Logs catégorisés avec emojis automatiques
LoggerService.info('Information');           // 
LoggerService.success('Opération réussie');  // ✅
LoggerService.warning('Attention');          // ⚠️
LoggerService.error('Erreur', exception);    // ❌ (toujours affiché)
LoggerService.debug('Debug', emoji: '🔧');   // 🔧

// Logs spécialisés
LoggerService.api('Requête API envoyée');    // 🔗
LoggerService.database('Box Hive ouverte');  // 💾
LoggerService.navigation('Route: /admin');   // 🧭
LoggerService.performance('Temps: 45ms');    // ⏱️

Fonctionnalités avancées

// Logs groupés pour améliorer la lisibilité
LoggerService.group('Traitement utilisateur', [
  'Validation des données',
  'Appel API',
  'Sauvegarde locale',
  'Notification envoyée'
]);
// Affiche :
// ┌─ Traitement utilisateur
// ├─ Validation des données
// ├─ Appel API
// ├─ Sauvegarde locale
// └─ Notification envoyée

// Log JSON formaté
LoggerService.json('Payload API', {
  'id': 123,
  'name': 'Test',
  'active': true
});
// Affiche :
// 📋 Payload API:
//    id: 123
//    name: Test
//    active: true

// Log conditionnel
LoggerService.conditional(
  'Debug spécifique',
  condition: user.role > 2
);

🔄 Migration depuis debugPrint

Avant (debugPrint partout)

debugPrint('🔄 Début de la création d\'un nouveau membre');
debugPrint('📤 Données envoyées à l\'API: $data');
debugPrint('❌ Erreur: $e');

Après (LoggerService)

LoggerService.info('Début de la création d\'un nouveau membre');
LoggerService.api('Données envoyées à l\'API: $data');
LoggerService.error('Erreur lors de la création', e);

📋 Utilisation avec les extensions

Pour une syntaxe encore plus concise, utilisez les extensions String :

// Import nécessaire
import 'package:geosector_app/core/services/logger_service.dart';

// Utilisation directe sur les strings
'Connexion réussie'.logSuccess();
'Erreur de validation'.logError(exception);
'Requête GET /users'.logApi();
'Box membres ouverte'.logDatabase();

🎯 Bonnes pratiques

1. Catégorisation des logs

// ✅ BON - Log catégorisé et clair
LoggerService.api('POST /users - Création membre');
LoggerService.database('Sauvegarde dans Box membres');

// ❌ MAUVAIS - Log générique sans contexte
debugPrint('Traitement en cours...');

2. Gestion des erreurs

try {
  await operation();
  LoggerService.success('Opération terminée');
} catch (e, stackTrace) {
  // Les erreurs sont TOUJOURS loggées, même en PROD
  LoggerService.error('Échec de l\'opération', e, stackTrace);
}

3. Logs de performance

final stopwatch = Stopwatch()..start();
await heavyOperation();
stopwatch.stop();
LoggerService.performance('Opération lourde: ${stopwatch.elapsedMilliseconds}ms');

🔒 Sécurité et performances

Avantages en production

  1. Sécurité : Aucune information sensible exposée dans la console
  2. Performance : Pas d'appels inutiles à debugPrint
  3. Taille : Bundle JavaScript plus léger (tree shaking)
  4. Professionnalisme : Console propre pour les utilisateurs finaux

Conservation des logs d'erreur

Les erreurs restent visibles en production pour faciliter le support :

// Toujours affiché, même en PROD
LoggerService.error('Erreur critique détectée', error);
// Mais sans la stack trace complète qui pourrait révéler
// des détails d'implémentation

📊 Impact sur le codebase

Métrique Avant Après
Logs en PROD Tous visibles Erreurs uniquement
Performance web debugPrint actifs Désactivés automatiquement
Maintenance debugPrint dispersés Service centralisé
Lisibilité Emojis manuels Catégorisation automatique

🚀 Configuration et initialisation

Le LoggerService fonctionne automatiquement sans configuration :

// main.dart - Aucune initialisation requise
void main() async {
  // LoggerService détecte automatiquement l'environnement
  // via ApiService.getCurrentEnvironment()
  
  runApp(GeosectorApp());
}

// Utilisation immédiate dans n'importe quel fichier
LoggerService.info('Application démarrée');

Cette architecture garantit un système de logging professionnel, sécurisé et performant, adapté aux besoins de développement tout en protégeant la production. 📝

🔐 Gestion des identifiants - Normes NIST SP 800-63B

🎯 Vue d'ensemble

GEOSECTOR v2.0 implémente les normes NIST SP 800-63B pour la gestion des identifiants (usernames et passwords), offrant une sécurité renforcée tout en améliorant l'expérience utilisateur avec des règles plus flexibles et modernes.

📋 Conformité NIST SP 800-63B

Exigence NIST Implémentation Statut
Longueur minimale : 8 caractères MIN = 8 caractères CONFORME
Longueur maximale : 64 caractères minimum MAX = 64 caractères CONFORME
Accepter TOUS les caractères ASCII imprimables Aucune restriction sur les caractères CONFORME
Accepter les espaces Espaces acceptés (début, milieu, fin) CONFORME
Accepter Unicode (émojis, accents, etc.) Support UTF-8 complet CONFORME
Vérifier contre les mots de passe compromis API Have I Been Pwned avec k-anonymity CONFORME
Pas d'obligation de composition Pas d'erreur si manque majuscules/chiffres/spéciaux CONFORME
Pas de changement périodique forcé Aucune expiration automatique CONFORME
Permettre les phrases de passe "Mon chat Félix a 3 ans!" accepté CONFORME

🔑 Règles pour les identifiants

Username (Nom d'utilisateur)

  • Longueur : 8 à 64 caractères
  • Caractères acceptés : TOUS (lettres, chiffres, espaces, accents, symboles, emojis)
  • Exemples valides :
    • jean dupont 75
    • Marie-Claire.2024
    • Pompier Paris 🚒
    • utilisateur@amicale

Password (Mot de passe)

  • Longueur : 8 à 64 caractères
  • Aucune règle de composition obligatoire (plus besoin de majuscules/minuscules/chiffres/spéciaux)
  • Phrases de passe recommandées pour une meilleure mémorisation
  • Exemples valides :
    • Mon chat Félix a 3 ans!
    • J'aime les pizzas du vendredi soir
    • Le camion rouge part à 8h30
    • ☀️ Soleil brillant sur Paris ☀️

🎲 Générateurs intelligents

Générateur de username

Crée des noms d'utilisateur uniques basés sur :

  • Nom/prénom de la personne
  • Code postal et ville de l'amicale
  • Numéro aléatoire pour l'unicité
  • Peut inclure des espaces et séparateurs (., -, _)

Générateur de phrases de passe

Génère des phrases de passe mémorables en français :

  • Phrases naturelles et faciles à retenir
  • Combinaisons variées (sujets, verbes, compléments)
  • Ajout optionnel de caractères spéciaux ou emojis
  • Exemples générés :
    • Le chien Max danse dans le jardin!
    • Mon vélo rouge vole 42 fois en été
    • Luna a 7 ans!☀️

⚠️ Points importants

  1. Pas de trim() : Les espaces en début/fin sont préservés et font partie de l'identifiant
  2. Pas de vérification password == username : Sur demande du client, cette règle a été retirée
  3. Validation côté API : L'API vérifie les mots de passe contre la base Have I Been Pwned
  4. Rétrocompatibilité : Les anciens identifiants restent valides

🔄 Impact sur l'expérience utilisateur

Aspect Avant Après NIST
Complexité Règles strictes difficiles à mémoriser Liberté totale, phrases naturelles
Longueur password 12-16 caractères obligatoires 8-64 caractères flexibles
Caractères spéciaux Obligatoires Optionnels
Mémorisation Mots de passe complexes oubliés Phrases personnelles mémorables
Sécurité Règles arbitraires Vérification contre bases de données compromises

🎯 Gestion des rôles

Hiérarchie des permissions

Membre (Rôle 1) : Consultation et distribution dans ses secteurs Admin Amicale (Rôle 2) : Gestion complète de son amicale et ses membres Super Admin (Rôle 3+) : Administration globale multi-amicales

Fonctionnalités par rôle

Admin Amicale - Gestion des membres Création : Nouveaux membres avec attribution de rôle Modification : Informations personnelles, rôle, statut actif Suppression : Avec confirmation obligatoire Validation : Contrôle d'unicité email/username par l'API

Interface adaptative

Sélecteur de rôle : Visible uniquement pour les admins Checkbox statut actif : Contrôle d'accès aux comptes Édition contextuelle : Champs modifiables selon les permissions Actions conditionnelles : Boutons disponibles selon le niveau d'autorisation

👥 Gestion des membres (Admin Amicale)

🎯 Vue d'ensemble

La gestion des membres est une fonctionnalité clé pour les Admins Amicale (Rôle 2) qui permet une administration complète des équipes au sein de leur amicale. Cette interface centralise toutes les opérations liées aux membres avec une approche sécurisée et intuitive.

📱 Interface AdminAmicalePage

L'interface principale admin_amicale_page.dart offre une vue d'ensemble complète :

  • Informations de l'amicale : Affichage des détails de l'amicale courante
  • Liste des membres : Tableau interactif avec actions contextuelles
  • Ajout de membres : Bouton d'action pour créer de nouveaux comptes
  • Opération courante : Indication de l'opération active en cours

Fonctionnalités principales

🆕 Création de nouveaux membres

// Workflow de création
1. Clic sur "Ajouter un membre"
2. Ouverture du UserFormDialog
3. Formulaire vierge avec valeurs par défaut
4. Sélection du rôle (Membre/Administrateur)
5. Configuration du statut actif
6. Validation et création via API
7. Attribution automatique à l'amicale courante

Champs obligatoires :

  • Email (unique dans le système)
  • Au moins un nom (nom de famille OU nom de tournée)
  • Rôle dans l'amicale

Champs optionnels :

  • Nom d'utilisateur (éditable pour les admins)
  • Prénom, téléphones, dates
  • Nom de tournée (pour identification terrain)

✏️ Modification des membres existants

// Actions disponibles
- Clic sur une ligne  Ouverture du formulaire d'édition
- Modification de tous les champs personnels
- Changement de rôle (Membre  Administrateur)
- Activation/Désactivation du compte
- Gestion du nom de tournée

Workflow de modification :

  1. Sélection du membre dans le tableau
  2. Ouverture automatique du UserFormDialog
  3. Formulaire pré-rempli avec données existantes
  4. Modification des champs souhaités
  5. Validation et mise à jour via API
  6. Synchronisation automatique avec Hive

🗑️ Suppression intelligente des membres

La suppression des membres intègre une logique métier avancée pour préserver l'intégrité des données :

🔍 Vérification des passages
// Algorithme de vérification
1. Récupération de opération courante
2. Analyse des passages du membre pour cette opération
3. Classification : passages réalisés vs passages à finaliser
4. Comptage total des passages actifs
📊 Scénarios de suppression

Cas 1 : Aucun passage (suppression simple)

DELETE /users/{id}
  • Confirmation simple
  • Suppression directe
  • Aucun transfert nécessaire

Cas 2 : Passages existants (suppression avec transfert)

DELETE /users/{id}?transfer_to={destinataire}&operation_id={operation}
  • Dialog d'avertissement avec détails
  • Sélection obligatoire d'un membre destinataire
  • Transfert automatique de tous les passages
  • Préservation de l'historique

Cas 3 : Alternative recommandée (désactivation)

// Mise à jour du statut
membre.copyWith(isActive: false)
  • Préservation complète des données
  • Blocage de la connexion
  • Maintien de l'historique
  • Réactivation possible ultérieurement

🔐 Sécurité et permissions

Contrôles d'accès

  • Isolation par amicale : Admins limités à leur amicale
  • Vérification des rôles : Validation côté client et serveur
  • Opération courante : Filtrage par contexte d'opération
  • Validation API : Contrôles d'unicité et cohérence

Gestion des erreurs

graph TD
    A[Action utilisateur] --> B[Validation locale]
    B --> C[Appel API]
    C --> D{Succès ?}
    D -->|Oui| E[Mise à jour Hive]
    D -->|Non| F[Affichage erreur]
    E --> G[Notification succès]
    F --> H[Dialog reste ouvert]

🎨 Interface utilisateur

Tableaux interactifs

MembreTableWidget - Composant principal :

  • Colonnes : ID, Prénom, Nom, Email, Rôle, Statut
  • Actions : Modification, Suppression
  • Alternance de couleurs pour lisibilité
  • Tri et navigation intuitifs

MembreRowWidget - Ligne individuelle :

  • Clic pour édition rapide
  • Boutons d'action contextuels
  • Indicateurs visuels de statut
  • Tooltip informatifs

Formulaires adaptatifs

UserFormDialog - Modale réutilisable :

  • Layout responsive (>900px vs mobile)
  • Validation en temps réel
  • Gestion des erreurs inline
  • Sauvegarde avec feedback

📡 Synchronisation et réactivité

Architecture ValueListenableBuilder

// Écoute automatique des changements
ValueListenableBuilder<Box<MembreModel>>(
  valueListenable: membreRepository.getMembresBox().listenable(),
  builder: (context, membresBox, child) {
    // Interface mise à jour automatiquement
    final membres = membresBox.values
        .where((m) => m.fkEntite == currentUser.fkEntite)
        .toList();

    return MembreTableWidget(membres: membres);
  },
)

Principe "API First"

  1. Validation API : Tentative sur serveur en priorité
  2. Succès → Sauvegarde locale + mise à jour interface
  3. Erreur → Affichage message + maintien état local
  4. Cohérence : Données locales toujours synchronisées

🔄 Workflow complet

sequenceDiagram
    participant A as Admin
    participant UI as Interface
    participant R as Repository
    participant API as Serveur
    participant H as Hive

    A->>UI: Action membre
    UI->>R: Appel repository
    R->>API: Requête HTTP
    API-->>R: Réponse

    alt Succès
        R->>H: Sauvegarde locale
        H-->>UI: Notification changement
        UI-->>A: Interface mise à jour
    else Erreur
        R-->>UI: Exception
        UI-->>A: Message d'erreur
    end

🎯 Bonnes pratiques

Pour les administrateurs

  1. Vérification avant suppression : Toujours examiner les passages
  2. Préférer la désactivation : Éviter la perte de données
  3. Noms de tournée : Utiliser des identifiants terrain clairs
  4. Rôles appropriés : Limiter les administrateurs aux besoins

Gestion des erreurs courantes

Erreur Cause Solution
Email déjà utilisé Duplication Choisir un autre email
Membre avec passages Données liées Transférer ou désactiver
Aucune opération active Configuration Vérifier les opérations
Accès refusé Permissions Vérifier le rôle admin

Cette architecture garantit une gestion des membres robuste, sécurisée et intuitive, optimisant l'efficacité administrative tout en préservant l'intégrité des données opérationnelles. 👥

🗺️ Cartes et géolocalisation

Flutter Map : Rendu cartographique haute performance Tuiles Mapbox : Cartographie détaillée et personnalisable Géolocalisation temps réel : Suivi GPS des équipes Secteurs géographiques : Visualisation et attribution dynamique Passages géolocalisés : Enregistrement précis des distributions

🔄 Synchronisation et réactivité

Hive + ValueListenableBuilder

Réactivité native : Mise à jour automatique de l'interface Performance optimisée : Pas de Provider, injection directe Écoute sélective : Réactivité fine par Box Hive Cohérence des données : Synchronisation bidirectionnelle User/Membre

Services Singleton

CurrentUserService : Gestion de l'utilisateur connecté CurrentAmicaleService : Amicale de l'utilisateur actuel ApiService : Communication centralisée avec l'API DataLoadingService : Orchestration du chargement des données

🚀 Optimisation des performances Hive

📈 Gestion des Box Hive avec cache

GEOSECTOR v2.0 implémente une stratégie de cache avancée pour les Box Hive afin d'éliminer les goulots d'étranglement de performance lors d'opérations haute fréquence.

🎯 Problème résolu

Avant l'optimisation, l'application effectuait jusqu'à 848 vérifications Hive.isBoxOpen() par chargement de page, causant des ralentissements significatifs lors du rendu des listes et du filtrage des données.

💡 Solution : Cache lazy des Box

// Pattern implémenté dans tous les repositories
class OptimizedRepository {
  Box<ModelType>? _cachedBox;
  
  Box<ModelType> get _modelBox {
    if (_cachedBox == null) {
      if (!Hive.isBoxOpen(AppKeys.boxName)) {
        throw Exception('Box non ouverte');
      }
      _cachedBox = Hive.box<ModelType>(AppKeys.boxName);
      debugPrint('Repository: Box mise en cache');
    }
    return _cachedBox!;
  }

  void _resetCache() {
    _cachedBox = null;
  }
}

🔄 Gestion du cache et réactivité

Point critique : Le cache doit être réinitialisé après toute modification pour garantir le bon fonctionnement de ValueListenableBuilder :

// ✅ OBLIGATOIRE après modification
Future<void> saveData(ModelType data) async {
  await _modelBox.put(data.id, data);
  _resetCache(); // ← Crucial pour la réactivité UI
  notifyListeners();
}

// ✅ OBLIGATOIRE après suppression
Future<void> deleteData(int id) async {
  await _modelBox.delete(id);
  _resetCache(); // ← Crucial pour la réactivité UI
  notifyListeners();
}

// ✅ OBLIGATOIRE après vidage ou traitement API
Future<void> processApiData(List<dynamic> data) async {
  await _modelBox.clear();
  // ... traitement des données
  _resetCache(); // ← Crucial pour la réactivité UI
  notifyListeners();
}

📊 Impact performance

Métrique Avant optimisation Après optimisation
Vérifications box 848 par page 1 par session
Temps de rendu 200-500ms <50ms
Filtrage liste Lent (check répétés) Instantané
Mémoire Overhead minimal Impact négligeable

🏗️ Repositories optimisés

L'optimisation est implémentée dans tous les repositories critiques :

  • SectorRepository : Gestion des secteurs géographiques
  • PassageRepository : Suivi des distributions
  • MembreRepository : Gestion des équipes
  • OperationRepository : Campagnes et opérations
  • AmicaleRepository : Organisations

🎯 Règles d'implémentation

  1. Cache systématique : Tous les repositories fréquemment utilisés
  2. Reset obligatoire : Après toute opération de modification
  3. Getter lazy : Accès différé à la box uniquement si nécessaire
  4. Debug logging : Traçabilité du cache en développement
  5. Cohérence : Pattern appliqué uniformément

Cette architecture garantit une application performante, maintenable et évolutive avec une excellente expérience utilisateur. 🚀

🎨 Architecture des Dialogs Auto-Gérées

🎯 Principe de conception

GEOSECTOR v2.0 implémente une architecture simplifiée des dialogs qui élimine la complexité des callbacks asynchrones et garantit une gestion robuste des formulaires modaux.

🏗️ Pattern "Dialog Auto-Gérée"

graph TD
    A[Page Parente] -->|Injection Repository| B[Dialog Auto-Gérée]
    B -->|Appel Direct| C[Repository]
    C -->|API Call| D[Serveur]
    D -->|Réponse| C
    C -->|Sauvegarde| E[Hive Local]
    B -->|Auto-Fermeture| A
    B -->|Callback Simple| A
    E -->|ValueListenableBuilder| A

Composants de l'architecture

1. Page Parente (ex: AdminOperationsPage)

// Ouverture simplifiée de dialog
void _showEditOperationDialog(OperationModel op) {
  showDialog(
    context: context,
    builder: (dialogContext) => OperationFormDialog(
      operation: op,
      operationRepository: widget.operationRepository, // ← Injection directe
      onSuccess: () {
        if (mounted) setState(() {}); // ← Simple rafraîchissement
      },
    ),
  );
}

2. Dialog Auto-Gérée (ex: OperationFormDialog)

// Gestion complète dans la dialog
void _handleSubmit() async {
  try {
    // Appel direct du repository
    final success = await widget.operationRepository.saveOperationFromModel(operationData);

    if (success && mounted) {
      // Délai pour synchronisation Hive
      Future.delayed(const Duration(milliseconds: 200), () {
        Navigator.of(context).pop(); // ← Auto-fermeture
        widget.onSuccess?.call(); // ← Notification simple
        ApiException.showSuccess(context, "Opération sauvegardée");
      });
    }
  } catch (e) {
    ApiException.showError(context, e); // ← Erreur sans fermeture
  }
}

🎯 Avantages de cette approche

Aspect Avant (Complexe) Après (Simplifié)
Callbacks Asynchrones complexes Simple onSuccess: () => setState()
Fermeture Gérée par le parent Auto-fermeture dans la dialog
Gestion d'erreurs Dispersée Centralisée dans la dialog
Synchronisation Problématique Délai de 200ms pour Hive
Maintenance Code dispersé Logique unifiée

🔧 Responsabilités claires

Page Parente

  • Ouverture des dialogs avec injection de dépendances
  • Rafraîchissement de l'interface via setState()
  • Gestion des tableaux et listes intégrés

Dialog Auto-Gérée

  • Validation et soumission du formulaire
  • Appel direct des repositories
  • Auto-fermeture en cas de succès
  • Gestion des erreurs sans fermeture

Repository

  • Logique métier (création vs modification)
  • Appels API appropriés
  • Sauvegarde locale dans Hive
  • Propagation des exceptions

🚀 Exemple d'implémentation

// 1. Page parente - Code minimal
void _showCreateDialog() {
  showDialog(
    context: context,
    builder: (context) => OperationFormDialog(
      title: 'Créer une opération',
      operationRepository: widget.operationRepository,
      onSuccess: () => setState(() {}),
    ),
  );
}

// 2. Dialog - Auto-gestion complète
class OperationFormDialog extends StatefulWidget {
  final OperationRepository operationRepository;
  final VoidCallback? onSuccess;

  // Gestion interne : validation, API, fermeture, erreurs
}

// 3. Repository - Logique métier pure
Future<bool> saveOperationFromModel(OperationModel operation) async {
  if (operation.id == 0) {
    return await createOperation(...); // POST
  } else {
    return await updateOperation(...); // PUT
  }
}

Résultats obtenus

  • 🎯 Simplicité : Code plus lisible et maintenable
  • 🔒 Robustesse : Gestion d'erreurs centralisée
  • Performance : Synchronisation optimisée avec Hive
  • 🎨 UX : Fermeture automatique et messages appropriés
  • 🔧 Maintenance : Architecture cohérente et prévisible

Cette approche "Dialog Auto-Gérée" constitue un pattern architectural clé de GEOSECTOR v2.0, garantissant une expérience utilisateur fluide et un code maintenable. 🎉

Fonction de création d'une opération

Fonction de création d'une opération

🔄 Traitement des données complexes

Lors de la création d'une opération via OperationRepository.createOperation(), l'API retourne en réponse 201 Created ou 200 OK un payload complexe contenant 4 groupes de données essentiels :

{
  "operations": [...],    // 3 dernières opérations dont la nouvelle active
  "secteurs": [...],      // Secteurs de la nouvelle opération active
  "passages": [...],      // Passages de la nouvelle opération active
  "users_sectors": [...]  // Associations user-secteurs de la nouvelle opération
}

📦 Groupes de données attendus

🎯 Groupe operations

Contient les 3 dernières opérations de l'amicale, incluant celle qui vient d'être créée et qui devient automatiquement active.

🗺️ Groupe secteurs

Contient tous les secteurs géographiques associés à la dernière opération créée et active.

📍 Groupe passages

Contient l'ensemble des passages générés pour cette dernière opération créée et active.

👥 Groupe users_sectors

Contient les associations utilisateur-secteurs définissant les attributions pour cette dernière opération créée et active.

⚙️ Traitement automatique des données

Ces 4 groupes sont traités de manière identique au processus de connexion utilisateur, en utilisant le DataLoadingService avec la logique suivante :

// Dans OperationRepository._processCreationResponse()
await _processOperationCreationData(responseData);

Future<void> _processOperationCreationData(Map<String, dynamic> data) async {
  // 1. Vidage des Box Hive concernées (comme au login)
  await _clearRelatedBoxes();

  // 2. Traitement des 4 groupes via DataLoadingService
  if (data.containsKey('operations')) {
    await DataLoadingService.instance.processOperationsFromApi(data['operations']);
  }

  if (data.containsKey('secteurs')) {
    await DataLoadingService.instance.processSectorsFromApi(data['secteurs']);
  }

  if (data.containsKey('passages')) {
    await DataLoadingService.instance.processPassagesFromApi(data['passages']);
  }

  if (data.containsKey('users_sectors')) {
    await DataLoadingService.instance.processUserSectorsFromApi(data['users_sectors']);
  }
}

Future<void> _clearRelatedBoxes() async {
  // Vidage des Box respectives avant rechargement
  final operationsBox = HiveService.instance.getTypedBox<OperationModel>(AppKeys.operationsBoxName);
  final sectorsBox = HiveService.instance.getTypedBox<SectorModel>(AppKeys.sectorsBoxName);
  final passagesBox = HiveService.instance.getTypedBox<PassageModel>(AppKeys.passagesBoxName);
  final userSectorsBox = HiveService.instance.getTypedBox<UserSectorModel>(AppKeys.userSectorBoxName);

  await operationsBox.clear();
  await sectorsBox.clear();
  await passagesBox.clear();
  await userSectorsBox.clear();
}

🔄 Flux de synchronisation

sequenceDiagram
    participant UI as Interface
    participant OR as OperationRepository
    participant API as Serveur API
    participant HS as HiveService
    participant DLS as DataLoadingService
    participant VLB as ValueListenableBuilder

    UI->>OR: createOperation(newOperation)
    OR->>API: POST /operations
    API-->>OR: 201 Created + 4 groupes

    Note over OR: Traitement des données complexes
    OR->>HS: clear() sur 4 Box concernées
    OR->>DLS: processOperationsFromApi()
    OR->>DLS: processSectorsFromApi()
    OR->>DLS: processPassagesFromApi()
    OR->>DLS: processUserSectorsFromApi()

    Note over DLS: Sauvegarde dans Hive
    DLS->>HS: put() dans Box typées
    HS-->>VLB: Notifications de changement
    VLB-->>UI: Mise à jour automatique

Avantages de cette approche

  • Cohérence totale : Données locales parfaitement synchronisées avec le serveur
  • Performance optimisée : Un seul appel API pour toutes les données nécessaires
  • Réactivité immédiate : Interface mise à jour automatiquement via ValueListenableBuilder
  • Logique centralisée : Réutilisation du DataLoadingService existant
  • Gestion d'erreurs : Rollback automatique en cas d'échec du traitement

Cette architecture garantit une synchronisation robuste et performante lors de la création d'opérations, en maintenant la cohérence des données tout en optimisant l'expérience utilisateur. 🚀