# 📱 Documentation Synchronisation Admin → Mobile TIM CASH

## 🎯 Vue d'ensemble

Ce système permet de synchroniser automatiquement les configurations de l'interface admin vers toutes les applications mobiles connectées en temps réel.

### Fonctionnalités synchronisées :
- ✅ **Commissions** (TIM BUSINESS, CinetPay, déblocage compte, prêts)
- ✅ **Publicités** (création, modification, suppression, activation/désactivation)
- ✅ **Paramètres de prêts SOS** (montants, durées, taux d'intérêt)
- ✅ **Limites de compte** (par type TIM MINI/MAXI/BUSINESS)
- ✅ **Paramètres NFC** (limites, sécurité)
- ✅ **Paramètres généraux** (maintenance, versions, features)

---

## 🔌 Architecture

### 1. WebSocket pour les notifications en temps réel

```
┌─────────────┐         WebSocket          ┌──────────────┐
│   Admin     │ ──────────────────────────> │   Backend    │
│  Interface  │   Mise à jour config        │   FastAPI    │
└─────────────┘                             └──────┬───────┘
                                                   │
                                                   │ Broadcast
                                                   │
                        ┌──────────────────────────┼──────────────────────────┐
                        │                          │                          │
                        ▼                          ▼                          ▼
                ┌───────────────┐          ┌───────────────┐         ┌───────────────┐
                │  Mobile App 1 │          │  Mobile App 2 │         │  Mobile App N │
                │   (Flutter)   │          │   (Flutter)   │         │   (Flutter)   │
                └───────────────┘          └───────────────┘         └───────────────┘
```

### 2. Endpoints REST pour récupération des configurations

Les apps mobiles peuvent également récupérer les configurations via des endpoints REST classiques.

---

## 📡 Intégration Mobile (Flutter/Dart)

### 1. Connexion WebSocket

```dart
import 'package:web_socket_channel/web_socket_channel.dart';
import 'dart:convert';

class ConfigSyncService {
  WebSocketChannel? _channel;
  final String baseUrl = 'ws://localhost:8001'; // ou votre URL de production
  
  // Connexion au WebSocket
  void connect(String userId) {
    try {
      _channel = WebSocketChannel.connect(
        Uri.parse('$baseUrl/api/app-config/ws/mobile/$userId'),
      );
      
      // Écouter les messages
      _channel!.stream.listen(
        (message) {
          _handleMessage(jsonDecode(message));
        },
        onError: (error) {
          print('WebSocket Error: $error');
          _reconnect(userId);
        },
        onDone: () {
          print('WebSocket Closed');
          _reconnect(userId);
        },
      );
      
      // Envoyer un heartbeat toutes les 30 secondes
      _startHeartbeat();
      
      print('✅ WebSocket connecté');
    } catch (e) {
      print('❌ Erreur de connexion WebSocket: $e');
    }
  }
  
  // Gérer les messages reçus
  void _handleMessage(Map<String, dynamic> message) {
    final type = message['type'];
    
    switch (type) {
      case 'connection_established':
        print('🔗 Connexion établie');
        _checkConfigVersions(message['config_versions']);
        break;
        
      case 'config_update':
        _handleConfigUpdate(message);
        break;
        
      case 'advertisement_update':
        _handleAdvertisementUpdate(message);
        break;
        
      default:
        print('Message inconnu: $type');
    }
  }
  
  // Gérer les mises à jour de configuration
  void _handleConfigUpdate(Map<String, dynamic> message) {
    final configType = message['config_type'];
    final data = message['data'];
    final version = message['version'];
    
    print('📥 Mise à jour de configuration: $configType');
    
    switch (configType) {
      case 'commissions':
        _updateCommissions(data, version);
        break;
        
      case 'loan_settings':
        _updateLoanSettings(data, version);
        break;
        
      case 'account_limits':
        _updateAccountLimits(data, version);
        break;
        
      case 'nfc_settings':
        _updateNfcSettings(data, version);
        break;
        
      default:
        print('Type de config inconnu: $configType');
    }
  }
  
  // Gérer les mises à jour de publicité
  void _handleAdvertisementUpdate(Map<String, dynamic> message) {
    final action = message['action'];
    final data = message['data'];
    
    print('📢 Mise à jour publicité: $action');
    
    switch (action) {
      case 'create':
        _addAdvertisement(data);
        break;
      case 'update':
        _updateAdvertisement(data);
        break;
      case 'delete':
        _removeAdvertisement(data['id']);
        break;
      case 'activate':
        _activateAdvertisement(data['id']);
        break;
      case 'deactivate':
        _deactivateAdvertisement(data['id']);
        break;
    }
  }
  
  // Heartbeat pour maintenir la connexion
  void _startHeartbeat() {
    Timer.periodic(Duration(seconds: 30), (timer) {
      if (_channel != null) {
        _channel!.sink.add('ping');
      }
    });
  }
  
  // Reconnexion automatique
  void _reconnect(String userId) {
    Future.delayed(Duration(seconds: 5), () {
      print('🔄 Tentative de reconnexion...');
      connect(userId);
    });
  }
  
  // Fermer la connexion
  void disconnect() {
    _channel?.sink.close();
  }
}
```

### 2. Récupération des configurations via REST

```dart
import 'package:http/http.dart' as http;
import 'dart:convert';

class ConfigApiService {
  final String baseUrl = 'http://localhost:8001/api/app-config';
  
  // Récupérer les versions des configurations
  Future<Map<String, dynamic>> getConfigVersions() async {
    final response = await http.get(Uri.parse('$baseUrl/config/versions'));
    return jsonDecode(response.body);
  }
  
  // Récupérer les commissions
  Future<Map<String, dynamic>> getCommissions() async {
    final response = await http.get(Uri.parse('$baseUrl/config/commissions'));
    return jsonDecode(response.body);
  }
  
  // Récupérer les publicités actives
  Future<List<dynamic>> getAdvertisements() async {
    final response = await http.get(Uri.parse('$baseUrl/config/advertisements'));
    final data = jsonDecode(response.body);
    return data['data'];
  }
  
  // Récupérer les paramètres de prêt
  Future<Map<String, dynamic>> getLoanSettings() async {
    final response = await http.get(Uri.parse('$baseUrl/config/loan-settings'));
    return jsonDecode(response.body);
  }
  
  // Récupérer les limites de compte
  Future<Map<String, dynamic>> getAccountLimits() async {
    final response = await http.get(Uri.parse('$baseUrl/config/account-limits'));
    return jsonDecode(response.body);
  }
  
  // Récupérer les paramètres NFC
  Future<Map<String, dynamic>> getNfcSettings() async {
    final response = await http.get(Uri.parse('$baseUrl/config/nfc-settings'));
    return jsonDecode(response.body);
  }
  
  // Récupérer les paramètres généraux
  Future<Map<String, dynamic>> getAppSettings() async {
    final response = await http.get(Uri.parse('$baseUrl/config/app-settings'));
    return jsonDecode(response.body);
  }
}
```

### 3. Stockage local des configurations

```dart
import 'package:shared_preferences/shared_preferences.dart';

class ConfigStorage {
  static const String _commissionsKey = 'config_commissions';
  static const String _loanSettingsKey = 'config_loan_settings';
  static const String _accountLimitsKey = 'config_account_limits';
  static const String _nfcSettingsKey = 'config_nfc_settings';
  static const String _advertisementsKey = 'config_advertisements';
  
  // Sauvegarder les commissions
  Future<void> saveCommissions(Map<String, dynamic> data, String version) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString(_commissionsKey, jsonEncode({
      'data': data,
      'version': version,
      'updated_at': DateTime.now().toIso8601String(),
    }));
  }
  
  // Récupérer les commissions
  Future<Map<String, dynamic>?> getCommissions() async {
    final prefs = await SharedPreferences.getInstance();
    final jsonString = prefs.getString(_commissionsKey);
    if (jsonString != null) {
      return jsonDecode(jsonString);
    }
    return null;
  }
  
  // Vérifier si une mise à jour est nécessaire
  Future<bool> needsUpdate(String configType, String serverVersion) async {
    final prefs = await SharedPreferences.getInstance();
    final key = 'config_$configType';
    final jsonString = prefs.getString(key);
    
    if (jsonString == null) return true;
    
    final config = jsonDecode(jsonString);
    return config['version'] != serverVersion;
  }
}
```

---

## 🔧 Utilisation côté Admin

### 1. Mettre à jour les commissions

```typescript
// Dans votre interface admin React/TypeScript
const updateCommissions = async (commissionData: any) => {
  try {
    const response = await fetch('http://localhost:8001/api/app-config/admin/update-commissions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(commissionData),
    });
    
    const result = await response.json();
    console.log(`✅ ${result.notified_clients} applications mobiles notifiées`);
  } catch (error) {
    console.error('Erreur:', error);
  }
};

// Exemple d'utilisation
updateCommissions({
  tim_business_commission: 2.5,  // Nouveau taux: 2.5%
  cinetpay_commission: 0.5,
  account_unlock_commission: 15.0,
  loan_interest_rate: 6.0,
});
```

### 2. Créer/Modifier une publicité

```typescript
const updateAdvertisement = async (adData: any, action: string) => {
  try {
    const response = await fetch(
      `http://localhost:8001/api/app-config/admin/update-advertisement?action=${action}`,
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(adData),
      }
    );
    
    const result = await response.json();
    console.log(`✅ Publicité ${action}: ${result.notified_clients} apps notifiées`);
  } catch (error) {
    console.error('Erreur:', error);
  }
};

