2026-07-13 13:17:06 +02:00
2026-07-06 21:22:30 +02:00
2026-07-12 11:23:37 +02:00
2026-07-13 12:20:13 +02:00
2026-06-15 21:26:29 +02:00
2026-07-12 14:54:02 +02:00
2026-07-11 16:17:21 +02:00
2026-07-12 11:23:56 +02:00
2026-05-01 20:53:48 +02:00
2026-07-13 13:17:06 +02:00
2026-02-28 23:23:54 +01:00
2026-02-28 23:23:54 +01:00
2026-07-11 11:45:13 +02:00

📚 Documentation API - Plateforme de Gestion de Commandes

Version: 5.4.0
Date: 2026-06-11
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
  2. Infrastructure Serveurs
  3. Déploiement Production
  4. Authentication
  5. API Client (v1)
  6. API Admin (v2)
  7. API Cabine (v1)
  8. API Livreur (v1)
  9. Notifications Push & Telegram
  10. Paiements Crypto
  11. Systeme GPS Integre
  12. Codes d'Erreur

🎯 Vue d'ensemble

Cette API REST complète gère une plateforme de livraison avec assignation automatique GPS des livreurs, suivi en temps réel, et système de pénalités avancé.

Fonctionnalités Principales

  • 🔐 Authentication JWT multi-rôles (Client, Admin, Livreur, Cabine) avec révocation de token
  • 🚗 Auto-assignation GPS des livreurs les plus proches (worker toutes les 1 minute)
  • 📍 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é)
  • 💸 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

Composant Technologie
Backend Go 1.24 + Gin · port 8080
Base de données PostgreSQL 16
Cache / Sessions / Queues Redis 7
Frontend web React 19 + TypeScript + Vite (frontend-prep/)
App mobile client Expo 54 + React Native (mobile/)
App mobile admin/livreur Expo 54 + React Native (frontend-admin/)
WAF / Reverse proxy Nginx + ModSecurity OWASP CRS
Géocodage Nominatim (OpenStreetMap)
Routing / ETA TomTom Routing API
Push notifications Expo Push Notifications
Paiements NowPayments (crypto)
Messagerie Telegram Bot API

🏗️ Architecture Globale du Systeme

graph TB
    subgraph Frontend["🖥️ Frontend (React/TypeScript)"]
        UI_CLIENT[Interface Client]
        UI_ADMIN[Interface Admin]
        UI_LIVREUR[Interface Livreur]
        UI_CABINE[Interface Cabine]
        TOMTOM_MAP[TomTomMap Component]
    end

    subgraph API_GATEWAY["🌐 API Gateway (Go/Gin)"]
        AUTH[Middleware Auth JWT]
        ROUTES[Router]
    end

    subgraph API_V1["📱 API v1"]
        CLIENT_API["/api/v1/client"]
        LIVREUR_API["/api/v1/livreur"]
        CABINE_API["/api/v1/cabine"]
        PUBLIC_API["/api/v1/public"]
    end

    subgraph API_V2["👨‍💼 API v2 Admin"]
        ADMIN_API["/api/v2/admin"]
    end

    subgraph HANDLERS["⚙️ Handlers"]
        H_AUTH[Auth Handler]
        H_PRODUCT[Product Handler]
        H_PANIER[Panier Handler]
        H_COMMAND[Command Handler]
        H_DELIVERY[Delivery Handler]
        H_GPS[GPS Handler]
        H_ETA[ETA Handler]
        H_PENALTY[Penalty Handler]
    end

    subgraph SERVICES["🔧 Services"]
        S_GEO[Geo Service]
        S_TOMTOM[TomTom Service]
        S_QUEUE[Queue Service]
        S_NOTIF[Notification Service]
    end

    subgraph EXTERNAL["🌍 APIs Externes"]
        NOMINATIM[Nominatim API]
        TOMTOM_API[TomTom API]
    end

    subgraph STORAGE["💾 Stockage"]
        POSTGRES[(PostgreSQL)]
        REDIS[(Redis)]
    end

    subgraph WORKERS["⚡ Workers"]
        W_ASSIGN[Auto-Assign Worker]
        W_CLEANUP[Cleanup Worker]
        W_ETA[ETA Update Worker]
    end

    %% Frontend connections
    UI_CLIENT --> AUTH
    UI_ADMIN --> AUTH
    UI_LIVREUR --> AUTH
    UI_CABINE --> AUTH
    TOMTOM_MAP --> TOMTOM_API

    %% API Gateway
    AUTH --> ROUTES
    ROUTES --> API_V1
    ROUTES --> API_V2

    %% API to Handlers
    CLIENT_API --> H_AUTH
    CLIENT_API --> H_PANIER
    CLIENT_API --> H_COMMAND
    LIVREUR_API --> H_DELIVERY
    LIVREUR_API --> H_GPS
    CABINE_API --> H_COMMAND
    PUBLIC_API --> H_PRODUCT
    ADMIN_API --> H_COMMAND
    ADMIN_API --> H_DELIVERY
    ADMIN_API --> H_GPS
    ADMIN_API --> H_PENALTY

    %% Handlers to Services
    H_GPS --> S_GEO
    H_GPS --> S_TOMTOM
    H_ETA --> S_TOMTOM
    H_DELIVERY --> S_QUEUE
    H_COMMAND --> S_NOTIF

    %% Services to External
    S_GEO --> NOMINATIM
    S_TOMTOM --> TOMTOM_API

    %% Services to Storage
    S_GEO --> REDIS
    S_QUEUE --> REDIS
    H_COMMAND --> POSTGRES
    H_PRODUCT --> POSTGRES
    H_DELIVERY --> POSTGRES

    %% Workers
    W_ASSIGN --> S_GEO
    W_ASSIGN --> S_QUEUE
    W_ASSIGN --> POSTGRES
    W_CLEANUP --> REDIS
    W_CLEANUP --> POSTGRES
    W_ETA --> S_TOMTOM
    W_ETA --> REDIS

🔄 Flux des Donnees par Role

flowchart LR
    subgraph Client["👤 Client"]
        C1[Consulter Produits]
        C2[Gerer Panier + Stock auto]
        C3[Passer Commande]
        C4[Suivre Livraison + ETA]
        C5[Approuver/Annuler]
        C6[Notifications Push]
        C7[Solde Parrainage]
    end

    subgraph Admin["👨‍💼 Admin"]
        A1[Gerer Produits/Clients]
        A2[Voir/Modifier Commandes]
        A3[Assigner Livreurs GPS]
        A4[Gerer Penalites]
        A5[Surveiller GPS + Queues]
        A6[Parametres Globaux]
    end

    subgraph Livreur["🚚 Livreur"]
        L1[Voir Mes Livraisons]
        L2[Mettre a Jour Position GPS]
        L3[Changer Statut]
        L4[Voir Ma Queue]
        L5[Alertes Police]
        L6[Notifications Push]
    end

    subgraph Cabine["🏭 Cabine/Cuisine"]
        K1[Voir Articles Commande]
        K2[Preparer/Marquer Pret]
        K3[Assigner Livreur]
        K4[Gerer Penalites Client]
        K5[Voir Alertes]
    end

    subgraph Backend["⚙️ Backend"]
        API[API REST]
        GPS[Service GPS]
        QUEUE[Gestion Queue]
    end

    C1 & C2 & C3 & C4 & C5 & C6 & C7 --> API
    A1 & A2 & A3 & A4 & A5 & A6 --> API
    L1 & L2 & L3 & L4 & L5 & L6 --> API
    K1 & K2 & K3 & K4 & K5 --> API
    API --> GPS
    API --> QUEUE

📡 Architecture des Endpoints API

