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:
495
api/docs/EVENTS-LOG.md
Normal file
495
api/docs/EVENTS-LOG.md
Normal 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
|
||||
Reference in New Issue
Block a user