// Exemple: Créer une nouvelle publicité
updateAdvertisement({
  id: 'ad_001',
  title: 'Promo TIM MAXI',
  description: 'Ouvrez votre compte sans frais',
  image_url: 'https://...',
  target_countries: ['CI'],
  target_cities: ['Abidjan'],
  start_date: '2024-01-01',
  end_date: '2024-12-31',
  is_active: true,
}, 'create');
```

### 3. Mettre à jour les paramètres de prêt

```typescript
const updateLoanSettings = async (loanData: any) => {
  try {
    const response = await fetch('http://localhost:8001/api/app-config/admin/update-loan-settings', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(loanData),
    });
    
    const result = await response.json();
    console.log(`✅ ${result.notified_clients} applications mobiles notifiées`);
  } catch (error) {
    console.error('Erreur:', error);
  }
};

// Exemple
updateLoanSettings({
  tim_maxi: {
    min_amount: 10000,
    max_amount: 150000,  // Augmentation de la limite
    duration_days: 3,
    interest_rate: 6.0,
  },
});
```

---

## 📊 Endpoints API Disponibles

### Pour les applications mobiles

| Endpoint | Méthode | Description |
|----------|---------|-------------|
| `/api/app-config/ws/mobile/{client_id}` | WebSocket | Connexion WebSocket pour notifications temps réel |
| `/api/app-config/config/versions` | GET | Récupérer toutes les versions de config |
| `/api/app-config/config/commissions` | GET | Récupérer les taux de commission |
| `/api/app-config/config/advertisements` | GET | Récupérer les publicités actives |
| `/api/app-config/config/loan-settings` | GET | Récupérer les paramètres de prêt |
| `/api/app-config/config/account-limits` | GET | Récupérer les limites de compte |
| `/api/app-config/config/nfc-settings` | GET | Récupérer les paramètres NFC |
| `/api/app-config/config/app-settings` | GET | Récupérer les paramètres généraux |

### Pour l'interface admin

| Endpoint | Méthode | Description |
|----------|---------|-------------|
| `/api/app-config/admin/update-commissions` | POST | Mettre à jour les commissions |
| `/api/app-config/admin/update-advertisement` | POST | Créer/Modifier une publicité |
| `/api/app-config/admin/update-loan-settings` | POST | Mettre à jour les paramètres de prêt |
| `/api/app-config/admin/update-account-limits` | POST | Mettre à jour les limites |
| `/api/app-config/admin/update-nfc-settings` | POST | Mettre à jour les paramètres NFC |

---

## 🔐 Sécurité

### Recommandations :

1. **Authentification** : Ajouter un token JWT pour les connexions WebSocket
2. **Validation** : Valider toutes les données côté serveur
3. **Rate Limiting** : Limiter le nombre de mises à jour par minute
4. **Logs** : Enregistrer toutes les modifications de configuration
5. **Rollback** : Prévoir un système de rollback en cas d'erreur

---

## 🚀 Déploiement

### 1. Ajouter les routes dans `main.py`

```python
from api.v1.route_app_config import router as app_config_router