graph TD
    subgraph PUBLIC["🔓 Endpoints Publics (sans auth)"]
        P1["GET /api/v1/products"]
        P2["GET /api/v1/products/:id"]
        P3["GET /api/v1/products/category/:category"]
        P4["GET /api/v1/categories"]
        P5["GET /api/v1/app-settings"]
        P6["POST /api/v1/webhooks/nowpayments"]
        P7["POST /webhook/telegram"]
    end

    subgraph AUTH_PUBLIC["🔑 Auth Publique (rate-limited)"]
        AP1["POST /api/v1/auth/login"]
        AP2["POST /api/v1/auth/logout"]
        AP3["PUT /api/v1/auth/change-password (JWT)"]
    end

    subgraph CLIENT_AUTH["🔐 Client (JWT Required)"]
        C1["POST /api/v1/panier/add"]
        C2["GET /api/v1/panier/:username"]
        C3["DELETE /api/v1/panier/remove"]
        C4["DELETE /api/v1/panier/clear"]
        C5["POST /api/v1/checkout"]
        C6["GET /api/v1/my-commands"]
        C7["GET /api/v1/my-commands/history"]
        C8["GET /api/v1/my-commands/history/detailed"]
        C9["GET /api/v1/commands/:id"]
        C10["GET /api/v1/commands/:id/status"]
        C11["GET /api/v1/commands/:id/tracking"]
        C12["GET /api/v1/commands/:id/eta"]
        C13["GET /api/v1/commands/:id/items"]
        C14["GET /api/v1/commands/:id/history"]
        C15["POST /api/v1/commands/:id/approve"]
        C16["POST /api/v1/commands/:id/cancel"]
        C17["POST /api/v1/commands/:id/address/respond"]
        C18["GET /api/v1/my-cancellation-history"]
        C19["GET /api/v1/penalties"]
        C20["GET /api/v1/notifications"]
        C21["POST /api/v1/notifications/read"]
        C22["GET /api/v1/profile"]
        C23["PUT /api/v1/profile/update"]
        C24["GET /api/v1/referral/balance"]
        C25["GET /api/v1/parrain"]
        C26["GET /api/v1/commands/:id/payment-status"]
        C27["POST /api/v1/telegram/link-token"]
        C28["GET /api/v1/telegram/status"]
        C29["DELETE /api/v1/telegram/unlink"]
    end

    subgraph ADMIN_AUTH["👨‍💼 Admin v2 (JWT Required)"]
        A1["GET /api/v2/admin/protected/orders"]
        A2["GET /api/v2/admin/protected/orders/:id"]
        A3["PUT /api/v2/admin/protected/orders/:id/status"]
        A4["PUT /api/v2/admin/protected/orders/:id/address"]
        A5["POST /api/v2/admin/protected/orders/:id/notify-client"]
        A6["POST /api/v2/admin/protected/orders/:id/auto-assign"]
        A7["POST /api/v2/admin/protected/orders/auto-assign-all"]
        A8["POST /api/v2/admin/protected/orders/:id/force-validate"]
        A9["POST /api/v2/admin/protected/orders/:id/confirm-reception"]
        A10["GET /api/v2/admin/protected/delivery-persons"]
        A11["GET /api/v2/admin/protected/delivery-persons/:username"]
        A12["GET /api/v2/admin/protected/delivery-persons/:username/location"]
        A13["GET /api/v2/admin/protected/delivery/queues"]
        A14["POST /api/v2/admin/protected/delivery/distances"]
        A15["POST/GET /api/v2/admin/protected/products/:id"]
        A16["POST /api/v2/admin/protected/categories"]
        A17["POST /api/v2/admin/protected/penalty"]
        A18["GET /api/v2/admin/protected/all/clients"]
        A19["PUT /api/v2/admin/protected/clients/:id"]
        A20["GET /api/v2/admin/protected/alerts"]
        A21["GET/PUT /api/v2/admin/protected/settings"]
        A22["GET /api/v2/admin/protected/penalties/all"]
        A23["GET /api/v2/admin/protected/addresses"]
    end

    subgraph LIVREUR_AUTH["🚚 Livreur (JWT Required)"]
        L1["GET /api/v1/livreur/deliveries"]
        L2["GET /api/v1/livreur/deliveries/:id"]
        L3["POST /api/v1/livreur/deliveries/:id/start"]
        L4["PUT /api/v1/livreur/deliveries/:id/status"]
        L5["GET /api/v1/livreur/deliveries/:id/nav-link"]
        L6["POST /api/v1/livreur/location/update"]
        L7["GET /api/v1/livreur/location"]
        L8["POST /api/v1/livreur/update/status"]
        L9["GET /api/v1/livreur/status"]
        L10["GET /api/v1/livreur/queue"]
        L11["POST /api/v1/livreur/alert"]
        L12["DELETE /api/v1/livreur/alert/:id"]
        L13["GET /api/v1/livreur/alerts"]
        L14["GET /api/v1/livreur/notifications"]
        L15["POST /api/v1/livreur/notifications/read"]
        L16["POST /api/v1/livreur/telegram/link-token"]
        L17["GET /api/v1/livreur/telegram/status"]
        L18["DELETE /api/v1/livreur/telegram/unlink"]
    end

    subgraph CABINE_AUTH["🏭 Cabine (JWT Required)"]
        K1["GET /api/v1/cabine/commands/:id/items"]
        K2["PUT /api/v1/cabine/commands/:id/status"]
        K3["PUT /api/v1/cabine/items/:item_id/status"]
        K4["POST /api/v1/cabine/commands/:id/confirm-reception"]
        K5["POST /api/v1/cabine/commands/:id/assign"]
        K6["POST /api/v1/cabine/commands/:id/notify-client"]
        K7["GET /api/v1/cabine/all/deliveryman"]
        K8["GET /api/v1/cabine/alerts"]
        K9["GET /api/v1/cabine/penalties/all"]
        K10["GET /api/v1/cabine/addresses"]
        K11["GET /api/v1/cabine/notifications"]
        K12["POST /api/v1/cabine/telegram/link-token"]
    end

    GW[API Gateway :8080]
    GW --> PUBLIC
    GW --> AUTH_PUBLIC
    GW --> CLIENT_AUTH
    GW --> ADMIN_AUTH
    GW --> LIVREUR_AUTH
    GW --> CABINE_AUTH

🔐 Flux d'Authentification

sequenceDiagram
    participant U as Utilisateur
    participant F as Frontend
    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
        U->>F: Remplir formulaire inscription
        F->>API: POST /auth/register
        API->>DB: Creer utilisateur
        DB-->>API: User cree
        API-->>F: 201 Created + User info
        F-->>U: Compte cree
    end

    rect rgb(200, 200, 230)
        Note over U,TG: Login Standard (2FA desactivee)
        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
        F-->>U: Connecte
    end

    rect rgb(230, 200, 200)
        Note over U,R: Requete Authentifiee
        U->>F: Action protegee
        F->>API: Request + Authorization: Bearer JWT
        API->>API: Valider JWT
        API->>R: Verifier session active
        R-->>API: Session valide
        API->>DB: Executer action
        DB-->>API: Resultat
        API-->>F: Response
        F-->>U: Resultat affiche
    end

📦 Flux Complet d'une Commande

sequenceDiagram
    participant C as Client
    participant F as Frontend
    participant API as Backend
    participant GPS as Service GPS
    participant Q as Queue Service
    participant L as Livreur
    participant DB as PostgreSQL
    participant R as Redis

    rect rgb(220, 240, 220)
        Note over C,R: 1. Ajout au panier
        C->>F: Ajouter produit
        F->>API: POST /panier/add {product_id, quantity}
        API->>DB: Verifier stock (GetProductStockByID)
        DB-->>API: Stock OK
        API->>DB: Decrementer stock (DecrementProductStockByID)
        API->>DB: Ajouter ligne panier
        API-->>F: 201 Created
        F-->>C: Panier mis a jour
        Note over C,R: Si suppression panier → stock restitue automatiquement
    end

    rect rgb(230, 245, 230)
        Note over C,R: 2. Checkout
        C->>F: Valider panier
        F->>API: POST /checkout {address}
        API->>GPS: Geocoder adresse
        GPS->>R: Check cache
        R-->>GPS: Miss
        GPS->>GPS: Appel Nominatim
        GPS->>R: Cache resultat (7j)
        GPS-->>API: Coordonnees
        API->>DB: Creer commande
        API->>Q: Chercher livreur proche
        Q->>R: Get positions livreurs
        Q->>Q: Calcul distances Haversine
        Q->>GPS: Calculer ETA (TomTom)
        Q-->>API: Livreur assigne
        API->>DB: Update commande
        API->>R: Ajouter a queue livreur
        API-->>F: Commande creee + ETA
        F-->>C: Confirmation
    end

    rect rgb(230, 230, 245)
        Note over C,R: 3. Livraison
        L->>API: GET /livreur/deliveries
        API-->>L: Liste commandes
        L->>API: POST /deliveries/:id/start
        L->>API: POST /location/update {lat, lng}
        API->>R: Update position
        API->>R: Publish update
        R-->>F: Notification position
        F-->>C: Carte mise a jour
        L->>API: PUT /deliveries/:id/status {en_route}
        Note over L,API: ETA calcule et stocke dans Redis
        L->>API: PUT /deliveries/:id/status {arrived}
        L->>API: PUT /deliveries/:id/status {livre}
    end

    rect rgb(245, 230, 230)
        Note over C,R: 4. Finalisation
        C->>F: Approuver livraison
        F->>API: POST /commands/:id/approve {rating}
        API->>DB: Update statut approved
        API->>R: Retirer de queue
        API-->>F: Confirmation
        F-->>C: Livraison terminee
    end

