Documentation technique et fonctionnelle complète de la plateforme UrbaFood — plateforme municipale de livraison alimentaire équitable.
v1.7.0NestJS 10Next.js 14MariaDB 11.5📱 PWA Vite 5Mise à jour : 20 février 2026
🏛️ Vue d'ensemble
Présentation
UrbaFood est une plateforme municipale de livraison alimentaire conçue pour soutenir l'économie locale. Contrairement aux plateformes commerciales (Uber Eats, Deliveroo…) qui prélèvent 25–30% de commission, UrbaFood prélève 1% seulement, uniquement pour couvrir les frais de maintenance.
💡L'argent reste dans le territoire local. Chaque euro dépensé bénéficie directement aux restaurateurs et coursiers de la commune.
Objectifs
Objectif
Description
Équité économique
Commission de 1% pour les restaurateurs vs 25–30% sur les plateformes commerciales
Rémunération juste
Les coursiers reçoivent une rémunération transparente et équitable
Économie locale
100% des revenus restent dans le territoire municipal
Gouvernance publique
Plateforme sans actionnaires ni pression de rentabilité commerciale
L'architecture suit un modèle en couches avec HAProxy comme point d'entrée unique, délégant vers Nginx qui route vers le backend ou le frontend selon le chemin de la requête.
Connexion HAProxy ↔ Nginx. Ce réseau est partagé avec les autres projets du serveur.
internal_network
Interne (bridge)
Communication entre services UrbaFood uniquement (backend, DB, redis, worker)
⚠️Le container Nginx doit s'appeler urbafood_web et écouter sur le port 80 — configuré en dur dans HAProxy.
🗄️ Base de données
Schéma
MariaDB 11.5, moteur InnoDB, charset UTF8MB4 (support émojis), timestamps UTC, soft deletes sur toutes les tables (deleted_at).
ℹ️Le schéma est initialisé via /srv/urbafood/database/init/01-schema.sql et les seeds via 02-seeds.sql. TypeORM est configuré avec synchronize: false — toute modification passe par SQL ou migration.
Tables
Table
Rôle
Clés importantes
users
Tous les utilisateurs de la plateforme
email UNIQUE, role ENUM (CLIENT/RESTAURANT/COURIER/ADMIN)
Recherche plein-texte — nom restaurant, plat, catégorie, ingrédients. city inclut les communes limitrophes (même département).
GET
/restaurants/categories
Non
Liste des catégories de plats distinctes (pour les chips de filtre)
GET
/restaurants/my
JWT
Restaurants du restaurateur connecté
POST
/restaurants
JWT RESTAURANT
Créer un restaurant
PUT
/restaurants/:id
JWT propriétaire
Modifier le restaurant
GET
/restaurants/:id/stats
JWT propriétaire
Statistiques du restaurant
Plats
Méthode
Route
Auth
Description
GET
/restaurants/:rId/dishes
JWT
Liste groupée par catégorie
POST
/restaurants/:rId/dishes
JWT propriétaire
Ajouter un plat
PUT
/restaurants/:rId/dishes/:id
JWT propriétaire
Modifier un plat
PATCH
/restaurants/:rId/dishes/:id/toggle
JWT propriétaire
Activer / désactiver
DELETE
/restaurants/:rId/dishes/:id
JWT propriétaire
Soft delete
Commandes
Méthode
Route
Auth
Description
GET
/orders/restaurant/:rId
JWT
Liste commandes avec filtre ?status=
GET
/orders/restaurant/:rId/dashboard
JWT
KPIs du tableau de bord
PATCH
/orders/:id/advance
JWT propriétaire
Avancer au statut suivant
POST
/orders/:id/cancel
JWT propriétaire
Annuler + raison
Google Business Profile
Ces endpoints permettent à un restaurateur de connecter et synchroniser sa fiche Google Business Profile avec son restaurant UrbaFood. L'OAuth Google est géré côté backend — aucun secret n'est exposé au frontend.
Méthode
Route
Auth
Description
GET
/restaurants/:id/google/auth
JWT
Initie le flux OAuth → redirige vers Google (ou ?format=json pour obtenir l'URL)
GET
/restaurants/google/callback
Public
Reçoit le code Google, importe les données, redirige vers le frontend
GET
/restaurants/:id/google/sync
JWT
Force une re-synchro des données GBP (refresh token auto)
GET
/restaurants/:id/google/status
JWT
Statut de la connexion Google (connecté, dernière synchro, expiration token)
ℹ️Les tokens Google sont chiffrés en AES-256-GCM avant stockage. Le state OAuth est signé HMAC-SHA256 avec une durée de vie de 10 minutes — aucune session serveur n'est nécessaire.
Coursiers
Tous les endpoints coursiers nécessitent un token JWT valide (role: COURIER recommandé). Le profil coursier est créé juste après l'inscription via POST /couriers/register.
Méthode
Route
Auth
Description
POST
/couriers/register
JWT
Créer ou récupérer le profil coursier (vehicleType, licensePlate)
GET
/couriers/me
JWT
Profil complet du coursier connecté (avec données user)
PATCH
/couriers/me
JWT
Modifier véhicule et plaque d'immatriculation
PATCH
/couriers/availability
JWT
Basculer disponible / hors ligne
GET
/couriers/orders/available
JWT
Commandes READY sans coursier assigné (triées par date)
GET
/couriers/orders/my?filter=active|history
JWT
Mes livraisons — active : picked_up / history : delivered + cancelled
POST
/couriers/orders/:id/accept
JWT
Accepter une livraison → courier_id assigné, status → picked_up. 409 si déjà actif.
POST
/couriers/orders/:id/deliver
JWT
Marquer livrée → status → delivered, deliveredAt = NOW(), totalDeliveries++
GET
/couriers/stats
JWT
Stats du jour : livraisons, gains, commande active, note moyenne, isAvailable
💡La documentation interactive complète (Swagger) est disponible ci-dessous dans la section API Reference.
📖 API Reference (Swagger)
L'interface Swagger UI générée automatiquement par NestJS documente l'intégralité des endpoints disponibles, avec schémas de requête/réponse, codes d'erreur et possibilité de tester chaque route directement depuis le navigateur.
💡Pour tester les routes protégées, cliquez sur Authorize (cadenas) dans l'interface ci-dessous et collez votre accessToken obtenu via POST /api/auth/login.
Le système utilise deux tokens : un access token (15 min) pour les requêtes API, et un refresh token (7 jours) pour renouveler l'accès sans re-authentification. Le renouvellement est automatique et transparent côté frontend — lib/api.ts intercepte les 401, appelle POST /auth/refresh, puis rejoue la requête initiale.
1. Inscription → /login (role: RESTAURANT)
2. Créer restaurant → /restaurant/profil
└─ En attente validation municipale (is_validated: false)
3. Ajouter menu → /restaurant/menu
└─ Plats groupés par catégorie
└─ Toggle disponibilité en temps réel
4. Traiter commandes → /restaurant/commandes
└─ pending → confirmer → préparer → prêt
5. Suivre revenus → /restaurant/statistiques
└─ CA brut, commission 1%, net, panier moyen
Parcours coursier
💡Le parcours coursier est disponible en deux versions : l'espace Next.js intégré (/coursier/*) et la PWA installable (/coursier-app/) — optimisée mobile avec mode hors-ligne et icône sur l'écran d'accueil.
1. Découverte → /rejoindre-coursier (page marketing)
2. Inscription → /login?mode=register&role=COURIER (Next.js)
ou directement sur https://urbafood.me/coursier-app/ (PWA)
└─ Sélection du véhicule (vélo, scooter, voiture)
└─ POST /couriers/register appelé automatiquement
└─ Redirection vers /coursier (Next.js) ou / (PWA)
3. Dashboard → /coursier (Next.js) | /coursier-app/ (PWA)
└─ Toggle disponible / hors ligne (PATCH /couriers/availability)
└─ Stats du jour : livraisons, gains, note
└─ Aperçu des livraisons disponibles (3 premières)
4. Accepter une livraison → /coursier/livraisons | /coursier-app/livraisons
└─ GET /couriers/orders/available (statut READY, courier_id IS NULL)
└─ POST /couriers/orders/:id/accept
→ courier_id = courseId, status → picked_up, picked_up_at = NOW()
→ 409 si le coursier a déjà une livraison active
5. Suivre la livraison → /coursier/en-cours | /coursier-app/en-cours
└─ Timeline : récupération au restaurant → livraison en cours
└─ POST /couriers/orders/:id/deliver
→ status → delivered, delivered_at = NOW(), totalDeliveries++
6. Consulter les gains → /coursier/gains | /coursier-app/gains
└─ Filtre aujourd'hui / 7 jours / 30 jours
└─ 100 % des frais de livraison reversés au coursier
7. Gérer le profil → /coursier/profil | /coursier-app/profil
└─ Modifier véhicule, plaque
└─ Voir note moyenne, total livraisons
💡Le coursier reçoit 100 % des frais de livraison. UrbaFood prélève uniquement 1 % du montant commande, côté restaurant — jamais sur les gains du coursier.
Historique gains par période (aujourd'hui / 7j / 30j)
/coursier/profil
Profil coursier
COURIER
Véhicule, stats, toggle dispo, déconnexion
/docs
Documentation
Non
Cette page — inclut Swagger UI embarqué
/api/docs
Swagger UI
Non
Documentation API interactive (NestJS, aussi accessible depuis /docs)
Système de design
Token
Valeur
Usage
Primary Blue
#1E3A8A
Titres, structure, sidebar, liens actifs
Action Orange
#F97316
Boutons CTA uniquement
Accent Green
#16A34A
Badges positifs, disponibilité, checkmarks
Light Grey
#F3F4F6
Fonds de sections alternées
Border
#E5E7EB
Séparateurs, bordures cartes
Border Radius Card
12px
Toutes les cartes
Border Radius Button
8px
Tous les boutons
Font
Inter
Poids 400–900, Google Fonts
📱 PWA Coursiers
Présentation
La PWA Coursiers est une application Progressive Web App installable sur Android et iOS, accessible à https://urbafood.me/coursier-app/. Elle offre une expérience native — plein écran sans barre Chrome, icône sur l'écran d'accueil, fonctionnement partiel hors-ligne — tout en étant une application web standard.
💡La PWA est distincte de l'espace coursier Next.js (/coursier/*) : elle est optimisée pour les appareils mobiles, supporte le mode hors-ligne via Service Worker (Workbox), et peut être installée comme une app native depuis Chrome Android.
Toggle disponibilité, stats du jour, aperçu commandes disponibles
Livraisons dispo
/livraisons
Liste des commandes READY à accepter (auto-refresh 30 s)
En cours
/en-cours
Livraison active : timeline + bouton « Livrée »
Gains
/gains
Historique des gains par période (jour / 7 j / 30 j)
Profil
/profil
Véhicule, plaque, toggle disponibilité, logout
Installation Android / iOS
Android (Chrome) :
1. Ouvrir https://urbafood.me/coursier-app/ dans Chrome Android
2. Se connecter avec un compte COURIER
3. Chrome affiche automatiquement le bandeau "Installer l'application"
4. Ou : menu ⋮ → "Installer l'application" / "Ajouter à l'écran d'accueil"
5. L'icône UrbaFood Coursier apparaît sur l'écran d'accueil
6. Au lancement : mode plein écran sans barre de navigation Chrome
⚠️HTTPS obligatoire — le Service Worker ne s'enregistre qu'en contexte sécurisé. En production, HAProxy gère le certificat Let's Encrypt.
Critères d'installabilité (Chrome Android) :
Critère
Statut
Servi en HTTPS
✅ HAProxy + Let's Encrypt
manifest.webmanifest valide
✅ vite-plugin-pwa (généré automatiquement)
display: standalone
✅
Service Worker enregistré
✅ Workbox (generateSW)
Icône ≥ 192×192
⚠️ SVG par défaut — exécuter npm run generate-icons pour PNG
start_url accessible
✅
Stack & architecture
Technologie
Version
Rôle
Vite
5
Bundler + dev server
React
18
UI
TypeScript
5
Typage statique
React Router
6
Navigation SPA avec basename=/coursier-app/
vite-plugin-pwa
0.20
Service Worker (Workbox) + manifest automatiques
Workbox
7
Stratégies de cache : CacheFirst assets, NetworkFirst API
Comportement hors-ligne :
Ressource
Stratégie
TTL
Assets statiques (JS, CSS, HTML, SVG)
CacheFirst (précache)
Permanent (versioning par hash)
Appels API /api/*
NetworkFirst
5 minutes
ℹ️L'app reste partiellement fonctionnelle sans réseau : les données du dernier chargement sont affichées depuis le cache. Les actions (accepter, livrer) nécessitent une connexion.
Auth JWT : même système que le frontend principal — uf_token (15 min), uf_refresh_token (7 j), refresh automatique sur 401 dans src/api.ts.