app.include_router(
    app_config_router,
    prefix="/api/app-config",
    tags=["App Configuration"]
)
```

### 2. Variables d'environnement

```env
# WebSocket
WEBSOCKET_PING_INTERVAL=30
WEBSOCKET_PING_TIMEOUT=10

# Configuration
CONFIG_CACHE_TTL=300  # 5 minutes
```

### 3. Production

- Utiliser **Redis** pour le cache des configurations
- Utiliser **Redis Pub/Sub** pour la synchronisation multi-serveurs
- Mettre en place un **Load Balancer** avec sticky sessions pour WebSocket

---

## 📝 Exemple de flux complet

```
1. Admin modifie une commission dans l'interface web
   ↓
2. Frontend envoie POST /api/app-config/admin/update-commissions
   ↓
3. Backend sauvegarde dans la base de données
   ↓
4. Backend notifie via WebSocket tous les mobiles connectés
   ↓
5. Mobile reçoit la notification en temps réel
   ↓
6. Mobile met à jour sa configuration locale
   ↓
7. Mobile applique les nouveaux taux immédiatement
```

---

## 🐛 Debugging

### Vérifier les connexions WebSocket actives

```python
# Dans votre code backend
from core.websocket_manager import manager

# Nombre de clients mobiles connectés
print(f"Clients mobiles: {len(manager.active_connections.get('mobile', set()))}")

# Versions des configurations
print(manager.get_all_config_versions())
```

### Tester le WebSocket

```bash
# Avec wscat
npm install -g wscat
wscat -c ws://localhost:8001/api/app-config/ws/mobile/test_user_123
```

---

## ✅ Checklist d'intégration

- [ ] Backend: Routes API créées
- [ ] Backend: WebSocket configuré
- [ ] Mobile: Service de synchronisation implémenté
- [ ] Mobile: Stockage local configuré
- [ ] Mobile: Gestion des reconnexions
- [ ] Admin: Boutons de mise à jour connectés
- [ ] Tests: Synchronisation en temps réel
- [ ] Tests: Reconnexion automatique
- [ ] Production: Redis configuré
- [ ] Production: Load balancer configuré

---

## 📞 Support

Pour toute question sur l'intégration, contactez l'équipe technique TIM CASH.