💾 Schema de la Base de Donnees

erDiagram
    CLIENTS {
        int id PK
        string username UK
        string password
        string nom
        string prenom
        string telephone UK
        int command
        int point
        int points_zipette
        float amende
        int cancellations_count
        timestamp created_at
    }

    ADMINS {
        int id PK
        string username UK
        string password
        string role
        timestamp created_at
    }

    LIVREURS {
        int id PK
        string username UK
        string password
        string status
        timestamp created_at
    }

    CABINES {
        int id PK
        string username UK
        string password
        timestamp created_at
    }

    PRODUCTS {
        int id PK
        string nom
        string description
        string category
        int stock
        float prix
        timestamp created_at
    }

    PRODUCT_PRICES {
        int id PK
        int product_id FK
        int quantity
        float price
        boolean active_price
    }

    PRODUCT_MEDIA {
        int id PK
        int product_id FK
        string type
        string url
    }

    PANIER {
        int id PK
        string username FK
        string name_product
        string category
        int quantity
        float price
        timestamp created_at
    }

    COMMANDS {
        int id PK
        string username FK
        string status
        float total
        string adresse
        float dest_latitude
        float dest_longitude
        string livreur_assign FK
        timestamp created_at
        timestamp updated_at
    }

    COMMAND_ITEMS {
        int id PK
        int command_id FK
        string produit
        int quantite
        float prix
        string status
        string category
    }

    RATINGS {
        int id PK
        int command_id FK
        string deliveryman
        int rating
        string comment
        timestamp created_at
    }

    CLIENTS ||--o{ PANIER : "possede"
    CLIENTS ||--o{ COMMANDS : "passe"
    PRODUCTS ||--o{ PRODUCT_PRICES : "a"
    PRODUCTS ||--o{ PRODUCT_MEDIA : "a"
    COMMANDS ||--o{ COMMAND_ITEMS : "contient"
    COMMANDS ||--o| RATINGS : "a"
    LIVREURS ||--o{ COMMANDS : "livre"

🗄️ Structure Redis

graph LR
    subgraph Sessions["🔐 Sessions"]
        S1["session:{token}<br/>TTL: 5h (client) / 2h (admin)"]
    end

    subgraph Positions["📍 Positions GPS"]
        P1["delivery:location:{username}<br/>TTL: 2h"]
        P2["delivery:status:{username}<br/>TTL: 1h"]
    end

    subgraph Queues["📋 Queues Livraison"]
        Q1["livreur:queue:{username}<br/>List of command_ids"]
        Q2["queue:size:{username}<br/>Integer"]
    end

    subgraph Cache["💾 Cache"]
        C1["geocode:cache:{hash}<br/>TTL: 7j"]
        C2["command:destination:{id}<br/>TTL: 4h"]
        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"]
        PS3["channel:notifications"]
    end

    REDIS[(Redis Server)]
    REDIS --- Sessions
    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

Internet
    │
    ▼
prod-uber (DMZ — 185.103.166.119)
    │  80/443 public
    │  ──── VPN (10.0.0.6) ────►  bdd-redis-prod (LAN — 10.0.0.5)
    │                               ├── PostgreSQL :5432
    │                               └── Redis      :6379
    │
vpn-uber (10.0.0.1) — jump host SSH pour accès aux autres serveurs

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

# 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

  • Docker + Docker Compose
  • Certificats SSL : fullchain.pem + privkey.pem (Let's Encrypt recommandé)
  • Fichier .env complété (voir .env.example)

Structure des fichiers

docker/
├── backend/
│   ├── Dockerfile          # Stage builder (Go), runtime, waf (nginx+ModSec)
│   ├── nginx.conf          # Config nginx hardened (TLS 1.2/1.3, HSTS, CSP)
│   ├── custom-rules.conf   # Règles ModSecurity personnalisées
│   └── entrypoint.sh       # Entrypoint backend Go
├── frontend/
│   ├── Dockerfile          # Build React/Vite + nginx interne
│   └── nginx.conf          # Nginx interne (HTTP, sans TLS)
├── certs/                  # Certificats SSL (non versionnés)
│   ├── fullchain.pem
│   └── privkey.pem
└── docker-compose-prod.yml

Déploiement

# 1. Copier les certificats
mkdir -p docker/certs
cp /etc/letsencrypt/live/votre-domaine/fullchain.pem docker/certs/
cp /etc/letsencrypt/live/votre-domaine/privkey.pem   docker/certs/
chmod 644 docker/certs/privkey.pem

# 2. Configurer les variables d'environnement
cp docker/.env.example docker/.env
# Éditer docker/.env avec vos valeurs

# 3. Lancer la stack
docker compose -f docker/docker-compose-prod.yml up -d --build

# 4. Vérifier les logs
docker compose -f docker/docker-compose-prod.yml logs -f waf

Services Docker (prod-uber)

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.

Variables d'environnement requises

# 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

🔔 Notifications

Les notifications clients et livreurs sont gérées exclusivement via Telegram — les push notifications Expo (iOS/Android) ne sont plus utilisées.

Notifications Telegram

Clients, livreurs et admins reçoivent leurs alertes via un bot Telegram lié à leur compte.

Types de notifications envoyées :

  • assigned — Commande assignée à un livreur
  • en_route — Livreur en route (avec ETA)
  • arrived — Livreur arrivé
  • livre — Commande livrée
  • ready_pickup — Cabine : "descendez chercher votre commande"
  • address_proposal — Proposition de changement d'adresse

Les admins et livreurs peuvent lier leur compte Telegram pour recevoir des alertes.

Générer un token de liaison (Admin)

POST /api/v2/admin/protected/telegram/link-token
Authorization: Bearer <admin_token>

Réponse:

{
  "link_token": "abc123xyz",
  "expires_in": 300,
  "bot_url": "https://t.me/votre_bot?start=abc123xyz"
}

Générer un token de liaison (Livreur)

POST /api/v1/livreur/telegram/link-token
Authorization: Bearer <livreur_token>

Statut Telegram (Livreur)

GET /api/v1/livreur/telegram/status
Authorization: Bearer <livreur_token>

Délier Telegram

DELETE /api/v1/livreur/telegram/unlink
Authorization: Bearer <livreur_token>

💸 Paiements Crypto

Le système intègre NowPayments pour les paiements en cryptomonnaie.

Webhook IPN (Instant Payment Notification)

Endpoint public sécurisé par signature HMAC-SHA512.

POST /api/v1/webhooks/nowpayments
Content-Type: application/json
x-nowpayments-sig: <hmac_sha512_signature>

Le webhook met automatiquement à jour le statut de paiement de la commande correspondante.

Vérifier le statut de paiement d'une commande

GET /api/v1/commands/:id/payment-status
Authorization: Bearer <client_token>

Réponse:

{
  "command_id": 1234,
  "payment_status": "confirmed",
  "amount_paid": 25.00,
  "currency": "USDT"
}

🔐 Authentication

Création de compte 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.


Login Client

Se connecter et obtenir un token JWT

POST /api/v1/auth/login
Content-Type: application/json

Requête:

{
  "username": "jean_dupont",
  "password": "SecurePass123!"
}

Réponse standard (200 OK) — 2FA désactivée:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 18000,
  "user": {
    "username": "jean_dupont",
    "role": "client"
  }
}

Réponse avec 2FA (200 OK) — quand 2FA activée par le client ET Telegram lié ET admin l'a activée:

{
  "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

POST /api/v1/auth/2fa/verify
Content-Type: application/json

Requête:

{
  "session_token": "550e8400-e29b-41d4-a716-446655440000",
  "code": "483721"
}

Réponse (200 OK):

{
  "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é

GET /api/v1/two-fa/status
Authorization: Bearer <token>

Réponse (200 OK):

{
  "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

POST /api/v1/two-fa/toggle
Authorization: Bearer <token>
Content-Type: application/json

Requête:

{
  "enabled": true
}

Réponse (200 OK):

{
  "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)

POST /api/v1/auth/logout
Authorization: Bearer <token>

Réponse (200 OK):

{
  "success": true,
  "message": "Déconnexion réussie"
}

🛒 API Client (v1)

Toutes les routes clients nécessitent le header:

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

GET /api/v1/products

Réponse (200 OK):

{
  "success": true,
  "products": [
    {
      "id": 1,
      "nom": "Pizza Margherita",
      "description": "Pizza traditionnelle avec tomate, mozzarella et basilic",
      "category": "pizza",
      "stock": 50,
      "prix": 12.50,
      "prices": [
        {
          "id": 1,
          "product_id": 1,
          "quantity": 1,
          "price": 12.50,
          "active_price": true
        }
      ],
      "media": [
        {
          "id": 1,
          "product_id": 1,
          "type": "image",
          "url": "/uploads/pizza_margherita.jpg"
        }
      ],
      "created_at": "2025-01-18T00:00:00Z"
    }
  ],
  "total": 1
}

Produit par ID

GET /api/v1/products/:id

Réponse (200 OK):

{
  "success": true,
  "product": {
    "id": 1,
    "nom": "Pizza Margherita",
    "description": "Pizza traditionnelle avec tomate, mozzarella et basilic",
    "category": "pizza",
    "stock": 50,
    "prix": 12.50,
    "prices": [...],
    "media": [...],
    "created_at": "2025-01-18T00:00:00Z"
  }
}

Erreurs possibles:

  • 404 - Produit non trouvé

Gestion du Panier

Note stock : Le stock est décrémenté dès l'ajout au panier et restitué automatiquement lors de la suppression d'un article ou du vidage du panier.

Ajouter au panier

POST /api/v1/panier/add
Authorization: Bearer <token>
Content-Type: application/json

Requête:

{
  "name_product": "Pizza Margherita",
  "category": "pizza",
  "quantity": 2
}

Réponse (201 Created):

{
  "success": true,
  "message": "Produit ajouté au panier avec succès",
  "panier": {
    "id": 15,
    "username": "jean_dupont",
    "name_product": "Pizza Margherita",
    "category": "pizza",
    "quantity": 2,
    "price": 25.00,
    "created_at": "2025-01-18T14:35:00Z"
  }
}

Erreurs possibles:

  • 400 - Données invalides ou stock insuffisant
  • 401 - Non authentifié
  • 404 - Produit non trouvé

Voir mon panier

GET /api/v1/panier/:username
Authorization: Bearer <token>

Réponse (200 OK):

{
  "success": true,
  "message": "Panier récupéré avec succès",
  "panier": [
    {
      "id": 15,
      "username": "jean_dupont",
      "name_product": "Pizza Margherita",
      "category": "pizza",
      "quantity": 2,
      "price": 25.00,
      "created_at": "2025-01-18T14:35:00Z"
    }
  ],
  "count": 1,
  "total_amount": 25.00
}

Erreurs possibles:

  • 403 - Accès au panier d'un autre utilisateur
  • 404 - Utilisateur non trouvé

Supprimer du panier

DELETE /api/v1/panier/remove
Authorization: Bearer <token>
Content-Type: application/json

Requête:

{
  "id": 15
}

Réponse (200 OK):

{
  "success": true,
  "message": "Produit supprimé du panier avec succès",
  "item_id": 15
}

Erreurs possibles:

  • 403 - Tentative de supprimer l'article d'un autre utilisateur
  • 404 - Article non trouvé

Vider le panier

DELETE /api/v1/panier/clear
Authorization: Bearer <token>

Réponse (200 OK):

{
  "success": true,
  "message": "Panier vidé avec succès"
}

Commandes

Valider le panier (Checkout avec Auto-assignation)

POST /api/v1/checkout
Authorization: Bearer <token>
Content-Type: application/json

Requête:

{
  "delivery_address": "15 Rue de la Paix, 75002 Paris, France"
}

Réponse avec auto-assignation (200 OK):

{
  "success": true,
  "message": "Commande créée et livreur assigné automatiquement",
  "command_id": 1234,
  "delivery_address": "15 Rue de la Paix, 75002 Paris, France",
  "status": "assigned",
  "auto_assigned": true,
  "assigned_to": {
    "username": "john_deliveryman",
    "distance_km": 1.5,
    "travel_time": 8
  }
}

Réponse sans auto-assignation (200 OK):

{
  "success": true,
  "message": "Commande créée - En attente d'assignation",
  "command_id": 1234,
  "delivery_address": "15 Rue de la Paix, 75002 Paris, France",
  "status": "pending",
  "auto_assigned": false
}

Erreurs possibles:

  • 400 - Panier vide ou adresse manquante
  • 401 - Non authentifié
  • 500 - Erreur création commande

Mes commandes avec suivi

GET /api/v1/my-commands
Authorization: Bearer <token>

Réponse (200 OK):

{
  "success": true,
  "commands": [
    {
      "id": 1234,
      "username": "jean_dupont",
      "status": "assigned",
      "total": 25.00,
      "delivery_address": "15 Rue de la Paix, 75002 Paris",
      "livreur_assign": "john_deliveryman",
      "created_at": "2025-01-18T14:40:00Z",
      "updated_at": "2025-01-18T14:42:00Z",
      "tracking": {
        "eta_minutes": 25,
        "estimated_arrival": "15:05",
        "deliveryman_status": "en_route",
        "queue_position": 2
      }
    }
  ],
  "total": 1
}

Statut temps réel d'une commande

GET /api/v1/commands/:id/status
Authorization: Bearer <token>

Réponse (200 OK):

{
  "success": true,
  "command_id": 1234,
  "status": "en_route",
  "deliveryman": "john_deliveryman",
  "updated_at": "2025-01-18T15:20:00Z"
}

Statuts possibles:

Statut Description Qui peut le définir
pending En attente d'assignation Système (checkout)
assigned Assignée à un livreur Système (auto-assign), Admin
en_route Livreur en chemin Livreur, Admin
arrived Livreur arrivé à destination Livreur, Admin, Cabine (bouton "Le livreur est là")
livre Livrée, en attente d'approbation Livreur (validation GPS), Admin
approved Approuvée et finalisée Client (approve), Admin, Cabine
cancelled Annulée Client, Livreur, Admin

Suivi détaillé avec ETA

GET /api/v1/commands/:id/tracking
Authorization: Bearer <token>

Réponse (200 OK):

{
  "success": true,
  "command_id": 1234,
  "status": "en_route",
  "deliveryman": "john_deliveryman",
  "eta": {
    "minutes": 22,
    "estimated_arrival": "15:02",
    "arrival_time_unix": 1705669320,
    "updated_at": "2025-01-18T14:40:00Z"
  },
  "queue_position": 2,
  "total_queue_size": 5
}

ETA de livraison

GET /api/v1/commands/:id/eta
Authorization: Bearer <token>

Réponse (200 OK):

{
  "success": true,
  "command_id": 1234,
  "eta_minutes": 22,
  "estimated_arrival": "15:02:00",
  "queue_position": 2,
  "updated_at": "2025-01-18T14:40:00Z"
}

Approuver une livraison

POST /api/v1/commands/:id/approve
Authorization: Bearer <token>
Content-Type: application/json

Requête:

{
  "rating": 5,
  "comment": "Excellent service, livreur très professionnel !"
}

Réponse (200 OK):

{
  "success": true,
  "message": "Livraison approuvée avec succès",
  "command_id": 1234,
  "status": "approved",
  "rating": {
    "deliveryman": "john_deliveryman",
    "rating": 5,
    "comment": "Excellent service, livreur très professionnel !",
    "created_at": "2025-01-18T15:05:00Z"
  }
}

Annuler une commande

POST /api/v1/commands/:id/cancel
Authorization: Bearer <token>
Content-Type: application/json

Requête:

{
  "reason": "Changement de plans"
}

Réponse (200 OK):

{
  "success": true,
  "message": "Commande annulée avec succès",
  "command_id": 1234,
  "penalty_applied": true,
  "penalty_details": {
    "cancellations_count": 1,
    "penalty_amount": 20.0,
    "warning": "Pénalité appliquée pour annulation"
  }
}

Profil et Historique

Mettre à jour mon profil

PUT /api/v1/profile/update
Authorization: Bearer <token>
Content-Type: application/json

Requête:

{
  "nom": "Nouveau Nom",
  "prenom": "Nouveau Prénom",
  "telephone": "+33687654321",
  "password": "NewSecurePass456!"
}

Réponse (200 OK):

{
  "success": true,
  "message": "Profil mis à jour avec succès",
  "client": {
    "id": 42,
    "username": "jean_dupont",
    "nom": "Nouveau Nom",
    "prenom": "Nouveau Prénom",
    "telephone": "+33687654321",
    "updated_at": "2025-01-18T15:30:00Z"
  }
}

Mes pénalités

GET /api/v1/penalties
Authorization: Bearer <token>

Réponse (200 OK):

{
  "success": true,
  "client": {
    "username": "jean_dupont",
    "amende": 20.0,
    "cancellations_count": 1,
    "last_penalty_reason": "Annulation tardive"
  },
  "penalty_scale": {
    "1st": 20.0,
    "2nd": 50.0,
    "3rd": 100.0,
    "4th+": 150.0
  },
  "next_penalty": 50.0,
  "warning": "La prochaine annulation entraînera une pénalité de 50 points"
}

👨‍💼 API Admin (v2)

Toutes les routes admin nécessitent le header:

Authorization: Bearer <admin_token>

Authentication Admin

Login Admin

POST /api/v2/admin/auth/login
Content-Type: application/json

Requête:

{
  "username": "admin_master",
  "password": "AdminSecure123!"
}

Réponse (200 OK):

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 7200,
  "user": {
    "username": "admin_master",
    "role": "admin"
  }
}

Gestion des Commandes

Liste des commandes

GET /api/v2/admin/protected/orders
Authorization: Bearer <admin_token>

Réponse (200 OK):

{
  "success": true,
  "orders": [
    {
      "id": 1234,
      "username": "jean_dupont",
      "status": "assigned",
      "total": 25.00,
      "delivery_address": "15 Rue de la Paix, 75002 Paris",
      "livreur_assign": "john_deliveryman",
      "dest_latitude": 48.8698,
      "dest_longitude": 2.3311,
      "created_at": "2025-01-18T14:40:00Z",
      "updated_at": "2025-01-18T14:42:00Z"
    }
  ],
  "total": 1
}

Modifier le statut d'une commande

PUT /api/v2/admin/protected/orders/:id/status
Authorization: Bearer <admin_token>
Content-Type: application/json

Requête:

{ "status": "arrived" }

Statuts acceptés: pending, assigned, en_route, arrived, livre, approved, cancelled

Réponse (200 OK):

{
  "success": true,
  "message": "Statut mis à jour",
  "command_id": 1234,
  "new_status": "arrived"
}

Erreurs possibles:

  • 400 - Statut invalide
  • 403 - Rôle insuffisant (admin ou cabine requis)
  • 404 - Commande non trouvée

Notifier le client de descendre

Envoie une notification (Telegram + in-app) au client pour récupérer sa commande, puis passe le statut en arrived.

POST /api/v2/admin/protected/orders/:id/notify-client
Authorization: Bearer <admin_token>

Réponse (200 OK):

{
  "success": true,
  "message": "Client notifié",
  "client_username": "jean_dupont"
}

Assignation automatique GPS

POST /api/v2/admin/protected/orders/:id/auto-assign
Authorization: Bearer <admin_token>

Réponse (200 OK):

{
  "success": true,
  "message": "Commande assignée automatiquement",
  "command_id": 1234,
  "assigned_to": "john_deliveryman",
  "distance": {
    "km": 1.5,
    "eta_minutes": 8
  },
  "queue_position": 3
}

Erreurs possibles:

  • 404 - Commande non trouvée
  • 400 - Aucun livreur disponible
  • 409 - Commande déjà assignée

Assigner toutes les commandes en attente

POST /api/v2/admin/protected/orders/auto-assign-all
Authorization: Bearer <admin_token>

Réponse (200 OK):

{
  "success": true,
  "message": "Auto-assignation effectuée",
  "assigned_count": 5,
  "failed_count": 0,
  "details": [
    {
      "command_id": 1234,
      "assigned_to": "john_deliveryman",
      "distance_km": 1.5,
      "eta_minutes": 8
    }
  ]
}

Gestion des Livreurs

Livreurs disponibles

GET /api/v2/admin/protected/delivery-persons
Authorization: Bearer <admin_token>

Réponse (200 OK):

{
  "success": true,
  "delivery_persons": [
    {
      "username": "john_deliveryman",
      "status": "available",
      "current_command": 0,
      "queue_size": 2,
      "location": {
        "latitude": 48.8566,
        "longitude": 2.3522,
        "last_update": "2025-01-18T16:30:00Z"
      }
    }
  ],
  "total": 1
}

Position d'un livreur

GET /api/v2/admin/protected/delivery-persons/:username/location
Authorization: Bearer <admin_token>

Réponse (200 OK):

{
  "success": true,
  "deliveryman": "john_deliveryman",
  "location": {
    "latitude": 48.8566,
    "longitude": 2.3522,
    "last_update": "2025-01-18T16:25:00Z"
  },
  "status": "available"
}

Queues des livreurs

GET /api/v2/admin/protected/delivery/queues
Authorization: Bearer <admin_token>

Réponse (200 OK):

{
  "success": true,
  "queues": {
    "john_deliveryman": {
      "queue_size": 3,
      "commands": [1234, 1235, 1236],
      "can_accept_more": true,
      "capacity": "3/10",
      "status": "available"
    }
  },
  "total_pending": 3
}

Gestion des Produits

Créer un produit

POST /api/v2/admin/protected/products
Authorization: Bearer <admin_token>
Content-Type: application/json

Requête:

{
  "nom": "Pizza Pepperoni",
  "category": "pizza",
  "description": "Pizza avec pepperoni et mozzarella",
  "stock": 30,
  "prix": 14.50
}

Réponse (201 Created):

{
  "success": true,
  "message": "Produit créé avec succès",
  "product": {
    "id": 10,
    "nom": "Pizza Pepperoni",
    "category": "pizza",
    "description": "Pizza avec pepperoni et mozzarella",
    "stock": 30,
    "prix": 14.50,
    "created_at": "2025-01-18T16:10:00Z"
  }
}

Système de Pénalités (Clients uniquement)

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é :

{
  "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)

POST /api/v2/admin/protected/penalty
Authorization: Bearer <admin_token>
Content-Type: application/json

Requête:

{
  "username": "jean_dupont",
  "amount": 50.0,
  "reason": "Comportement inapproprié"
}

Réponse (200 OK):

{
  "success": true,
  "message": "Pénalité appliquée",
  "client": "jean_dupont",
  "penalty": {
    "amount": 50.0,
    "reason": "Comportement inapproprié",
    "applied_at": "2025-01-18T17:00:00Z"
  },
  "total_amende": 70.0
}

🏪 API Cabine (v1)

Toutes les routes cabine nécessitent le header:

Authorization: Bearer <cabine_token>

Articles d'une commande

GET /api/v1/cabine/commands/:id/items
Authorization: Bearer <cabine_token>

Réponse (200 OK):

{
  "success": true,
  "command_id": 1234,
  "items": [
    {
      "id": 1,
      "command_id": 1234,
      "produit": "Pizza Margherita",
      "quantite": 2,
      "prix": 25.0,
      "status": "pending",
      "category": "pizza"
    }
  ],
  "total": 1
}

Modifier le statut d'une commande

PUT /api/v1/cabine/commands/:id/status
Authorization: Bearer <cabine_token>
Content-Type: application/json

Requête:

{ "status": "arrived" }

Statuts acceptés: pending, assigned, en_route, arrived, livre, approved, cancelled

Réponse (200 OK):

{
  "success": true,
  "message": "Statut mis à jour",
  "command_id": 1234,
  "new_status": "arrived"
}

Notifier le client de descendre

POST /api/v1/cabine/commands/:id/notify-client
Authorization: Bearer <cabine_token>

Réponse (200 OK):

{
  "success": true,
  "message": "Client notifié",
  "client_username": "jean_dupont"
}

Modifier le statut d'un article

PUT /api/v1/cabine/items/:item_id/status
Authorization: Bearer <cabine_token>
Content-Type: application/json

Requête:

{
  "status": "prepared"
}

Réponse (200 OK):

{
  "success": true,
  "message": "Statut mis à jour",
  "item": {
    "id": 1,
    "command_id": 1234,
    "status": "prepared",
    "updated_at": "2025-01-18T17:15:00Z"
  }
}

Statuts disponibles: pending, prepared, packed, ready


Confirmer réception (approbation finale)

POST /api/v1/cabine/commands/:id/confirm-reception
Authorization: Bearer <cabine_token>

Réponse (200 OK):

{
  "success": true,
  "message": "Réception confirmée",
  "command_id": 1234,
  "new_status": "approved"
}

Assigner un livreur

POST /api/v1/cabine/commands/:id/assign
Authorization: Bearer <cabine_token>
Content-Type: application/json

Requête:

{ "username": "john_deliveryman" }

Livreurs disponibles

GET /api/v1/cabine/all/deliveryman
Authorization: Bearer <cabine_token>

🚚 API Livreur (v1)

Toutes les routes livreur nécessitent le header:

Authorization: Bearer <livreur_token>

Livraisons

Mes livraisons (données filtrées)

GET /api/v1/livreur/deliveries
Authorization: Bearer <livreur_token>

Réponse (200 OK):

{
  "success": true,
  "deliveries": [
    {
      "id": 1234,
      "status": "assigned",
      "adresse": "15 Rue de la Paix, 75002 Paris",
      "total_prix": 25.0,
      "created_at": "2025-01-18T14:40:00Z",
      "client_info": {
        "nom": "Dupont",
        "prenom": "Jean"
      },
      "items": [
        {
          "produit": "Pizza Margherita",
          "quantite": 2,
          "prix": 25.0
        }
      ],
      "items_count": 1,
      "eta": {
        "minutes": 25,
        "estimated_arrival": "15:05"
      }
    }
  ],
  "count": 1
}

Note: Les données sensibles comme le téléphone client sont filtrées pour les livreurs.


Mettre à jour le statut d'une livraison

PUT /api/v1/livreur/deliveries/:id/status
Authorization: Bearer <livreur_token>
Content-Type: application/json

Requête:

{
  "status": "en_route",
  "notes": "En route vers le client",
  "latitude": 48.8566,
  "longitude": 2.3522
}

Réponse (200 OK):

{
  "success": true,
  "message": "Statut mis à jour",
  "command_id": 1234,
  "status": "en_route",
  "eta_minutes": 15,
  "eta_message": "Arrivée prévue dans 15 minutes"
}

Statuts valides pour livreur (via cet endpoint) :

  • en_route — En route vers le client (déclenche calcul ETA + notification Telegram avec ETA)
  • arrived — Arrivé à destination (notifie le client de descendre)
  • livre — Livré (via validation GPS uniquement, voir endpoint force-validate)
  • cancelled — Annulation par le livreur

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

Mettre à jour ma position

POST /api/v1/livreur/location/update
Authorization: Bearer <livreur_token>
Content-Type: application/json

Requête:

{
  "latitude": 48.8566,
  "longitude": 2.3522
}

Réponse (200 OK):

{
  "success": true,
  "message": "Position mise à jour",
  "location": {
    "latitude": 48.8566,
    "longitude": 2.3522,
    "updated_at": "2025-01-18T17:35:00Z"
  }
}

Ma position actuelle

GET /api/v1/livreur/location
Authorization: Bearer <livreur_token>

Réponse (200 OK):

{
  "success": true,
  "location": {
    "latitude": 48.8566,
    "longitude": 2.3522,
    "last_update": "2025-01-18T17:35:00Z"
  }
}

Statut et Queue

Mettre à jour mon statut

POST /api/v1/livreur/update/status
Authorization: Bearer <livreur_token>
Content-Type: application/json

Requête:

{
  "status": "available"
}

Réponse (200 OK):

{
  "success": true,
  "message": "Statut mis à jour",
  "status": {
    "username": "john_deliveryman",
    "status": "available",
    "updated_at": "2025-01-18T17:40:00Z"
  }
}

Statuts disponibles: available, busy, offline


Ma queue de livraisons

GET /api/v1/livreur/queue
Authorization: Bearer <livreur_token>

Réponse (200 OK):

{
  "success": true,
  "deliveryman": "john_deliveryman",
  "queue_size": 3,
  "commands": [
    {
      "position": 1,
      "command_id": 1234,
      "address": "15 Rue de la Paix, 75002 Paris",
      "estimated_eta": 12,
      "total_prix": 25.0
    }
  ]
}

🗺️ Systeme GPS Integre

Le systeme GPS est au coeur de la plateforme de livraison. Il permet l'assignation automatique des livreurs, le calcul d'ETA en temps reel, et le suivi des livraisons.

Architecture GPS

Flux GPS Complet

flowchart TD
    A[1. Commande creee par le Client] --> B[2. Geocodage adresse]
    B --> |Nominatim API| C[(Cache Redis 7 jours)]
    C --> D[3. Worker Auto-Assignation]
    D --> |Toutes les 5 min| E[4. Recherche livreur le plus proche]
    E --> |Haversine + TomTom| F[5. Calcul ETA avec trafic reel]
    F --> G[6. Assignation a la queue du livreur]
    G --> H[7. Livreur accepte et met a jour sa position]
    H --> |Redis Pub/Sub| I[8. Client/Admin recoivent les mises a jour]
    I --> J[9. Navigation vers destination]
    J --> |TomTom Routing| K[10. Livraison terminee]

Architecture des Services GPS

graph TB
    subgraph Frontend
        MAP[TomTomMap Component]
        UI[Interface Utilisateur]
    end

    subgraph Backend
        GEO[Service Geocodage]
        DIST[Service Distance]
        ETA[Service ETA]
        ASSIGN[Service Auto-Assignation]
    end

    subgraph APIs Externes
        NOM[Nominatim API]
        TOM[TomTom API]
    end

    subgraph Stockage
        REDIS[(Redis Cache)]
        PG[(PostgreSQL)]
    end

    UI --> MAP
    MAP --> TOM
    GEO --> NOM
    GEO --> REDIS
    DIST --> GEO
    ETA --> TOM
    ETA --> DIST
    ASSIGN --> DIST
    ASSIGN --> ETA
    ASSIGN --> PG
    ASSIGN --> REDIS

Services GPS Utilises

Service Utilisation Cache
Nominatim (OpenStreetMap) Geocodage d'adresses Redis 7 jours
TomTom Routing API ETA avec trafic reel, itineraires Non
Haversine (local) Calcul de distance a vol d'oiseau Non

Configuration Requise

Variables d'environnement Backend:

TOMTOM_API_KEY=<votre_cle_api_tomtom>

Variables d'environnement Frontend:

VITE_TOMTOM_API_KEY=<votre_cle_api_tomtom>

Note: Obtenez une cle API TomTom gratuite sur developer.tomtom.com


Geocodage d'Adresses

Le geocodage convertit une adresse textuelle en coordonnees GPS (latitude/longitude).

Geocoder une adresse

POST /api/v1/geocode
Content-Type: application/json

Requete:

{
  "address": "15 Rue de la Paix, 75002 Paris, France"
}

Reponse (200 OK):

{
  "success": true,
  "address": "15 Rue de la Paix, 75002 Paris, France",
  "coordinates": {
    "latitude": 48.8698,
    "longitude": 2.3311
  },
  "cached": false
}

Erreurs possibles:

  • 400 - Adresse manquante ou invalide
  • 404 - Adresse non trouvee (geocodage echoue)

Valider une adresse

Verifie si une adresse peut etre geocodee sans la stocker.

POST /api/v1/validate-address
Content-Type: application/json

Requete:

{
  "address": "15 Rue de la Paix, 75002 Paris"
}

Reponse (200 OK):

{
  "success": true,
  "valid": true,
  "address": "15 Rue de la Paix, 75002 Paris",
  "coordinates": {
    "latitude": 48.8698,
    "longitude": 2.3311
  }
}

Calcul de Distance et ETA

Formule Haversine

Le systeme utilise la formule Haversine pour calculer la distance a vol d'oiseau entre deux points GPS:

a = sin²(Δlat/2) + cos(lat1) × cos(lat2) × sin²(Δlon/2)
c = 2 × atan2(√a, √(1-a))
distance = R × c

Ou R = 6371 km (rayon de la Terre)

ETA avec Trafic (TomTom)

L'ETA est calcule en utilisant l'API TomTom qui prend en compte:

  • Les conditions de trafic en temps reel
  • Les incidents routiers
  • 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.

flowchart TD
    A[Demande ETA] --> B{Clés TomTom configurées?}
    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]
    F --> G[Vitesse moyenne 30 km/h]
    G --> H[ETA estimé]
    D --> I[Retourner ETA]
    H --> I

Fallback: Si toutes les clés TomTom sont épuisées ou indisponibles, le système utilise un calcul local base sur:

  • Distance Haversine
  • Vitesse moyenne estimee (30 km/h en ville)

Auto-Assignation GPS

Le systeme assigne automatiquement les commandes au livreur le plus proche.

Processus d'Auto-Assignation

flowchart LR
    A[Nouvelle Commande] --> B{Adresse geocodee?}
    B -->|Non| C[Geocoder adresse]
    C --> D
    B -->|Oui| D[Recuperer livreurs disponibles]
    D --> E[Calculer distances Haversine]
    E --> F[Trier par proximite]
    F --> G{Livreur avec capacite?}
    G -->|Oui| H[Calculer ETA TomTom]
    G -->|Non| I{Forcer assignation?}
    I -->|Oui| H
    I -->|Non| J[Commande en attente]
    H --> K[Assigner a la queue]
    K --> L[Notifier livreur]

Regles de Capacite

  • Chaque livreur peut avoir jusqu'a 10 commandes dans sa queue
  • Si tous les livreurs sont a capacite maximale, le systeme peut forcer l'assignation
  • Les livreurs avec le statut offline ne recoivent pas de commandes

Auto-assigner une commande specifique

POST /api/v2/admin/protected/orders/:id/auto-assign
Authorization: Bearer <admin_token>

Reponse (200 OK):

{
  "success": true,
  "message": "Commande assignee automatiquement",
  "command_id": 1234,
  "assigned_to": "john_deliveryman",
  "assignment_details": {
    "distance_km": 1.5,
    "eta_minutes": 8,
    "queue_position": 3,
    "method": "nearest_available"
  }
}

Lister les livreurs par distance

Obtient la liste des livreurs tries par distance depuis une adresse.

POST /api/v2/admin/protected/delivery/distances
Authorization: Bearer <admin_token>
Content-Type: application/json

Requete:

{
  "address": "15 Rue de la Paix, 75002 Paris"
}

Reponse (200 OK):

{
  "success": true,
  "address": "15 Rue de la Paix, 75002 Paris",
  "destination": {
    "latitude": 48.8698,
    "longitude": 2.3311
  },
  "delivery_persons": [
    {
      "username": "john_deliveryman",
      "distance_km": 1.5,
      "eta_minutes": 8,
      "status": "available",
      "queue_size": 2,
      "location": {
        "latitude": 48.8566,
        "longitude": 2.3522
      }
    },
    {
      "username": "jane_delivery",
      "distance_km": 3.2,
      "eta_minutes": 15,
      "status": "available",
      "queue_size": 5,
      "location": {
        "latitude": 48.8456,
        "longitude": 2.3789
      }
    }
  ]
}

Liens de Navigation

Le systeme genere des liens vers plusieurs applications de cartographie.

Obtenir les liens carte d'un livreur

GET /api/v2/admin/protected/delivery-persons/:username/map-links
Authorization: Bearer <admin_token>

Reponse (200 OK):

{
  "success": true,
  "deliveryman": "john_deliveryman",
  "location": {
    "latitude": 48.8566,
    "longitude": 2.3522
  },
  "map_links": {
    "google_maps": "https://www.google.com/maps?q=48.8566,2.3522",
    "google_maps_app": "comgooglemaps://?center=48.8566,2.3522",
    "waze": "https://www.waze.com/ul?ll=48.8566,2.3522&navigate=yes",
    "waze_app": "waze://?ll=48.8566,2.3522&navigate=yes",
    "apple_maps": "https://maps.apple.com/?ll=48.8566,2.3522",
    "openstreetmap": "https://www.openstreetmap.org/?mlat=48.8566&mlon=2.3522",
    "bing_maps": "https://www.bing.com/maps?cp=48.8566~2.3522",
    "here_maps": "https://share.here.com/l/48.8566,2.3522"
  }
}

Obtenir les liens de navigation pour une commande

Genere des liens de navigation depuis la position du livreur vers la destination de livraison.

GET /api/v2/admin/protected/commands/:id/navigation-links
Authorization: Bearer <admin_token>

Reponse (200 OK):

{
  "success": true,
  "command_id": 1234,
  "origin": {
    "latitude": 48.8566,
    "longitude": 2.3522,
    "description": "Position du livreur"
  },
  "destination": {
    "latitude": 48.8698,
    "longitude": 2.3311,
    "address": "15 Rue de la Paix, 75002 Paris"
  },
  "navigation_links": {
    "google_maps": "https://www.google.com/maps/dir/48.8566,2.3522/48.8698,2.3311",
    "waze": "https://www.waze.com/ul?ll=48.8698,2.3311&navigate=yes&from=48.8566,2.3522",
    "apple_maps": "https://maps.apple.com/?saddr=48.8566,2.3522&daddr=48.8698,2.3311"
  }
}

Gestion des Positions en Temps Reel

Flux de Mise a Jour de Position

sequenceDiagram
    participant L as Livreur (App)
    participant API as Backend API
    participant R as Redis
    participant PS as Redis Pub/Sub
    participant A as Admin/Client

    L->>API: POST /location/update {lat, lng}
    API->>API: Valider coordonnees
    API->>R: SET delivery:location:{username}
    API->>PS: PUBLISH position_update
    PS-->>A: Notification temps reel
    API-->>L: 200 OK {location, updated_at}

Mise a jour de position (Livreur)

POST /api/v1/livreur/location/update
Authorization: Bearer <livreur_token>
Content-Type: application/json

Requete:

{
  "latitude": 48.8566,
  "longitude": 2.3522
}

Validation des coordonnees:

  • Latitude: entre -90 et +90
  • Longitude: entre -180 et +180

Reponse (200 OK):

{
  "success": true,
  "message": "Position mise a jour",
  "location": {
    "latitude": 48.8566,
    "longitude": 2.3522,
    "updated_at": "2025-01-18T17:35:00Z"
  }
}

Erreurs possibles:

  • 400 - Coordonnees invalides (hors limites)
  • 401 - Non authentifie

Stockage Redis des Positions

Les positions GPS sont stockees dans Redis pour un acces rapide:

graph LR
    subgraph Redis Cache
        A[delivery:location:username<br/>TTL: 2h]
        B[delivery:status:username<br/>TTL: 1h]
        C[geocode:cache:address_hash<br/>TTL: 7j]
        D[command:destination:id<br/>TTL: 4h]
    end

    subgraph Donnees
        A --> A1[latitude, longitude, updated_at]
        B --> B1[status, latitude, longitude]
        C --> C1[latitude, longitude, address]
        D --> D1[dest_lat, dest_lng]
    end

| command:destination:{command_id} | Coordonnees destination | 4 heures |


Composant Carte Frontend (TomTomMap)

Le frontend inclut un composant React/TypeScript pour afficher la carte interactive.

Fonctionnalites du Composant

  • Carte interactive avec marqueurs livreur/destination
  • Calcul d'itineraire en temps reel
  • Instructions de navigation (tourner a gauche, a droite, rond-point, etc.)
  • Affichage distance/duree du trajet
  • Mode 3D avec orientation selon la direction
  • Support francais pour toutes les instructions

Exemple d'Utilisation

import TomTomMap from './components/TomTomMap';

function DeliveryTracking() {
  return (
    <TomTomMap
      driverPosition={{ lat: 48.8566, lng: 2.3522 }}
      destinationPosition={{ lat: 48.8698, lng: 2.3311 }}
      showRoute={true}
      showInstructions={true}
    />
  );
}

Props du Composant

Prop Type Description
driverPosition {lat: number, lng: number} Position du livreur
destinationPosition {lat: number, lng: number} Destination de livraison
showRoute boolean Afficher l'itineraire
showInstructions boolean Afficher le panneau d'instructions
onEtaUpdate (eta: number) => void Callback quand l'ETA change

Worker d'Auto-Assignation

Un worker CRON s'execute automatiquement pour assigner les commandes en attente.

Configuration

  • Frequence: Toutes les 1 minute
  • Fichier: backend/gestion/workers/cron_auto_assign.go

Processus du Worker

flowchart TD
    START((Demarrage CRON)) --> A[Recuperer commandes pending]
    A --> B{Commandes a traiter?}
    B -->|Non| END((Fin))
    B -->|Oui| C[Prendre commande suivante]
    C --> D{Adresse geocodee?}
    D -->|Non| E[Geocoder via Nominatim]
    E --> F
    D -->|Oui| F[Recuperer livreurs disponibles]
    F --> G[Calculer distances]
    G --> H[Selectionner le plus proche]
    H --> I{Capacite disponible?}
    I -->|Non| J[Marquer pour retry]
    I -->|Oui| K[Calculer ETA TomTom]
    K --> L[Assigner commande]
    L --> M[Mettre a jour statut]
    M --> N{Autres commandes?}
    J --> N
    N -->|Oui| C
    N -->|Non| O[Logger resultats]
    O --> END

Logs d'Exemple

[AUTO-ASSIGN] Processing 5 pending orders
[AUTO-ASSIGN] Order #1234: Geocoded to (48.8698, 2.3311)
[AUTO-ASSIGN] Order #1234: Nearest driver is john_deliveryman (1.5 km)
[AUTO-ASSIGN] Order #1234: ETA calculated: 8 minutes
[AUTO-ASSIGN] Order #1234: Assigned successfully
[AUTO-ASSIGN] Completed: 5 assigned, 0 failed

Cycle de Vie d'une Livraison (GPS)

stateDiagram-v2
    [*] --> pending: Commande creee (checkout)
    pending --> assigned: Auto-assignation GPS / Admin
    pending --> cancelled: Annulation client ou admin

    assigned --> en_route: Livreur demarre (start)
    assigned --> cancelled: Annulation

    en_route --> arrived: Livreur arrive (GPS ou Admin/Cabine)
    en_route --> en_route: Mise a jour position GPS
    en_route --> livre: Validation GPS livreur / Admin

    arrived --> livre: Remise au client (validation GPS livreur)
    arrived --> approved: Admin/Cabine force approbation

    livre --> approved: Client confirme / Admin / Cabine

    approved --> [*]
    cancelled --> [*]

    note right of arrived
        Declenche par :
        - 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
        ETA calcule et stocke dans Redis
        (command:eta:{id})
        Utilise pour les notifications Telegram
    end note

Gestion des Erreurs GPS

Geocodage Echoue

Si une adresse ne peut pas etre geocodee:

  • La commande reste en statut pending
  • Un log d'erreur est genere
  • L'admin peut corriger l'adresse manuellement

API TomTom Indisponible / Quota dépassé

Le système tente d'abord toutes les clés disponibles en rotation, puis bascule 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

Bonnes Pratiques

Pour les Livreurs

  1. Mettre a jour la position frequemment (toutes les 30 secondes recommande)
  2. Verifier le statut avant de commencer une livraison
  3. Utiliser les liens de navigation generes par l'API

Pour les Admins

  1. Surveiller les queues des livreurs pour eviter la surcharge
  2. Verifier les adresses qui echouent au geocodage
  3. Utiliser l'endpoint distances pour l'assignation manuelle si necessaire

Pour les Developpeurs

  1. Toujours valider les coordonnees avant stockage
  2. Utiliser le cache Redis pour les adresses frequentes
  3. Implementer un fallback si TomTom est indisponible
  4. Logger les erreurs de geocodage pour analyse

Codes d'Erreur

Erreurs HTTP Standards

Code Signification Description
200 OK Succès
201 Created Ressource créée
400 Bad Request Données invalides
401 Unauthorized Non authentifié
403 Forbidden Non autorisé
404 Not Found Ressource introuvable
409 Conflict Conflit (username déjà pris)
422 Unprocessable Entity Validation échouée
500 Internal Server Error Erreur serveur

Exemples d'Erreurs

400 - Bad Request

{
  "error": "Données invalides",
  "details": "Le champ 'username' est requis"
}

401 - Unauthorized

{
  "error": "Token invalide ou expiré",
  "message": "Veuillez vous reconnecter"
}

403 - Forbidden

{
  "error": "Accès refusé",
  "message": "Vous n'avez pas les permissions nécessaires"
}

403 - Checkout bloqué (amende en attente)

{
  "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

{
  "error": "Ressource introuvable",
  "message": "Commande avec l'ID 9999 introuvable"
}

409 - Conflict

{
  "error": "Conflit",
  "message": "Ce nom d'utilisateur est déjà pris"
}

422 - Unprocessable Entity

{
  "error": "Validation échouée",
  "details": {
    "field": "telephone",
    "message": "Format de téléphone invalide"
  }
}

500 - Internal Server Error

{
  "error": "Erreur serveur interne",
  "message": "Une erreur inattendue s'est produite"
}

📝 Notes Importantes

Sécurité

  • Tokens JWT : Expiration variable selon le rôle (Client: 5h · Admin/Livreur/Cabine: 10h)
  • Validation GPS : Requis pour certaines actions de livraison
  • Filtrage des données : Les livreurs n'ont pas accès aux téléphones clients
  • Rate Limiting : Protection contre les abus

Format des Données

  • Dates : Format ISO 8601 (2025-01-18T14:30:00Z)
  • Coordonnées : Latitude/Longitude en décimal (WGS84)
  • Prix : En euros avec 2 décimales
  • Téléphones : Format international (+33612345678)

Workers Automatiques

  • Auto-Assignment Cron : Toutes les 1 minute (workers/cron_auto_assign.go)
  • Queue Cleanup : Toutes les 5 minutes
  • ETA Updates : Toutes les 30 secondes
  • Stock Cleanup : Toutes les 5 minutes

Fonctionnalités Avancées

  • Assignation GPS automatique au checkout
  • Calcul ETA temps réel avec TomTom API
  • Système de queues optimisé pour les livreurs
  • Nettoyage automatique des commandes invalides
  • Notifications temps réel via Redis Pub/Sub

📋 Changelog

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 (ResolveAddressGeocodeAddress) : 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 (arrivedcancelled), 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
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

S
Description
No description provided
Readme
154 MiB
Languages
TypeScript 59.3%
Go 33.9%
CSS 6.3%
Python 0.3%