# Documentation Technique API GeoSector

## 🏗️ Infrastructure

**Stack** : PHP 8.3 | NGINX | MariaDB 10.11 | Alpine Linux (Incus)

| Environnement | Container | DB (IP) | URL | Serveur |
|---------------|-----------|---------|-----|---------|
| **DEV** | dva-geo | maria3 (13.23.33.4) | https://dapp.geosector.fr | IN3 (195.154.80.116) |
| **REC** | rca-geo | maria3 (13.23.33.4) | https://rapp.geosector.fr | IN3 (195.154.80.116) |
| **PROD** | pra-geo | maria4 (13.23.33.4) | https://app3.geosector.fr | IN4 (51.159.7.190) |

**Architecture** : MVC sans framework | Point d'entrée : `index.php` | Config : `AppConfig.php` singleton

## 🗄️ Base de données

### Modèle d'isolation par opération (CRITIQUE)

**Principe** : Chaque opération est un **univers fermé** isolé de la table centrale `users`.

```
users (table centrale, conservée après suppression opération)
  └── ope_users (id, fk_user, fk_operation) ← PIVOT par opération
        ├── ope_users_sectors (fk_user → ope_users.id)
        └── ope_pass (fk_user → ope_users.id)
```

**⚠️ IMPORTANT pour Flutter** :
- **`users.id`** = Identifiant global (gestion membres, login)
- **`ope_users.id`** = Identifiant opération (passages, secteurs)
- **Flutter doit gérer les 2 IDs** lors du login et des CRUD

**Réponse login :**
```json
{
  "users_sectors": [
    {
      "id": 123,           // users.id (gestion membres)
      "ope_user_id": 50,   // ope_users.id (passages/secteurs)
      "name": "John Doe",
      "fk_sector": 456
    }
  ]
}
```

**Requêtes API depuis Flutter :**
```json
// ✅ Création secteur : envoyer ope_users.id
POST /api/sectors { "users": [50, 51] }  // ope_users.id

// ✅ Création passage : fk_user = ope_users.id
POST /api/passages { "fk_user": 50 }  // ope_users.id

// ✅ Modification membre : utiliser users.id
PUT /api/users/123  // users.id
```

### Tables principales

- **`entites`** : Amicales (chiffrement AES-256-CBC sur nom, email, IBAN)
- **`users`** : Table centrale utilisateurs (conservée même si opération supprimée)
- **`operations`** : Campagnes liées à une entité
- **`ope_users`** : **PIVOT** users ↔ opérations (ON DELETE CASCADE depuis operations)
- **`ope_sectors`** : Secteurs géographiques (polygones)
- **`ope_users_sectors`** : Affectation users ↔ secteurs (FK → `ope_users.id`)
- **`ope_pass`** : Passages (FK → `ope_users.id`, ON DELETE CASCADE)
- **`medias`** : Fichiers (logos, exports, reçus) - Stockage : `/uploads/`

## 🔒 Sécurité

- **Auth** : Sessions PHP (httpOnly, secure, SameSite=Strict)
- **Mots de passe** : NIST SP 800-63B, bcrypt, HIBP check (k-anonymity)
- **Chiffrement** : AES-256-CBC (noms, emails, téléphones, IBAN)
- **Protection** : Brute force (8 tentatives/5min), IP blocking, PDO prepared statements
- **Monitoring** : `SecurityMonitor`, `AlertService`, `IPBlocker`

## 💳 Stripe Connect

- **DEV** : Clés TEST Pierre
- **REC** : Clés TEST client + webhook `webhook-rca`
- **PROD** : Clés LIVE client + webhook `webhook-pra`
- **API** : `2025-08-27.basil`
- **Tap to Pay** : iOS 16.4+ (iPhone XS+) | Android 11+ (95+ devices certifiés)
- **Flow** : Passage créé → PaymentIntent → Tap to Pay → Mise à jour `stripe_payment_id`

## 📦 Fonctionnalités

1. **Reçus fiscaux** : PDF auto (<5KB) pour dons, envoi email queue
2. **Logos entités** : Upload PNG/JPG, redimensionnement 250x250px, base64
3. **Migration** : Endpoints REST par entité (9 phases)
4. **CRONs** : Email queue (*/5), cleanup sécurité (2h)

## 📊 Statistiques Events (Admin Flutter)

### Architecture

**Principe** : Stats pré-agrégées en SQL + détail JSONL à la demande

| Source | Usage | Performance |
|--------|-------|-------------|
| Table `event_stats_daily` | Dashboard, graphiques, tendances | Instantané (~1ms) |
| Fichiers JSONL | Détail événements (clic sur stat) | Paginé (~50-100ms) |

### Flux de données

1. **EventLogService** écrit les événements dans `/logs/events/YYYY-MM-DD.jsonl`
2. **CRON nightly** agrège J-1 dans `event_stats_daily`
3. **API** sert les stats agrégées (SQL) ou le détail paginé (JSONL)
4. **Flutter Admin** affiche dashboard avec drill-down

### Table d'agrégation

