feat: Version 3.5.2 - Configuration Stripe et gestion des immeubles

- Configuration complète Stripe pour les 3 environnements (DEV/REC/PROD)
  * DEV: Clés TEST Pierre (mode test)
  * REC: Clés TEST Client (mode test)
  * PROD: Clés LIVE Client (mode live)
- Ajout de la gestion des bases de données immeubles/bâtiments
  * Configuration buildings_database pour DEV/REC/PROD
  * Service BuildingService pour enrichissement des adresses
- Optimisations pages et améliorations ergonomie
- Mises à jour des dépendances Composer
- Nettoyage des fichiers obsolètes

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
pierre
2025-11-09 18:26:27 +01:00
parent 21657a3820
commit 2f5946a184
812 changed files with 142105 additions and 25992 deletions

495
api/docs/EVENTS-LOG.md Normal file
View File

@@ -0,0 +1,495 @@
# Système de logs d'événements JSONL
## 📋 Vue d'ensemble
Système de traçabilité des événements métier pour statistiques et audit, stocké en fichiers JSONL (JSON Lines) sans impact sur la base de données principale.
**Créé le :** 26 Octobre 2025
**Rétention :** 15 mois
**Format :** JSONL (une ligne = un événement JSON)
## 🎯 Objectifs
### Événements tracés
**Authentification**
- Connexions réussies (user_id, entity_id, plateforme, IP)
- Tentatives échouées (username, raison, IP, nb tentatives)
**CRUD métier**
- **Passages** : création, modification, suppression
- **Secteurs** : création, modification, suppression
- **Membres** : création, modification, suppression
- **Entités** : création, modification, suppression
### Cas d'usage
**1. Admin entité**
- Stats de son entité : connexions, passages, secteurs sur 1 jour/semaine/mois
- Activité des membres de l'entité
**2. Super-admin**
- Stats globales : tous les passages modifiés sur 2 semaines
- Événements toutes entités sur période donnée
- Détection d'anomalies
## 📁 Architecture de stockage
### Structure des répertoires
```
/logs/events/
├── 2025-10-26.jsonl # Fichier du jour (écriture append)
├── 2025-10-25.jsonl
├── 2025-10-24.jsonl
├── 2025-09-30.jsonl
├── 2025-09-29.jsonl.gz # Compression auto après 30 jours
└── archive/
├── 2025-09.jsonl.gz # Archive mensuelle
├── 2025-08.jsonl.gz
└── 2024-07.jsonl.gz # Supprimé auto après 15 mois
```
### Cycle de vie des fichiers
| Âge | État | Taille estimée | Accès |
|-----|------|----------------|-------|
| 0-30 jours | `.jsonl` non compressé | 1-10 MB/jour | Lecture directe rapide |
| 30 jours-15 mois | `.jsonl.gz` compressé | ~100 KB/jour | Décompression à la volée |
| > 15 mois | Supprimé automatiquement | - | - |
### Rotation et rétention
**CRON mensuel** : `scripts/cron/rotate_event_logs.php`
- **Fréquence** : 1er du mois à 3h00
- **Actions** :
1. Compresser les fichiers `.jsonl` de plus de 30 jours en `.jsonl.gz`
2. Supprimer les fichiers `.jsonl.gz` de plus de 15 mois
3. Logger le résumé de rotation
**Commande manuelle** :
```bash
php scripts/cron/rotate_event_logs.php
```
## 📊 Format des événements
### Structure commune
Tous les événements partagent ces champs :
```json
{
"timestamp": "2025-10-26T14:32:15Z", // ISO 8601 UTC
"event": "nom_evenement", // Type d'événement
"user_id": 123, // ID utilisateur (si authentifié)
"entity_id": 5, // ID entité (si applicable)
"ip": "192.168.1.100", // IP client
"platform": "ios|android|web", // Plateforme
"app_version": "3.3.6" // Version app (mobile uniquement)
}
```
### Événements d'authentification
#### Login réussi
```jsonl
{"timestamp":"2025-10-26T14:32:15Z","event":"login_success","user_id":123,"entity_id":5,"platform":"ios","app_version":"3.3.6","ip":"192.168.1.100","username":"user123"}
```
#### Login échoué
```jsonl
{"timestamp":"2025-10-26T14:35:22Z","event":"login_failed","username":"test","reason":"invalid_password","ip":"192.168.1.101","attempt":3,"platform":"web"}
```
**Raisons possibles** : `invalid_password`, `user_not_found`, `account_inactive`, `blocked_ip`
#### Logout
```jsonl
{"timestamp":"2025-10-26T16:45:00Z","event":"logout","user_id":123,"entity_id":5,"platform":"android","session_duration":7800}
```
### Événements Passages
#### Création
```jsonl
{"timestamp":"2025-10-26T14:40:10Z","event":"passage_created","passage_id":45678,"user_id":123,"entity_id":5,"operation_id":789,"sector_id":12,"amount":50.00,"payment_type":"cash","platform":"android"}
```
#### Modification
```jsonl
{"timestamp":"2025-10-26T14:42:05Z","event":"passage_updated","passage_id":45678,"user_id":123,"entity_id":5,"changes":{"amount":{"old":50.00,"new":75.00},"payment_type":{"old":"cash","new":"stripe"}},"platform":"ios"}
```
#### Suppression
```jsonl
{"timestamp":"2025-10-26T14:45:30Z","event":"passage_deleted","passage_id":45678,"user_id":123,"entity_id":5,"operation_id":789,"deleted_by":123,"soft_delete":true,"platform":"web"}
```
### Événements Secteurs
#### Création
```jsonl
{"timestamp":"2025-10-26T15:10:00Z","event":"sector_created","sector_id":456,"operation_id":789,"entity_id":5,"user_id":123,"sector_name":"Secteur A","platform":"web"}
```
#### Modification
```jsonl
{"timestamp":"2025-10-26T15:12:00Z","event":"sector_updated","sector_id":456,"operation_id":789,"entity_id":5,"user_id":123,"changes":{"sector_name":{"old":"Secteur A","new":"Secteur Alpha"}},"platform":"web"}
```
#### Suppression
```jsonl
{"timestamp":"2025-10-26T15:15:00Z","event":"sector_deleted","sector_id":456,"operation_id":789,"entity_id":5,"user_id":123,"deleted_by":123,"soft_delete":true,"platform":"web"}
```
### Événements Membres (Users)
#### Création
```jsonl
{"timestamp":"2025-10-26T15:20:00Z","event":"user_created","new_user_id":789,"entity_id":5,"created_by":123,"role_id":1,"username":"newuser","platform":"web"}
```
#### Modification
```jsonl
{"timestamp":"2025-10-26T15:25:00Z","event":"user_updated","user_id":789,"entity_id":5,"updated_by":123,"changes":{"role_id":{"old":1,"new":2},"encrypted_phone":true},"platform":"web"}
```
**Note** : Les champs chiffrés sont indiqués par un booléen `true` sans exposer les valeurs
#### Suppression
```jsonl
{"timestamp":"2025-10-26T15:30:00Z","event":"user_deleted","user_id":789,"entity_id":5,"deleted_by":123,"soft_delete":true,"platform":"web"}
```
### Événements Entités
#### Création
```jsonl
{"timestamp":"2025-10-26T15:35:00Z","event":"entity_created","entity_id":25,"created_by":1,"entity_type_id":1,"postal_code":"75001","platform":"web"}
```
#### Modification
```jsonl
{"timestamp":"2025-10-26T15:40:00Z","event":"entity_updated","entity_id":25,"user_id":123,"updated_by":123,"changes":{"encrypted_name":true,"encrypted_email":true,"chk_stripe":{"old":0,"new":1}},"platform":"web"}
```
#### Suppression (rare)
```jsonl
{"timestamp":"2025-10-26T15:45:00Z","event":"entity_deleted","entity_id":25,"deleted_by":1,"soft_delete":true,"reason":"duplicate","platform":"web"}
```
### Événements Opérations
#### Création
```jsonl
{"timestamp":"2025-10-26T16:00:00Z","event":"operation_created","operation_id":999,"entity_id":5,"created_by":123,"date_start":"2025-11-01","date_end":"2025-11-30","platform":"web"}
```
#### Modification
```jsonl
{"timestamp":"2025-10-26T16:05:00Z","event":"operation_updated","operation_id":999,"entity_id":5,"updated_by":123,"changes":{"date_end":{"old":"2025-11-30","new":"2025-12-15"},"chk_active":{"old":0,"new":1}},"platform":"web"}
```
#### Suppression
```jsonl
{"timestamp":"2025-10-26T16:10:00Z","event":"operation_deleted","operation_id":999,"entity_id":5,"deleted_by":123,"soft_delete":true,"platform":"web"}
```
## 🛠️ Implémentation
### Service EventLogService.php
**Emplacement** : `src/Services/EventLogService.php`
**Méthodes publiques** :
```php
EventLogService::logLoginSuccess($userId, $entityId, $username)
EventLogService::logLoginFailed($username, $reason, $attempt)
EventLogService::logLogout($userId, $entityId, $sessionDuration)
EventLogService::logPassageCreated($passageId, $operationId, $sectorId, $amount, $paymentType)
EventLogService::logPassageUpdated($passageId, $changes)
EventLogService::logPassageDeleted($passageId, $operationId, $softDelete)
EventLogService::logSectorCreated($sectorId, $operationId, $sectorName)
EventLogService::logSectorUpdated($sectorId, $operationId, $changes)
EventLogService::logSectorDeleted($sectorId, $operationId, $softDelete)
EventLogService::logUserCreated($newUserId, $entityId, $roleId, $username)
EventLogService::logUserUpdated($userId, $changes)
EventLogService::logUserDeleted($userId, $softDelete)
EventLogService::logEntityCreated($entityId, $entityTypeId, $postalCode)
EventLogService::logEntityUpdated($entityId, $changes)
EventLogService::logEntityDeleted($entityId, $reason)
EventLogService::logOperationCreated($operationId, $dateStart, $dateEnd)
EventLogService::logOperationUpdated($operationId, $changes)
EventLogService::logOperationDeleted($operationId, $softDelete)
```
**Enrichissement automatique** :
- `timestamp` : Généré automatiquement (UTC)
- `user_id`, `entity_id` : Récupérés depuis `Session`
- `ip` : Récupérée via `ClientDetector`
- `platform` : Détecté via `ClientDetector` (ios/android/web)
- `app_version` : Extrait du User-Agent pour mobile
### Intégration dans les Controllers
**Exemple dans PassageController** :
```php
public function createPassage(Request $request, Response $response): void {
// ... validation et création ...
$passageId = $db->lastInsertId();
// Log de l'événement
EventLogService::logPassageCreated(
$passageId,
$data['fk_operation'],
$data['fk_sector'],
$data['montant'],
$data['fk_type_reglement']
);
// ... suite du code ...
}
```
### Scripts d'analyse
#### 1. Stats entité
**Fichier** : `scripts/stats/entity_stats.php`
**Usage** :
```bash
# Stats entité 5 sur 7 derniers jours
php scripts/stats/entity_stats.php --entity-id=5 --days=7
# Stats entité 5 entre deux dates
php scripts/stats/entity_stats.php --entity-id=5 --from=2025-10-01 --to=2025-10-26
# Résultat JSON
{
"entity_id": 5,
"period": {"from": "2025-10-20", "to": "2025-10-26"},
"stats": {
"logins": {"success": 45, "failed": 2},
"passages": {"created": 120, "updated": 15, "deleted": 3},
"sectors": {"created": 2, "updated": 8, "deleted": 0},
"users": {"created": 1, "updated": 5, "deleted": 0}
},
"top_users": [
{"user_id": 123, "actions": 85},
{"user_id": 456, "actions": 42}
]
}
```
#### 2. Stats globales super-admin
**Fichier** : `scripts/stats/global_stats.php`
**Usage** :
```bash
# Tous les passages modifiés sur 2 semaines
php scripts/stats/global_stats.php --event=passage_updated --days=14
# Toutes les connexions échouées du mois
php scripts/stats/global_stats.php --event=login_failed --month=2025-10
# Résultat JSON
{
"event": "passage_updated",
"period": {"from": "2025-10-13", "to": "2025-10-26"},
"total_events": 342,
"by_entity": [
{"entity_id": 5, "count": 120},
{"entity_id": 12, "count": 85},
{"entity_id": 18, "count": 67}
],
"by_day": {
"2025-10-26": 45,
"2025-10-25": 38,
"2025-10-24": 52
}
}
```
#### 3. Export CSV pour analyse externe
**Fichier** : `scripts/stats/export_events_csv.php`
**Usage** :
```bash
# Exporter toutes les connexions du mois en CSV
php scripts/stats/export_events_csv.php \
--event=login_success \
--month=2025-10 \
--output=/tmp/logins_october.csv
```
### CRON de rotation
**Fichier** : `scripts/cron/rotate_event_logs.php`
**Configuration crontab** :
```cron
# Rotation des logs d'événements - 1er du mois à 3h
0 3 1 * * cd /var/www/geosector/api && php scripts/cron/rotate_event_logs.php
```
**Actions** :
1. Compresser fichiers > 30 jours : `gzip logs/events/2025-09-*.jsonl`
2. Supprimer archives > 15 mois : `rm logs/events/*-2024-06-*.jsonl.gz`
3. Logger résumé dans `logs/rotation.log`
## 📈 Performances et volumétrie
### Estimations
**Volume quotidien moyen** (pour 50 entités actives) :
- 500 connexions/jour = 500 lignes
- 2000 passages créés/modifiés = 2000 lignes
- 100 autres événements = 100 lignes
- **Total : ~2600 événements/jour**
**Taille fichier** :
- 1 événement ≈ 200-400 bytes JSON
- 2600 événements ≈ 0.8-1 MB/jour non compressé
- Compression gzip : ratio ~10:1 → **~100 KB/jour compressé**
**Rétention 15 mois** :
- Non compressé (30 jours) : 30 MB
- Compressé (14.5 mois) : 45 MB
- **Total stockage : ~75 MB** pour 15 mois
### Optimisation lecture
**Lecture mono-fichier** : < 50ms pour analyser 1 jour (2600 événements)
**Lecture période 7 jours** :
- 7 fichiers × 1 MB = 7 MB à lire
- Filtrage `jq` ou PHP : ~200-300ms
**Lecture période 2 semaines (super-admin)** :
- 14 fichiers × 1 MB = 14 MB à lire
- Filtrage sur type événement : ~500ms
**Lecture archive compressée** :
- Décompression à la volée : +100-200ms
- Total : ~700-800ms pour 1 mois compressé
## 🔒 Sécurité et confidentialité
### Données sensibles
** Jamais loggé en clair** :
- Mots de passe
- Contenu chiffré (noms, emails, téléphones, IBAN)
- Tokens d'authentification
** Loggé** :
- IDs (user_id, entity_id, passage_id, etc.)
- Montants financiers
- Dates et timestamps
- Types de modifications (indicateur booléen pour champs chiffrés)
### Exemple champ chiffré
```json
{
"event": "user_updated",
"changes": {
"encrypted_name": true, // Indique modification sans valeur
"encrypted_email": true,
"role_id": {"old": 1, "new": 2} // Champ non sensible = valeurs OK
}
}
```
### Permissions d'accès
**Fichiers logs** :
- Propriétaire : `nginx:nginx`
- Permissions : `0640` (lecture nginx, écriture nginx, aucun autre)
- Dossier `/logs/events/` : `0750`
**Scripts d'analyse** :
- Exécution : root ou nginx uniquement
- Pas d'accès direct via endpoints API (pour l'instant)
## 🚀 Roadmap et évolutions futures
### Phase 1 - MVP (actuel) ✅
- [x] Architecture JSONL quotidienne
- [x] Service EventLogService.php
- [x] Intégration dans controllers (LoginController, PassageController, UserController, SectorController, OperationController, EntiteController)
- [ ] CRON de rotation 15 mois
- [ ] Scripts d'analyse de base
### Phase 2 - Dashboards (Q1 2026)
- [ ] Endpoints API : `GET /api/stats/entity/{id}`, `GET /api/stats/global`
- [ ] Interface web admin : graphiques connexions, passages
- [ ] Filtres avancés (période, plateforme, utilisateur)
### Phase 3 - Alertes (Q2 2026)
- [ ] Détection anomalies (pics de connexions échouées)
- [ ] Alertes email super-admins
- [ ] Seuils configurables par entité
### Phase 4 - Migration TimescaleDB (si besoin)
- [ ] Évaluation volume : si > 50k événements/jour
- [ ] Import JSONL → TimescaleDB
- [ ] Rétention hybride : 90j TimescaleDB, archives JSONL
## 📝 Statut implémentation
**Date : 28 Octobre 2025**
### ✅ Terminé
- Service `EventLogService.php` créé avec toutes les méthodes de logging
- Intégration complète dans les 6 controllers principaux :
- **LoginController** : login réussi/échoué, logout
- **PassageController** : création, modification, suppression passages
- **UserController** : création, modification, suppression utilisateurs
- **SectorController** : création, modification, suppression secteurs
- **OperationController** : création, modification, suppression opérations
- **EntiteController** : création, modification entités
- Enrichissement automatique : timestamp UTC, user_id, entity_id, IP, platform, app_version
- Sécurité : champs sensibles loggés en booléen uniquement (pas de valeurs chiffrées)
- Script de déploiement `deploy-api.sh` crée automatiquement `/logs/events/` avec permissions 0750
### 🔄 En attente
- Scripts d'analyse (`entity_stats.php`, `global_stats.php`, `export_events_csv.php`)
- CRON de rotation 15 mois (`rotate_event_logs.php`)
- Tests en environnement DEV
## 📝 Checklist déploiement
### Environnement DEV (dva-geo)
- [x] Créer dossier `/logs/events/` (permissions 0750) - Intégré dans deploy-api.sh
- [x] Déployer `EventLogService.php`
- [ ] Déployer scripts stats et rotation
- [ ] Configurer CRON rotation
- [ ] Tests : générer événements manuellement
- [ ] Valider format JSONL et rotation
### Environnement RECETTE (rca-geo)
- [ ] Déployer depuis DEV validé
- [ ] Tests de charge : 10k événements/jour
- [ ] Valider performances scripts d'analyse
- [ ] Valider compression et suppression auto
### Environnement PRODUCTION (pra-geo)
- [ ] Déployer depuis RECETTE validée
- [ ] Monitoring volumétrie
- [ ] Backups quotidiens `/logs/events/` (via CRON général)
---
**Dernière mise à jour :** 28 Octobre 2025
**Version :** 1.1
**Statut :** ✅ Service implémenté et intégré - Scripts d'analyse à développer