Integration Architecture — backend ↔ mobile
Les 2 parts communiquent principalement via REST + JWT (Bearer mobile, cookie web) et push notifications (Expo + FCM + APNs). Aucune dépendance directe au build — chaque part est compilable et déployable indépendamment.
Schéma d'intégration
┌─────────────────────────────────────────────────────────────┐
│ Mobile (Expo SDK 54) Web (browsers) │
│ - Espace client (5 onglets) - Vitrines *.wari.pro │
│ - Espace gérant (vitrine-pro/) - /admin (gérant) │
│ - /superadmin │
└──────────────────┬──────────────────────────┬───────────────┘
│ Bearer JWT │ Cookie superapp_session
│ /api/mobile/* │ (httpOnly, .wari.pro)
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Backend Next.js 16.2 (single process) │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ lib/auth/session.ts │ │
│ │ getSessionFromRequest(req) │ │
│ │ • lit cookie superapp_session OU │ │
│ │ • lit Authorization: Bearer <jwt> │ │
│ │ • même payload { userId, role, tenantId? } │ │
│ │ • même secret NEXTAUTH_SECRET │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │ /api/mobile/* │ │ /api/admin/*│ │ /api/ │ │
│ │ 186 routes │ │ ~50 routes │ │ superadmin/* │ │
│ └────────────────┘ └─────────────┘ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────┐ │
│ │ Prisma 7.6 (multi-tenant by row)│ │
│ │ 72 models, tenantId everywhere │ │
│ └──────────────────────────────────┘ │
│ │ │
│ ┌──────────┐ ┌──────────┐ ▼ ┌──────────┐ ┌──────────┐ │
│ │PostgreSQL│ │ Redis │ │ MinIO │ │Typesense │ │
│ │ 15 │ │ 7 │ │ (S3) │ │ 0.25 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
│ │
│ outbound │ outbound
▼ ▼
Resend expo-server-sdk
Vonage Anthropic Vision
PostHog HuggingFace CLIP
Sentry PayDunya (sandbox)
Cloudflare Stream
Google Generative AI (Gemini)Points d'intégration détaillés
1. Auth unifiée (cookie + Bearer)
Décision pivot DEC-107 : routes séparées /api/admin (web) vs /api/mobile (Bearer) pour ne pas casser le web en touchant le mobile.
| Surface | Identifier | Storage | Lecture |
|---|---|---|---|
| Web admin | Cookie superapp_session httpOnly | Browser cookie partagé .wari.pro via proxy_cookie_path Nginx | cookies().get("superapp_session") |
| Web client | Idem | Idem | Idem |
| Mobile | Bearer JWT | expo-secure-store + cache mémoire | Header Authorization: Bearer <jwt> |
| Tous | Même JWT HS256, même secret NEXTAUTH_SECRET, même payload { userId, role, tenantId? } |
Helper unique : app/src/lib/auth/session.ts.getSessionFromRequest(req). Ne jamais bypasser — tous les écrans/routes y passent. C'est ce qui rend possible un seul backend pour 3 surfaces (web client, web admin, mobile).
2. Routes mobile dédiées
Préfixe /api/mobile/* (186 routes). Aucune route mobile ne touche /api/admin/* ou /api/superadmin/* (et inversement).
- Public mobile : lecture vitrines + produits + prestations + home + search + paiement modes + dispos + restaurant + ical
- Bearer CLIENT : commandes, réservations, abonnements, acces, push-tokens, conversations, wishlist
- Bearer TENANT_ADMIN : vitrine/* (dashboard, parametres, commandes, catalogue, services, clientes, acces, medias, restaurant/*, reservations, creneaux, stories)
Voir api-contracts-backend.md.
3. Push notifications (Expo + FCM + APNs)
[Mobile boot]
useNotifications hook → register
↓
Expo push service → token "ExponentPushToken[xxx]"
↓
POST /api/mobile/push-tokens/register { token, platform }
↓
Backend stocke selon session.role :
TENANT_ADMIN/SUPER_ADMIN → PushToken.userId
CLIENT → PushToken.clientAccountId
CHECK constraint XOR (BUG-124)
[Backend trigger event]
ex. POST /api/mobile/commandes (création)
↓
lib/push.ts sendPushToTenantAdmin(tenantId, payload)
lib/push.ts sendPushToClient(clientAccountId, payload)
↓
expo-server-sdk → Expo push service → APNs (iOS) / FCM (Android)
↓
[Mobile receive]
useNotifications.addNotificationResponseReceivedListener
↓
Routing selon data.type :
"commande.confirmee" / "commande.statut" → /(tabs)/activite (CLIENT)
/(tabs)/compte → setSelectedCommande (TENANT_ADMIN)
"paiement.valide" → /(tabs)/activite
"acces.approuve" → /vitrine/[slug]
"story.publiee" → /vitrine/[slug] (bandeau story)
"rdv.confirme" / "rdv.rappel" → /(tabs)/activiteBest-effort partout (try/catch sur chaque send, cleanup auto DeviceNotRegistered).
4. iCal sync (calendrier client + gérant)
[Gérant active sync]
POST /api/mobile/vitrine/parametres/ical { regenerate?: true }
↓
Backend génère icalToken UUID v4 et update Tenant
↓
Mobile affiche URL stable wari.pro/api/ical/<token>
↓
Partage via Share natif / QR code → Google/Apple/Outlook
↓
Subscribers polent /api/ical/<token> (Cache-Control: public, max-age=300)
[Client réserve]
POST /api/mobile/reservations → Reservation créée
Reservation.icalToken auto-généré (@default uuid)
↓
Email Resend envoyé avec bouton "📅 Ajouter au calendrier"
→ https://wari.pro/api/ical/reservation/<icalToken>
↓
Tap → app calendrier ouvre single-event ICSHelper : app/src/lib/ical.ts (RFC 5545 strict, line folding, escape, VALARM -24h + -2h).
5. Stockage médias (MinIO)
[Mobile/Web upload]
POST /api/mobile/vitrine/medias/upload (Bearer TENANT_ADMIN)
↓
Backend lib/minio/ : presign URL S3 + sharp (resize/convert)
↓
Media created (orphelin si pas encore attaché à un produit/prestation/plat)
↓
POST produit/prestation/plat avec mediaIds[] → relation FK
Cron cleanup médias orphelins (à activer V2)
[Lecture]
Mobile/Web affiche https://wari.pro/medias/<key> (proxy Nginx)
↓
Nginx forwarde vers minio container interne :9000Ne jamais exposer les URLs internes MinIO directement (CLAUDE.md).
6. Vitrine builder (web → vitrines publiques)
[Gérant édite /admin/vitrine/builder]
Zustand store local (`use-builder-store.ts`)
↓
Autosave debounced → PATCH /api/admin/vitrine/snapshots/[id]
↓
VitrineSnapshot.statut = DRAFT
isDirty calculé client-side via fast-deep-equal
[Publication]
POST /api/admin/vitrine/snapshots/[id]/publish
↓
Snapshot draft devient PUBLISHED (bump version)
[Lecture publique]
GET https://kady.wari.pro/page-slug
↓
src/app/[slug]/page.tsx → lecture VitrineSnapshot(statut=PUBLISHED)
↓
Renderer composants/vitrine/vitrine-page.tsx
Sections legacy protégées par guards hasBuilderXXX (NE PAS SUPPRIMER)
[Preview live]
GET https://kady.wari.pro/page-slug?preview=live
↓
Lecture DB live (non snapshot) → reflète état builder en coursMobile builder = WebView qui charge /admin/vitrine/builder directement (page web embedded) — pattern simple sans re-implémenter la logique côté natif.
7. International Foundation
- Backend : table
Market(24 markets ISO Wave 1-8 BF→UEMOA→CEDEAO→EU→Maghreb→US→Lusophonie) - Backend :
lib/money.ts(Money pattern multi-devises) - Backend :
lib/i18n/(next-intl) - Mobile : i18next + react-i18next + expo-localization
- Routes :
/api/mobile/marketslit liste, mobile choisit market au boot
8. Search (texte + image)
[Search texte V1]
Mobile : SearchOverlay → POST /api/mobile/search?q=...
Backend : lookup Produit/Prestation full-text + relations Tenant
[Search image V1 — Anthropic Vision]
Mobile : expo-camera → photo → upload → /api/mobile/search/image
Backend : lib/anthropic-vision.ts → Claude vision API → match keywords/categories
[Search image V2 — Option A déployée 2026-05-18/19]
Mobile photo → /api/mobile/search/image
Backend : lib/embedding.ts → HuggingFace CLIP API → embedding vector
Backend : lib/typesense.ts → search vector index (Typesense 0.25)
Container Typesense Docker (port 8108 loopback)9. Services externes
| Service | Usage | Variables env |
|---|---|---|
| Resend | Emails transactionnels | RESEND_API_KEY, RESEND_FROM |
| Vonage | SMS OTP (partiel V1) | VONAGE_API_KEY, VONAGE_API_SECRET |
| Anthropic | Vision search V1 | ANTHROPIC_API_KEY |
| HuggingFace | CLIP embeddings (search image V2) | HUGGINGFACE_TOKEN |
| Google Generative AI | Snap-to-list OCR (Gemini) | GEMINI_API_KEY |
| PayDunya | Paiements RDV (sandbox V1) | (helper lib/paydunya.ts) |
| PostHog | Analytics | POSTHOG_KEY, POSTHOG_HOST |
| Sentry | Monitoring | SENTRY_DSN, SENTRY_AUTH_TOKEN |
| Cloudflare Stream | Vidéos vitrines/prestations | CLOUDFLARE_STREAM_TOKEN |
| FCM (Firebase) | Push Android | google-services.json mobile |
| APNs (Apple) | Push iOS | cert prod via eas credentials |
Data flow critique : création de commande
[Mobile client tape "Commander"]
Panier local Zustand → si !auth → /auth (sauf configAcces.commandeSansCompte=true)
↓
POST /api/mobile/commandes Bearer CLIENT
Body: { tenantId, lignes:[{produitId, varianteId?, quantite}], modePaiement, refPaiement?, addresseLivraison? }
↓
[Backend transaction Prisma]
1. Lookup produits + variantes, vérifie stock IMMEDIATE
2. Create Commande + CommandeLigne[] (snapshot prix au moment)
3. Decrement Produit.stock (marque disponibilite=indispo si stock=0)
4. Commit transaction
↓
[Hooks best-effort try/catch]
- sendPushToTenantAdmin(tenantId, "Nouvelle commande #abc")
- sendPushToClient(clientAccountId, "Commande confirmée")
- envoyerEmailCommande (Resend) → tenant.email + client.email
↓
Response 201 { commande }
↓
[Mobile]
WhatsApp deep link wa.me/<num>?text=<message structuré commande>
clearCart() + router.replace('/(tabs)/activite')
[Plus tard — gérant change statut]
PATCH /api/mobile/vitrine/commandes/[id] { statut: "CONFIRMEE" } Bearer TENANT_ADMIN
↓
Backend update + sendPushToClient { type: "commande.statut", label: "Confirmée" }
↓
[Mobile client] notification → tap → /(tabs)/activite
[Encore plus tard — gérant valide paiement]
PATCH /api/mobile/vitrine/commandes/[id] { statutPaiement: "PAYE", refPaiement: "XYZ" }
↓
Backend envoie email "envoyerPaiementValide" + sendPushToClient
↓
[Mobile client] email + notif → tap "Visiter la boutique"Versioning & contrat API
- Aucun versioning explicite (
/api/v1/...) V1 — le mobile et le backend sont déployés sous le contrôle d'un seul user, OTA mobile rapide. - Cohérence types : mobile
types/api.tsdoit rester aligné avec routes backend. Manuel V1, à automatiser V2+ (codegen depuis Zod ?). - Breaking changes : éviter ; sinon coordonner avec une OTA mobile préalable qui supporte les 2 versions.
Documentation visuelle
Diagrammes Excalidraw maintenus dans app/Docs/ :
architecture-gerant.excalidraw— 239 éléments, TabBar dynamique modulesActifs, 4 onglets Réglages WP-232, zones restau WP-228 + accès WP-224architecture-client.excalidraw— 78 éléments, 5 onglets, niveaux d'accès, flows réservation/repas/contact, paiement USSD
⚠️ Règle (CLAUDE.md racine) : MAJ après chaque feature touchant ces espaces.