diff --git a/README.md b/README.md index c69a2329..cab44093 100644 --- a/README.md +++ b/README.md @@ -680,17 +680,20 @@ Tous les serveurs backend communiquent via un réseau WireGuard privé `10.0.0.0 ### 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 +```mermaid +graph TB + Internet((Internet)) -->|"80/443 public"| Prod["prod-uber — DMZ
185.103.166.119"] + Prod -->|"VPN (10.0.0.6)"| BDD["bdd-redis-prod — LAN
10.0.0.5"] + BDD --> PG["PostgreSQL :5432"] + BDD --> Redis["Redis :6379"] + + VPN["vpn-uber — 10.0.0.1
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 : @@ -826,6 +829,26 @@ 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 /
UploadMedia"] --> Check{"STORAGE_DRIVER"} + Check -->|"local (défaut)"| Local["LocalStorage
disque ./uploads"] + Check -->|"s3"| S3["S3Storage
RustFS (auto-hébergé)"] + + Delete["DeleteProduct /
DeleteMedia"] --> KeyCheck{"media.Key
rempli ?"} + KeyCheck -->|non| Local + KeyCheck -->|oui| S3 ``` --- @@ -1356,6 +1379,38 @@ 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é
(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 @@ -3106,6 +3161,13 @@ 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`.