**`event_stats_daily`** : Une ligne par (date, entité, type d'événement)

| Colonne | Description |
|---------|-------------|
| `stat_date` | Date des stats |
| `entity_id` | Entité (NULL = global super-admin) |
| `event_type` | Type événement (login_success, passage_created, etc.) |
| `count` | Nombre d'occurrences |
| `sum_amount` | Somme montants (passages) |
| `unique_users` | Utilisateurs distincts |
| `metadata` | JSON agrégé (top secteurs, erreurs fréquentes, etc.) |

### Endpoints API

| Endpoint | Période | Source | Taille réponse |
|----------|---------|--------|----------------|
| `GET /events/stats/summary` | Jour courant | SQL | ~1 KB |
| `GET /events/stats/daily` | Plage dates | SQL | ~5 KB |
| `GET /events/stats/weekly` | Calculé depuis daily | SQL | ~2 KB |
| `GET /events/stats/monthly` | Calculé depuis daily | SQL | ~1 KB |
| `GET /events/details` | Détail paginé | JSONL | ~10 KB |

### Optimisations transfert Flutter

- **Pagination** : 50 events max par requête détail
- **Champs filtrés** : Pas d'IP ni user_agent complet dans les réponses
- **Compression gzip** : -70% sur JSON
- **Cache HTTP** : ETag sur stats (changent 1x/jour)
- **Calcul hebdo/mensuel** : À la volée depuis `daily` (pas de tables supplémentaires)

### Types d'événements agrégés

| Catégorie | Events |
|-----------|--------|
| **Auth** | login_success, login_failed, logout |
| **Passages** | passage_created, passage_updated, passage_deleted |
| **Secteurs** | sector_created, sector_updated, sector_deleted |
| **Users** | user_created, user_updated, user_deleted |
| **Entités** | entity_created, entity_updated, entity_deleted |
| **Opérations** | operation_created, operation_updated, operation_deleted |
| **Stripe** | stripe_payment_created, stripe_payment_success, stripe_payment_failed, stripe_payment_cancelled, stripe_terminal_error |

### Accès et sécurité

- **Rôle requis** : Admin entité (role_id = 2) ou Super-admin (role_id = 1)
- **Isolation** : Admin voit uniquement les stats de son entité
- **Super-admin** : Accès global (entity_id = NULL dans requêtes)

## 🚀 Déploiement

```bash
./deploy-api.sh      # Local → dva-geo (DEV)
./deploy-api.sh rca  # dva-geo → rca-geo (REC)
./deploy-api.sh pra  # rca-geo → pra-geo (PROD)
```

- Backup auto (10 versions)
- Préservation `/logs/` et `/uploads/`
- Permissions : `nginx:nginx` (code), `nginx:nginx` (logs/uploads)
- Composer install avec `--optimize-autoloader`

## ⚠️ Points d'attention API ↔ Flutter

### 1. Isolation opérations (depuis Oct 2025)

**Avant** : `ope_pass.fk_user` → `users.id` (table centrale)
**Après** : `ope_pass.fk_user` → `ope_users.id` (pivot opération)

**Impact Flutter** :
- Login retourne **2 IDs** : `id` (users.id) + `ope_user_id` (ope_users.id)
- Création secteur/passage : envoyer `ope_user_id`
- Affichage passages : mapper avec `ope_user_id`

### 2. Endpoints critiques modifiés

| Endpoint | Body envoyé | Mapping |
|----------|-------------|---------|
| `POST /api/sectors` | `users: [50, 51]` | ope_users.id |
| `PUT /api/sectors/{id}` | `users: [50, 51]` | ope_users.id |
| `POST /api/passages` | `fk_user: 50` | ope_users.id |
| `PUT /api/passages/{id}` | `fk_user: 50` | ope_users.id |
| `POST /api/users` | - | Retourne `ope_user_id` |

### 3. Requête SQL typique

```sql
-- ❌ AVANT (CASSÉ)
SELECT op.*, u.encrypted_name
FROM ope_pass op
JOIN users u ON op.fk_user = u.id

-- ✅ APRÈS (CORRECT)
SELECT op.*, u.encrypted_name
FROM ope_pass op
JOIN ope_users ou ON op.fk_user = ou.id
JOIN users u ON ou.fk_user = u.id
```

### 4. Suppression en cascade

```sql
DELETE FROM operations WHERE id = 850;
-- Supprime automatiquement (CASCADE) :
-- - ope_users
-- - ope_users_sectors
-- - ope_pass
-- - ope_sectors
-- ✅ users conservé (table centrale)
```

## 📝 Changelog critique

**Version 3.3.7 (26 Oct 2025)** :
- 🔧 Correction bug `SectorController::update()` : Recherche users par `ope_users.id` au lieu de `users.id`
- 🔧 Permissions logs corrigées : `nginx:nginx` + `750/640`
- 🔧 PHP `display_errors = Off` (warnings loggés dans `/var/log/php83/error.log`)

**Version 3.3.6 (21 Oct 2025)** :
- Validation inscription : Code postal + ville (doublon)

**Version 3.2.7 (16 Oct 2025)** :
- Migration RCA-GEO vers maria3 complétée
- URL PROD : `app3.geosector.fr`

**Version 3.2.4-3.2.6 (Sep 2025)** :
- Stripe Connect complet (Tap to Pay, webhooks multi-env)

---

**Mis à jour : 22 Décembre 2025**
