chore: readme
This commit is contained in:
@@ -1,26 +1,26 @@
|
||||
# 📚 Documentation API - Plateforme de Gestion de Commandes
|
||||
|
||||
**Version:** 5.4.0
|
||||
**Date:** 2026-06-11
|
||||
**Version:** 5.2.0
|
||||
**Date:** 2026-05-01
|
||||
**Base URL prod:** `https://mln-uber.club` (HTTPS via WAF nginx + ModSecurity)
|
||||
**Base URL dev:** `http://localhost:8080`
|
||||
**Technologies:** Go 1.24, Gin, PostgreSQL 16, Redis 7, React 19 + Vite, Expo 54 (React Native), TomTom API
|
||||
|
||||
---
|
||||
|
||||
## 📋 Table des Matières
|
||||
|
||||
1. [Vue d'ensemble](#vue-densemble)
|
||||
2. [Infrastructure Serveurs](#-infrastructure-serveurs)
|
||||
3. [Déploiement Production](#-déploiement-production)
|
||||
4. [Authentication](#authentication)
|
||||
5. [API Client (v1)](#api-client-v1)
|
||||
6. [API Admin (v2)](#api-admin-v2)
|
||||
7. [API Cabine (v1)](#api-cabine-v1)
|
||||
8. [API Livreur (v1)](#api-livreur-v1)
|
||||
9. [Notifications Push & Telegram](#-notifications-push--telegram)
|
||||
10. [Paiements Crypto](#-paiements-crypto)
|
||||
11. [Systeme GPS Integre](#-systeme-gps-integre)
|
||||
12. [Codes d'Erreur](#codes-derreur)
|
||||
2. [Déploiement Production](#-déploiement-production)
|
||||
3. [Authentication](#authentication)
|
||||
4. [API Client (v1)](#api-client-v1)
|
||||
5. [API Admin (v2)](#api-admin-v2)
|
||||
6. [API Cabine (v1)](#api-cabine-v1)
|
||||
7. [API Livreur (v1)](#api-livreur-v1)
|
||||
8. [Notifications Push & Telegram](#-notifications-push--telegram)
|
||||
9. [Paiements Crypto](#-paiements-crypto)
|
||||
10. [Systeme GPS Integre](#-systeme-gps-integre)
|
||||
11. [Codes d'Erreur](#codes-derreur)
|
||||
|
||||
---
|
||||
|
||||
@@ -35,14 +35,12 @@ Cette API REST complète gère une plateforme de livraison avec assignation auto
|
||||
- **📍 Suivi temps réel** avec ETA et géolocalisation
|
||||
- **🔄 Système de queues** Redis optimisé pour les livraisons
|
||||
- **⚡ Workers automatiques** (nettoyage, assignation, notifications)
|
||||
- **🎯 Système de pénalités clients** — amendes progressives sur annulations (livreurs non concernés)
|
||||
- **🤖 Notifications Telegram** pour clients, livreurs et admins (push Expo abandonné)
|
||||
- **🎯 Système de pénalités** pour la gestion des comportements
|
||||
- **🔔 Notifications push** Expo (iOS/Android) pour clients et livreurs
|
||||
- **🤖 Intégration Telegram** pour alertes et notifications admin/livreur
|
||||
- **💸 Paiements crypto** via NowPayments (webhook HMAC)
|
||||
- **🛡️ WAF nginx + ModSecurity** (OWASP CRS) en production
|
||||
- **📱 Applications mobiles** Expo 54 (client + admin/livreur)
|
||||
- **🏷️ Prix par quantité activables/désactivables** — visibilité client filtrée automatiquement
|
||||
- **⏱️ Bouton "Client absent"** — timer 5 min au statut `arrived`, amende automatique appliquée au **client** si absent
|
||||
- **📲 OTA updates** canaux nommés par rôle (`pre-prod-client`, `production-client`, `pre-prod-admin`, etc.)
|
||||
|
||||
### 🏗️ Stack Technique
|
||||
|
||||
@@ -242,7 +240,7 @@ graph TD
|
||||
P3["GET /api/v1/products/category/:category"]
|
||||
P4["GET /api/v1/categories"]
|
||||
P5["GET /api/v1/app-settings"]
|
||||
P6["POST /api/v1/webhooks/nowpayments"]
|
||||
P6["POST /api/v1/webhook/nowpayments"]
|
||||
P7["POST /webhook/telegram"]
|
||||
end
|
||||
|
||||
@@ -364,7 +362,6 @@ sequenceDiagram
|
||||
participant API as Backend API
|
||||
participant DB as PostgreSQL
|
||||
participant R as Redis
|
||||
participant TG as Telegram
|
||||
|
||||
rect rgb(200, 230, 200)
|
||||
Note over U,R: Registration
|
||||
@@ -377,36 +374,15 @@ sequenceDiagram
|
||||
end
|
||||
|
||||
rect rgb(200, 200, 230)
|
||||
Note over U,TG: Login Standard (2FA desactivee)
|
||||
Note over U,R: Login
|
||||
U->>F: Entrer credentials
|
||||
F->>API: POST /auth/login
|
||||
API->>DB: Verifier credentials
|
||||
DB-->>API: User valide
|
||||
API->>API: Generer JWT
|
||||
API->>R: Stocker session
|
||||
API-->>F: 200 OK + { access_token, user }
|
||||
F->>F: Stocker token
|
||||
F-->>U: Connecte
|
||||
end
|
||||
|
||||
rect rgb(255, 240, 200)
|
||||
Note over U,TG: Login avec 2FA (Telegram active)
|
||||
U->>F: Entrer credentials
|
||||
F->>API: POST /auth/login
|
||||
API->>DB: Verifier credentials + verifier 2FA active
|
||||
DB-->>API: User valide, 2FA requise
|
||||
API->>R: Stocker session_token (TTL 5min)
|
||||
API->>TG: Envoyer code 6 chiffres via bot Telegram
|
||||
API-->>F: 200 OK + { requires_2fa: true, session_token }
|
||||
F-->>U: Afficher saisie du code Telegram
|
||||
U->>F: Entrer code recu sur Telegram
|
||||
F->>API: POST /auth/2fa/verify { session_token, code }
|
||||
API->>R: Verifier code + session_token
|
||||
R-->>API: Code valide
|
||||
API->>API: Generer JWT
|
||||
API->>R: Stocker session
|
||||
API-->>F: 200 OK + { access_token, user }
|
||||
F->>F: Stocker token
|
||||
API-->>F: 200 OK + JWT Token
|
||||
F->>F: Stocker token (localStorage)
|
||||
F-->>U: Connecte
|
||||
end
|
||||
|
||||
@@ -556,7 +532,6 @@ erDiagram
|
||||
int product_id FK
|
||||
int quantity
|
||||
float price
|
||||
boolean active_price
|
||||
}
|
||||
|
||||
PRODUCT_MEDIA {
|
||||
@@ -641,10 +616,6 @@ graph LR
|
||||
C3["product:cache:{id}<br/>TTL: 1h"]
|
||||
end
|
||||
|
||||
subgraph Notifs["🔔 Notifications"]
|
||||
N1["notifications:{username}<br/>TTL: 1h"]
|
||||
end
|
||||
|
||||
subgraph PubSub["�� Pub/Sub Channels"]
|
||||
PS1["channel:position_updates"]
|
||||
PS2["channel:order_status"]
|
||||
@@ -656,95 +627,11 @@ graph LR
|
||||
REDIS --- Positions
|
||||
REDIS --- Queues
|
||||
REDIS --- Cache
|
||||
REDIS --- Notifs
|
||||
REDIS --- PubSub
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Infrastructure Serveurs
|
||||
|
||||
### Réseau VPN (WireGuard)
|
||||
|
||||
Tous les serveurs backend communiquent via un réseau WireGuard privé `10.0.0.0/24`. Le SSH est restreint à l'IP VPN uniquement sur les serveurs sensibles — il faut être connecté au VPN pour s'y connecter.
|
||||
|
||||
| Serveur | IP Publique | IP VPN | Rôle |
|
||||
|---------|-------------|--------|------|
|
||||
| **vpn-uber** | `45.150.111.158` | `10.0.0.1` | Serveur WireGuard — point d'entrée VPN et jump host SSH |
|
||||
| **monitoring-uber** | `185.103.167.138` | `10.0.0.2` | Wazuh · Dozzle · Beszel hub · SSH via VPN uniquement |
|
||||
| **backup-mln** | `85.121.176.241` | `10.0.0.4` | MinIO S3 · ClamAV · Beszel agent · SSH via VPN uniquement |
|
||||
| **bdd-redis-prod** | `132.243.162.62` | `10.0.0.5` | PostgreSQL 16 · Redis 7 · Beszel agent · SSH via VPN uniquement |
|
||||
| **prod-uber** | `185.103.166.119` | `10.0.0.6` | Backend Go + WAF nginx · accessible publiquement sur 80/443 |
|
||||
| **pre-prod-uber** | `185.103.166.112` | — | Environnement de pré-production |
|
||||
| **s3-uber** | `80.96.58.164` | — | Stockage S3 externe · Dozzle agent |
|
||||
|
||||
### Architecture DMZ / LAN
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
Internet((Internet)) -->|"80/443 public"| Prod["prod-uber — DMZ<br/>185.103.166.119"]
|
||||
Prod -->|"VPN (10.0.0.6)"| BDD["bdd-redis-prod — LAN<br/>10.0.0.5"]
|
||||
BDD --> PG["PostgreSQL :5432"]
|
||||
BDD --> Redis["Redis :6379"]
|
||||
|
||||
VPN["vpn-uber — 10.0.0.1<br/>jump host SSH"] -.->|"accès admin uniquement"| BDD
|
||||
VPN -.-> Prod
|
||||
|
||||
style Internet fill:#eee,stroke:#999
|
||||
style Prod fill:#fde68a,stroke:#b45309
|
||||
style BDD fill:#bbf7d0,stroke:#15803d
|
||||
style VPN fill:#bfdbfe,stroke:#1d4ed8
|
||||
```
|
||||
|
||||
L'application prod-uber se connecte à PostgreSQL et Redis via les IP VPN :
|
||||
- `DB_HOST=10.0.0.5` (PostgreSQL sur bdd-redis-prod)
|
||||
- `REDIS_HOST=10.0.0.5` (Redis sur bdd-redis-prod)
|
||||
|
||||
### Monitoring
|
||||
|
||||
Toutes les ressources monitoring sont accessibles via VPN (`10.0.0.2`) :
|
||||
|
||||
| Outil | URL | Description |
|
||||
|-------|-----|-------------|
|
||||
| **Wazuh** | `https://10.0.0.2` | SIEM — alertes sécurité, logs agents |
|
||||
| **Dozzle** | `https://10.0.0.2:8080` | Logs Docker de tous les serveurs en temps réel |
|
||||
| **Beszel** | `https://10.0.0.2:8090` | Métriques système (CPU, RAM, disque, réseau) |
|
||||
|
||||
Dozzle agrège les logs de : `pre-prod-uber`, `prod-uber`, `backup-mln (VPN)`, `s3-uber`.
|
||||
Beszel surveille : `monitoring-uber`, `backup-mln` (agent Docker port 10001), `bdd-redis-prod` (agent binaire systemd port 10001).
|
||||
|
||||
Nettoyage automatique des logs Wazuh : cron tous les dimanches à 3h00 sur monitoring-uber (`/usr/local/bin/clean-wazuh-logs.sh`).
|
||||
|
||||
### Sécurité réseau
|
||||
|
||||
- **UFW** activé sur monitoring-uber et backup-mln : SSH bloqué depuis IP publique, accessible uniquement via VPN
|
||||
- **UFW** activé sur bdd-redis-prod : SSH, PostgreSQL et Redis accessibles uniquement depuis le réseau VPN (`10.0.0.0/24`)
|
||||
- Ports Docker liés à l'IP VPN (`10.0.0.4:port:port`) pour ne pas bypasser UFW
|
||||
- **ClamAV** sur backup-mln : scan antivirus quotidien de `/mnt/data`
|
||||
|
||||
### Accès SSH aux serveurs VPN-only
|
||||
|
||||
```bash
|
||||
# Via le jump host vpn-uber
|
||||
ssh -J root@45.150.111.158 root@10.0.0.2 # monitoring-uber
|
||||
ssh -J root@45.150.111.158 root@10.0.0.4 # backup-mln
|
||||
ssh -J root@45.150.111.158 root@10.0.0.5 # bdd-redis-prod
|
||||
```
|
||||
|
||||
### Sauvegarde S3 (backup-mln)
|
||||
|
||||
MinIO S3 tourne sur backup-mln avec nginx SSL proxy :
|
||||
|
||||
| Endpoint | Adresse |
|
||||
|----------|---------|
|
||||
| API S3 | `https://10.0.0.4:9000` (via VPN) |
|
||||
| Console MinIO | `https://10.0.0.4:9001` (via VPN) |
|
||||
| Console nginx | `https://10.0.0.4:8080` (via VPN) |
|
||||
|
||||
Les données sont montées sur `/mnt/data`.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Déploiement Production
|
||||
|
||||
### Prérequis
|
||||
@@ -791,84 +678,53 @@ docker compose -f docker/docker-compose-prod.yml up -d --build
|
||||
docker compose -f docker/docker-compose-prod.yml logs -f waf
|
||||
```
|
||||
|
||||
### Services Docker (prod-uber)
|
||||
### Services Docker
|
||||
|
||||
| Service | Image | Rôle |
|
||||
|---------|-------|------|
|
||||
| `waf` | owasp/modsecurity-crs:nginx-alpine | Point d'entrée HTTPS (ports 80/443) |
|
||||
| `backend` | Go 1.24 alpine | API REST (port 8080 interne) |
|
||||
| `frontend` | nginx:alpine | SPA React (port 80 interne) |
|
||||
|
||||
> PostgreSQL et Redis **ne tournent plus sur prod-uber**. Ils sont hébergés sur le serveur dédié `bdd-redis-prod` (`10.0.0.5`) et accessibles via le VPN WireGuard. Voir la section [Infrastructure Serveurs](#-infrastructure-serveurs).
|
||||
| `postgres` | postgres:16-alpine | Base de données |
|
||||
| `redis` | redis:7-alpine | Cache + sessions + queues |
|
||||
|
||||
### Variables d'environnement requises
|
||||
|
||||
```bash
|
||||
# Base de données (bdd-redis-prod via VPN)
|
||||
DB_HOST=10.0.0.5 # IP VPN de bdd-redis-prod
|
||||
DB_PORT=5432
|
||||
DB_PASSWORD= # Mot de passe PostgreSQL
|
||||
|
||||
# Redis (bdd-redis-prod via VPN)
|
||||
REDIS_HOST=10.0.0.5 # IP VPN de bdd-redis-prod
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD= # Mot de passe Redis
|
||||
|
||||
# JWT
|
||||
USER_JWT_SECRET= # Secret JWT clients (min 32 chars)
|
||||
ADMIN_JWT_SECRET= # Secret JWT admin/livreur/cabine (min 32 chars)
|
||||
|
||||
# TomTom (rotation automatique entre les 3 clés)
|
||||
TOMTOM_API_KEY= # Clé TomTom principale / legacy
|
||||
TOMTOM_API_KEY_1= # Clé TomTom #1
|
||||
TOMTOM_API_KEY_2= # Clé TomTom #2
|
||||
TOMTOM_API_KEY_3= # Clé TomTom #3
|
||||
|
||||
# Divers
|
||||
SESSION_SECRET= # Secret sessions
|
||||
TELEGRAM_WEBHOOK_URL= # URL webhook Telegram
|
||||
TELEGRAM_WEBHOOK_SECRET= # Secret webhook Telegram
|
||||
NOWPAYMENTS_IPN_SECRET= # Secret IPN NowPayments
|
||||
|
||||
# Stockage médias produits (photos/vidéos)
|
||||
STORAGE_DRIVER=local # "local" (disque, défaut) ou "s3" (RustFS)
|
||||
# Requis uniquement si STORAGE_DRIVER=s3 :
|
||||
S3_REGION= # Région S3 (arbitraire pour RustFS, ex: us-east-1)
|
||||
S3_BUCKET= # Nom du bucket
|
||||
S3_ENDPOINT= # URL du endpoint S3-compatible (RustFS, IP VPN interne)
|
||||
RUSTFS_ACCESS_KEY= # Clé d'accès RustFS
|
||||
RUSTFS_SECRET_KEY= # Clé secrète RustFS
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Upload["CreateProduct /<br/>UploadMedia"] --> Check{"STORAGE_DRIVER"}
|
||||
Check -->|"local (défaut)"| Local["LocalStorage<br/>disque ./uploads"]
|
||||
Check -->|"s3"| S3["S3Storage<br/>RustFS (auto-hébergé)"]
|
||||
|
||||
Delete["DeleteProduct /<br/>DeleteMedia"] --> KeyCheck{"media.Key<br/>rempli ?"}
|
||||
KeyCheck -->|non| Local
|
||||
KeyCheck -->|oui| S3
|
||||
REDIS_PASSWORD= # Mot de passe Redis
|
||||
TOMTOM_API_KEY= # Clé API TomTom
|
||||
SESSION_SECRET= # Secret sessions
|
||||
TELEGRAM_WEBHOOK_URL= # URL webhook Telegram
|
||||
TELEGRAM_WEBHOOK_SECRET= # Secret webhook Telegram
|
||||
NOWPAYMENTS_IPN_SECRET= # Secret IPN NowPayments
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔔 Notifications
|
||||
## 🔔 Notifications Push & Telegram
|
||||
|
||||
Les notifications clients et livreurs sont gérées **exclusivement via Telegram** — les push notifications Expo (iOS/Android) ne sont plus utilisées.
|
||||
### Push Notifications (Expo)
|
||||
|
||||
### Notifications Telegram
|
||||
Le système utilise Expo Push Notifications pour envoyer des notifications aux applications mobiles (client et livreur). Les tokens Expo sont envoyés directement via l'API Expo depuis le backend — il n'y a pas d'endpoint REST dédié à l'enregistrement du push token.
|
||||
|
||||
Clients, livreurs et admins reçoivent leurs alertes via un bot Telegram lié à leur compte.
|
||||
**Flux :**
|
||||
1. L'app mobile obtient un `ExponentPushToken` via `expo-notifications`
|
||||
2. Le backend envoie les notifications via `sendExpoPush()` dans `db/db_notifications.go`
|
||||
3. Expo relay la notification vers le device cible (iOS/Android)
|
||||
|
||||
**Types de notifications envoyées :**
|
||||
- `assigned` — Commande assignée à un livreur
|
||||
- `en_route` — Livreur en route (avec ETA)
|
||||
- `arrived` — Livreur arrivé
|
||||
- `arrived` — Livreur arrivé (bouton "Le livreur est là")
|
||||
- `livre` — Commande livrée
|
||||
- `ready_pickup` — Cabine : "descendez chercher votre commande"
|
||||
- `ready_pickup` — Notification cabine "descendez chercher"
|
||||
- `address_proposal` — Proposition de changement d'adresse
|
||||
|
||||
### Notifications Telegram
|
||||
|
||||
Les admins et livreurs peuvent lier leur compte Telegram pour recevoir des alertes.
|
||||
|
||||
#### Générer un token de liaison (Admin)
|
||||
@@ -947,9 +803,50 @@ Authorization: Bearer <client_token>
|
||||
|
||||
## 🔐 Authentication
|
||||
|
||||
### Création de compte client
|
||||
### Register Client
|
||||
|
||||
Les comptes clients sont créés **uniquement par un administrateur** via `POST /api/v2/admin/protected/clients`. Il n'existe pas d'endpoint d'auto-inscription.
|
||||
**Créer un nouveau compte client**
|
||||
|
||||
```bash
|
||||
POST /api/v1/auth/register
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**Requête:**
|
||||
```json
|
||||
{
|
||||
"username": "jean_dupont",
|
||||
"password": "SecurePass123!",
|
||||
"nom": "Dupont",
|
||||
"prenom": "Jean",
|
||||
"telephone": "+33612345678"
|
||||
}
|
||||
```
|
||||
|
||||
**Réponse (201 Created):**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Client créé avec succès",
|
||||
"client": {
|
||||
"id": 42,
|
||||
"username": "jean_dupont",
|
||||
"nom": "Dupont",
|
||||
"prenom": "Jean",
|
||||
"telephone": "+33612345678",
|
||||
"command": 0,
|
||||
"point": 0,
|
||||
"points_zipette": 0,
|
||||
"amende": 0.0,
|
||||
"cancellations_count": 0,
|
||||
"created_at": "2025-01-18T14:30:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Erreurs possibles:**
|
||||
- `400` - Données invalides (validation échouée)
|
||||
- `409` - Username ou téléphone déjà utilisé
|
||||
|
||||
---
|
||||
|
||||
@@ -970,7 +867,7 @@ Content-Type: application/json
|
||||
}
|
||||
```
|
||||
|
||||
**Réponse standard (200 OK) — 2FA désactivée:**
|
||||
**Réponse (200 OK):**
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
@@ -983,122 +880,12 @@ Content-Type: application/json
|
||||
}
|
||||
```
|
||||
|
||||
**Réponse avec 2FA (200 OK) — quand 2FA activée par le client ET Telegram lié ET admin l'a activée:**
|
||||
```json
|
||||
{
|
||||
"requires_2fa": true,
|
||||
"session_token": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
> Un code à 6 chiffres est envoyé automatiquement sur le compte Telegram lié. Le `session_token` expire dans **5 minutes**.
|
||||
|
||||
**Erreurs possibles:**
|
||||
- `400` - Données manquantes
|
||||
- `401` - Identifiants incorrects
|
||||
|
||||
---
|
||||
|
||||
### Vérifier le code 2FA
|
||||
|
||||
**Valider le code Telegram reçu pour finaliser la connexion**
|
||||
|
||||
```bash
|
||||
POST /api/v1/auth/2fa/verify
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**Requête:**
|
||||
```json
|
||||
{
|
||||
"session_token": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"code": "483721"
|
||||
}
|
||||
```
|
||||
|
||||
**Réponse (200 OK):**
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 18000,
|
||||
"user": {
|
||||
"username": "jean_dupont",
|
||||
"role": "client"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Erreurs possibles:**
|
||||
- `400` - `session_token` ou `code` manquant
|
||||
- `401` - Code incorrect ou session expirée (TTL 5 min)
|
||||
- `429` - Trop de tentatives (rate limiting)
|
||||
|
||||
---
|
||||
|
||||
### Statut 2FA du compte
|
||||
|
||||
**Obtenir l'état de la 2FA pour le compte connecté**
|
||||
|
||||
```bash
|
||||
GET /api/v1/two-fa/status
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**Réponse (200 OK):**
|
||||
```json
|
||||
{
|
||||
"two_fa_enabled": true,
|
||||
"telegram_linked": true,
|
||||
"admin_2fa_enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
| Champ | Description |
|
||||
|-------|-------------|
|
||||
| `two_fa_enabled` | La 2FA est activée sur ce compte client |
|
||||
| `telegram_linked` | Un compte Telegram est lié (prérequis pour activer) |
|
||||
| `admin_2fa_enabled` | L'admin a activé la 2FA dans les paramètres globaux |
|
||||
|
||||
---
|
||||
|
||||
### Activer / Désactiver la 2FA
|
||||
|
||||
**Basculer l'état de la 2FA sur son propre compte**
|
||||
|
||||
```bash
|
||||
POST /api/v1/two-fa/toggle
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**Requête:**
|
||||
```json
|
||||
{
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
**Réponse (200 OK):**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"two_fa_enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
**Prérequis pour activer (`"enabled": true`):**
|
||||
1. Le client doit avoir lié son compte Telegram (via `/api/v1/auth/link-telegram`)
|
||||
2. L'administrateur doit avoir activé `telegram_2fa_enabled` dans les paramètres globaux
|
||||
|
||||
**Erreurs possibles:**
|
||||
- `400` - Telegram non lié (impossible d'activer sans compte Telegram)
|
||||
- `403` - La 2FA n'est pas autorisée par l'administrateur
|
||||
- `401` - Non authentifié
|
||||
|
||||
> **Interface utilisateur:** Sur le frontend web (page profil) et l'app mobile, un toggle permet d'activer/désactiver la 2FA directement depuis les paramètres du compte.
|
||||
|
||||
---
|
||||
|
||||
### Logout Client
|
||||
|
||||
**Se déconnecter (invalider le token)**
|
||||
@@ -1127,8 +914,6 @@ Authorization: Bearer <client_token>
|
||||
|
||||
### Produits (Public - Pas d'auth requis)
|
||||
|
||||
> **Filtrage des prix inactifs :** Chaque prix d'un produit porte un flag `active_price` (booléen). Les endpoints publics et client **n'exposent que les prix actifs** (`active_price = true`). Les rôles `admin` et `cabine` reçoivent tous les prix (actifs et inactifs) pour permettre la gestion complète. Côté frontend (web + mobile), les prix sont également filtrés au chargement (`filter(p => p.active_price !== false)`).
|
||||
|
||||
#### Liste des produits
|
||||
|
||||
```bash
|
||||
@@ -1152,8 +937,7 @@ GET /api/v1/products
|
||||
"id": 1,
|
||||
"product_id": 1,
|
||||
"quantity": 1,
|
||||
"price": 12.50,
|
||||
"active_price": true
|
||||
"price": 12.50
|
||||
}
|
||||
],
|
||||
"media": [
|
||||
@@ -1301,7 +1085,8 @@ Content-Type: application/json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Produit supprimé du panier avec succès",
|
||||
"item_id": 15
|
||||
"item_id": 15,
|
||||
"stock_released": true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1322,7 +1107,8 @@ Authorization: Bearer <token>
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Panier vidé avec succès"
|
||||
"message": "Panier vidé avec succès",
|
||||
"stock_released": 3
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1379,38 +1165,6 @@ Content-Type: application/json
|
||||
- `401` - Non authentifié
|
||||
- `500` - Erreur création commande
|
||||
|
||||
**Réponse — adresse connue pour être mal formée (400 Bad Request):**
|
||||
|
||||
Si l'adresse saisie correspond (match exact ou normalisé — accents/casse/espaces ignorés) à une entrée de la table de corrections gérée par l'admin/cabine (`POST/DELETE /addresses`), le checkout est volontairement bloqué pour forcer une reconfirmation du client plutôt que d'appliquer la correction en silence :
|
||||
```json
|
||||
{
|
||||
"error": "Adresse non reconnue",
|
||||
"corrected_address": "15 Rue de la Paix, 75002 Paris, France"
|
||||
}
|
||||
```
|
||||
Le client doit renvoyer la requête avec `delivery_address` = `corrected_address` pour valider le checkout.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as Client
|
||||
participant API as API (ValidateBasket)
|
||||
participant DB as adresse_correction
|
||||
|
||||
C->>API: POST /checkout {delivery_address}
|
||||
API->>DB: CheckAddress(delivery_address)
|
||||
DB->>DB: Match exact, sinon fallback normalisé<br/>(accents/casse/espaces)
|
||||
alt Correction trouvée
|
||||
DB-->>API: corrected_address
|
||||
API-->>C: 400 {error, corrected_address}
|
||||
C->>C: Affiche la suggestion (modal)
|
||||
C->>API: POST /checkout {delivery_address: corrected_address}
|
||||
API-->>C: 200 Commande créée
|
||||
else Aucune correction connue
|
||||
DB-->>API: nil
|
||||
API-->>C: 200 Commande créée
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Mes commandes avec suivi
|
||||
@@ -1963,34 +1717,9 @@ Content-Type: application/json
|
||||
|
||||
---
|
||||
|
||||
### Système de Pénalités (Clients uniquement)
|
||||
### Système de Pénalités
|
||||
|
||||
Les amendes s'appliquent **uniquement aux clients**. Les livreurs n'ont pas d'amende.
|
||||
|
||||
Les amendes sont stockées dans `clients.amende` (PostgreSQL). Tant que `amende > 0`, le middleware `BlockClientIfPenalty` bloque toute tentative de checkout.
|
||||
|
||||
**Échelle progressive (annulations) :**
|
||||
| Nbre d'annulations | Amende |
|
||||
|---|---|
|
||||
| 1ère | 20 € |
|
||||
| 2ème | 50 € |
|
||||
| 3ème | 100 € |
|
||||
| 4ème et + | 150 € |
|
||||
|
||||
**Sources d'amende :**
|
||||
- Client annule sa propre commande → `ApplyCancellationPenalty` (incrémente `cancellations_count`)
|
||||
- Livreur marque le client absent depuis le statut `arrived` → amende appliquée automatiquement sur le **client**
|
||||
|
||||
**Message d'erreur au checkout bloqué :**
|
||||
```json
|
||||
{
|
||||
"error": "Commande bloquée : vous avez une amende de 20€ en attente de paiement. Prenez attache avec Milieu Nantais sur signal pour régulariser votre situation..",
|
||||
"amende": 20.0,
|
||||
"blocked": true
|
||||
}
|
||||
```
|
||||
|
||||
#### Appliquer une pénalité (Admin)
|
||||
#### Appliquer une pénalité
|
||||
|
||||
```bash
|
||||
POST /api/v2/admin/protected/penalty
|
||||
@@ -2272,22 +2001,6 @@ Content-Type: application/json
|
||||
|
||||
> **Note :** Le passage à `livre` via ce endpoint requiert des coordonnées GPS valides pour la validation de proximité.
|
||||
|
||||
> **Amende automatique :** si le livreur annule une commande dont le statut courant est `arrived`, une amende progressive est automatiquement appliquée au client (`ApplyCancellationPenalty`) — échelle : 1ère=20€, 2ème=50€, 3ème=100€, 4ème+=150€. Le livreur peut déclencher cette annulation via le bouton **"Client absent"** (cf. section ci-dessous).
|
||||
|
||||
---
|
||||
|
||||
### Bouton "Client absent" (DashboardScreen)
|
||||
|
||||
Lorsque le statut d'une livraison passe à `arrived`, un timer de **5 minutes** démarre automatiquement dans l'app livreur (`frontend-admin`). Pendant ce délai, un compte à rebours est affiché sur la carte de livraison. Une fois les 5 minutes écoulées, le bouton **"Client absent"** apparaît.
|
||||
|
||||
**Comportement :**
|
||||
- Appui sur "Client absent" → annulation de la commande avec la raison `client_absent`
|
||||
- L'API reçoit `status: cancelled` + `issue_type: client_absent`
|
||||
- Le backend applique automatiquement `ApplyCancellationPenalty(clientUsername)` (amende progressive)
|
||||
- Le timer est stocké dans un `useRef` (pas de re-render) et comparé à `Date.now()` toutes les secondes
|
||||
|
||||
**Implémentation :** `frontend-admin/src/screens/delivery/DashboardScreen.tsx` — constante `ABSENT_TIMEOUT_SECS = 300`, ref `arrivedAtRef`, état `elapsedSeconds`.
|
||||
|
||||
---
|
||||
|
||||
### Position GPS
|
||||
@@ -2580,26 +2293,23 @@ L'ETA est calcule en utilisant l'API TomTom qui prend en compte:
|
||||
- Les travaux
|
||||
- L'heure de la journee
|
||||
|
||||
**Rotation automatique des clés :** jusqu'à 3 clés TomTom peuvent être configurées (`TOMTOM_API_KEY`, `TOMTOM_API_KEY_1`, `TOMTOM_API_KEY_2`, `TOMTOM_API_KEY_3`). En cas de quota dépassé (HTTP 403/429), le système passe automatiquement à la clé suivante sans interruption de service.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Demande ETA] --> B{Clés TomTom configurées?}
|
||||
A[Demande ETA] --> B{TomTom API disponible?}
|
||||
B -->|Oui| C[Appel TomTom Routing API]
|
||||
C --> D[ETA avec trafic reel]
|
||||
B -->|Non| E[Calcul Haversine]
|
||||
B -->|Oui| C[Appel TomTom — clé active]
|
||||
C --> Q{Quota dépassé 403/429?}
|
||||
Q -->|Oui| R{Clé suivante disponible?}
|
||||
R -->|Oui| C
|
||||
R -->|Non| E
|
||||
Q -->|Non| D[ETA avec trafic réel]
|
||||
E --> F[Distance à vol d'oiseau]
|
||||
E --> F[Distance a vol d'oiseau]
|
||||
F --> G[Vitesse moyenne 30 km/h]
|
||||
G --> H[ETA estimé]
|
||||
G --> H[ETA estime]
|
||||
D --> I[Retourner ETA]
|
||||
H --> I
|
||||
I --> J{Fallback utilise?}
|
||||
J -->|Oui| K[Ajouter flag fallback_used: true]
|
||||
J -->|Non| L[Response standard]
|
||||
```
|
||||
|
||||
**Fallback:** Si toutes les clés TomTom sont épuisées ou indisponibles, le système utilise un calcul local base sur:
|
||||
**Fallback:** Si l'API TomTom est indisponible, le systeme utilise un calcul local base sur:
|
||||
- Distance Haversine
|
||||
- Vitesse moyenne estimee (30 km/h en ville)
|
||||
|
||||
@@ -2986,8 +2696,6 @@ stateDiagram-v2
|
||||
- Livreur PUT status=arrived
|
||||
- Admin/Cabine bouton "Le livreur est la"
|
||||
Notifie le client (Telegram + push)
|
||||
Timer 5min → bouton "Client absent"
|
||||
Annulation depuis arrived → amende client
|
||||
end note
|
||||
|
||||
note right of en_route
|
||||
@@ -3008,9 +2716,9 @@ Si une adresse ne peut pas etre geocodee:
|
||||
- Un log d'erreur est genere
|
||||
- L'admin peut corriger l'adresse manuellement
|
||||
|
||||
#### API TomTom Indisponible / Quota dépassé
|
||||
#### API TomTom Indisponible
|
||||
|
||||
Le système tente d'abord toutes les clés disponibles en rotation, puis bascule sur le calcul local :
|
||||
Le systeme bascule automatiquement sur le calcul local:
|
||||
- Utilise la formule Haversine pour la distance
|
||||
- Estime l'ETA avec une vitesse moyenne de 30 km/h
|
||||
- Un flag `fallback_used: true` est ajoute a la reponse
|
||||
@@ -3084,15 +2792,6 @@ Le système tente d'abord toutes les clés disponibles en rotation, puis bascule
|
||||
}
|
||||
```
|
||||
|
||||
#### 403 - Checkout bloqué (amende en attente)
|
||||
```json
|
||||
{
|
||||
"error": "Commande bloquée : vous avez une amende de 20€ en attente de paiement. Prenez attache avec Milieu Nantais sur signal pour régulariser votre situation..",
|
||||
"amende": 20.0,
|
||||
"blocked": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 404 - Not Found
|
||||
```json
|
||||
{
|
||||
@@ -3159,64 +2858,8 @@ Le système tente d'abord toutes les clés disponibles en rotation, puis bascule
|
||||
|
||||
---
|
||||
|
||||
## 📋 Changelog
|
||||
|
||||
### v5.7.0 — 2026-08-04
|
||||
|
||||
- **Nouveau — choix du backend de stockage médias (`STORAGE_DRIVER`)** : les photos/vidéos produits pouvaient être stockées soit sur disque local (`CreateProduct`) soit sur S3/RustFS (`UploadMedia`) selon l'endpoint utilisé, sans logique commune. Une interface `Storage` unifiée (`services/storage.go`, implémentations `LocalStorage`/`S3Storage`) est maintenant utilisée par les deux endpoints, pilotée par la variable d'environnement `STORAGE_DRIVER` (`local` par défaut, ou `s3`).
|
||||
- **Fix — nettoyage croisé des médias à la suppression** : `DeleteProduct` supprimait toujours sur disque (même pour un média stocké sur S3) et `DeleteMedia` supprimait toujours sur S3 (même pour un média local), laissant systématiquement des fichiers orphelins sur l'autre backend. Les deux fonctions branchent désormais sur `media.Key` (rempli uniquement pour les médias S3) pour cibler le bon backend, indépendamment du `STORAGE_DRIVER` courant.
|
||||
- **Fix — correction d'adresse jamais appliquée au checkout (`CheckAddress`)** : le mécanisme de correction d'adresse (table `adresse_correction`, gérée par l'admin/cabine) détectait bien une adresse connue pour être mal formée, mais le checkout était systématiquement abandonné au lieu de proposer la correction de façon exploitable — le contrat de réponse (`corrected_address`) n'était lu nulle part côté client (mobile : parsing d'un format de message d'erreur obsolète ; web : aucune gestion de ce cas). Mobile et web lisent désormais directement `corrected_address` et proposent au client de reprendre le checkout avec l'adresse corrigée.
|
||||
- **Fix — matching de correction d'adresse trop strict** : `CheckAddress` ne matchait qu'une égalité exacte de texte. Ajout d'un fallback normalisé (accents/casse/espaces ignorés, réutilise `utils.NormalizeAddress` déjà utilisée par le service de géocodage) pour rattraper les variantes mineures de saisie.
|
||||
|
||||
### v5.6.0 — 2026-07-11
|
||||
|
||||
- **Fix — ETA introuvable pour l'annulation tardive (`CheckCommandETAExistsAndValid`)** : la fonction lisait la clé Redis `command:eta:{id}` (un *hash*) avec `Redis.Get` (string), ce qui provoquait systématiquement une erreur `WRONGTYPE` silencieuse. Une ETA valide n'était donc jamais détectée par ce chemin, et un client pouvait annuler sans pénalité juste après l'assignation d'un livreur (avant le passage au statut `en_route`). Corrigé en `Redis.HGetAll`.
|
||||
- **Fix — position livreur introuvable (`GetDeliverymanLocationForCommand`)** : même bug `WRONGTYPE` (`Redis.Get` sur un hash) empêchant l'affichage de la position temps réel sur certains suivis de commande. Corrigé en `Redis.HGetAll`.
|
||||
- **Fix — ETA absente des notifications/app mobile/site web** : trois fonctions qui écrivent l'ETA dans Redis utilisaient des noms de champ incohérents (`eta_minutes` vs `total_eta_minutes`) alors que les clients ne lisent que `eta_minutes`. Les trois writers écrivent désormais les deux champs de façon cohérente.
|
||||
- **Fix — boucle infinie de géocodage (`ResolveAddress` ↔ `GeocodeAddress`)** : la correction d'adresse mal écrite et le géocodage se rappelaient mutuellement sans condition de sortie pour toute adresse échouant au géocodage direct (le cas d'usage même de la correction), provoquant un blocage. `ResolveAddress` appelle désormais directement le cache/Nominatim sans repasser par `GeocodeAddress`.
|
||||
- **Fix — récompenses par points non atomiques** : `ClaimPoolReward` (consommation des points) et `AddRewardsToBasket` (ajout du produit au panier) étaient deux étapes séparées ; un produit récompense supprimé/introuvable faisait perdre la récompense au client sans qu'il reçoive rien. Fusionnées dans `ClaimPoolRewardAndAddToBasket`, exécutée dans une seule transaction.
|
||||
- **Fix — annulation admin non atomique** : `UpdateCommandStatusAdmin` pouvait rembourser deux fois le stock en cas d'appels concurrents. Bascule sur `CancelCommandByAdminAtomic` (transaction + verrou `FOR UPDATE`).
|
||||
- **Fix — proratisation du chiffre d'affaires (stats)** : les commandes avec `referral_used` n'étaient pas correctement proratisées dans les statistiques de revenu.
|
||||
- **Notifications — durée de rétention réduite à 1h** : les notifications stockées dans Redis passent d'un TTL de 7 jours à 1 heure (cohérent avec leur usage temps réel, évite l'accumulation inutile).
|
||||
- **Suppression de code mort supplémentaire** : nettoyage dans `CreateCommand` et `DeleteCommandItem` (rendu atomique).
|
||||
- **Tests unitaires** : ajout de suites complètes couvrant la gestion de stock (checkout/annulation/items), les statistiques, le géocodage et la correction d'adresses mal écrites (algorithme pur + intégration réseau réelle rate-limitée), les calculs de temps/ETA de commande, les récompenses par points (déduction de stock, atomicité), et l'annulation de commande avec pénalité de retard (barème, cumul concurrentiel, détection via statut ou ETA, flux "client absent").
|
||||
|
||||
### v5.5.0 — 2026-07-08
|
||||
|
||||
- **Fix — amende annulation livreur (`ApplyCancellationPenalty`)** : l'amende appliquée quand un livreur marque le client absent écrasait le montant existant au lieu de l'additionner, et n'était pas protégée par un verrou (`FOR UPDATE`). Elle est désormais cumulative et transactionnelle, cohérente avec le chemin d'annulation client (`CancelAtomic`).
|
||||
- **Fix — mot de passe loggé en clair** : la modification d'un client par un admin (`PUT /admin/protected/clients/:id`) journalisait le corps de requête complet, y compris le nouveau mot de passe. Le log ne contient plus de donnée sensible.
|
||||
- **Fix — IDOR consultation d'alerte police** : un livreur pouvait consulter le détail de l'alerte d'un autre livreur en devinant l'ID (`GET /api/v1/livreur/alert/:id`). L'accès est désormais restreint à ses propres alertes ; admin et cabine conservent l'accès complet.
|
||||
- **Nettoyage — code mort** : suppression des handlers et fonctions utilitaires non routés/non appelés (ancien module `handlers/cabine.go`, `RegisterClient`, `GetCurrentClient`, `GetCurrentAdmin`, `GetMyCompletedOrders`, `GetRealtimeStats`, `StartPaymentChecker`, et helpers internes associés), identifiés via `staticcheck` et `deadcode`.
|
||||
|
||||
### v5.4.0 — 2026-05-18
|
||||
|
||||
- **Rotation automatique des clés TomTom** : jusqu'à 3 clés configurables (`TOMTOM_API_KEY_1/2/3`). En cas de quota dépassé (403/429), le système passe à la clé suivante automatiquement sans interruption. Fallback Haversine si toutes les clés sont épuisées.
|
||||
- **Sécurité — création d'utilisateurs** : seul un `admin` peut créer des comptes `livreur` ou `cabine` via l'API. La création de compte `admin` est entièrement bloquée via l'application — uniquement possible en base de données directement.
|
||||
- **Sécurité — cabine** : le rôle `cabine` n'a plus aucun droit de création d'utilisateurs ou de clients (retiré côté backend).
|
||||
- **Fix frontend** : `createUserByAdmin` appelait `/admin/auth/register` (inexistant) → corrigé vers `/admin/protected/users`.
|
||||
- **WAF ModSecurity logs** : les logs nginx et l'audit log ModSecurity sont désormais montés sur l'hôte (`/var/log/waf/nginx/` et `/var/log/waf/modsec/`) via volumes Docker stables. La variable `MODSEC_AUDIT_LOG` redirige l'audit log vers un fichier (au lieu de stdout) pour collecte Wazuh.
|
||||
- **Notifications Telegram uniquement** : les push notifications Expo (iOS/Android) sont abandonnées. Clients et livreurs reçoivent désormais toutes leurs alertes via Telegram.
|
||||
|
||||
### v5.3.0 — 2026-05-15
|
||||
|
||||
- **Prix inactifs filtrés côté client** : le flag `active_price` sur `PRODUCT_PRICES` permet de désactiver un tarif sans le supprimer. Les endpoints publics/client et les frontends web+mobile masquent automatiquement les prix inactifs. Admin et cabine voient tous les prix.
|
||||
- **Désactivation au lieu de suppression** : dans la modal d'édition produit (admin), retirer un prix existant le désactive (`active_price = false`) plutôt que de le supprimer de la base.
|
||||
- **Amende automatique client — livreur annule depuis `arrived`** : quand le livreur marque le client absent (`arrived` → `cancelled`), `ApplyCancellationPenalty` est appelé automatiquement sur le **client** (amende progressive : 20→50→100→150€).
|
||||
- **Bouton "Client absent"** : après 5 minutes au statut `arrived`, l'app livreur affiche un bouton "Client absent" qui déclenche l'annulation avec amende sur le client.
|
||||
- **Message d'erreur checkout avec contact** : le message de blocage inclut désormais "Prenez attache avec Milieu Nantais sur signal pour régulariser votre situation."
|
||||
- **OTA channels par rôle** : les canaux Expo OTA sont désormais nommés `pre-prod-client` / `production-client` / `pre-prod-admin` / `production-admin` pour éviter les mises à jour croisées entre builds.
|
||||
|
||||
### v5.2.0 — 2026-05-01
|
||||
|
||||
- Activation/désactivation des prix par quantité (admin)
|
||||
- Gestion de stock améliorée
|
||||
- Refactoring handlers produits
|
||||
|
||||
---
|
||||
|
||||
**Documentation mise à jour le :** 2026-07-11
|
||||
**Version API :** 5.6.0
|
||||
**Documentation mise à jour le :** 2026-05-01
|
||||
**Version API :** 5.2.0
|
||||
**Technologies :** Go 1.24, Gin, PostgreSQL 16, Redis 7, React 19, Expo 54, TomTom API, ModSecurity WAF
|
||||
**Déploiement :** Docker Compose · Nginx + ModSecurity OWASP CRS · TLS 1.2/1.3
|
||||
**Base URL prod :** `https://mln-uber.club`
|
||||
**Infrastructure :** WireGuard VPN · Wazuh SIEM · Dozzle · Beszel · ClamAV · MinIO S3
|
||||
**Base URL prod :** `https://mln-uber.club`
|
||||
|
||||
Reference in New Issue
Block a user