Architecture·8 min de lecture·1,421 mots

Architecture d'intégration

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.

SurfaceIdentifierStorageLecture
Web adminCookie superapp_session httpOnlyBrowser cookie partagé .wari.pro via proxy_cookie_path Nginxcookies().get("superapp_session")
Web clientIdemIdemIdem
MobileBearer JWTexpo-secure-store + cache mémoireHeader Authorization: Bearer <jwt>
TousMê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)/activite

Best-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 ICS

Helper : 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 :9000

Ne 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 cours

Mobile 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/markets lit 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

ServiceUsageVariables env
ResendEmails transactionnelsRESEND_API_KEY, RESEND_FROM
VonageSMS OTP (partiel V1)VONAGE_API_KEY, VONAGE_API_SECRET
AnthropicVision search V1ANTHROPIC_API_KEY
HuggingFaceCLIP embeddings (search image V2)HUGGINGFACE_TOKEN
Google Generative AISnap-to-list OCR (Gemini)GEMINI_API_KEY
PayDunyaPaiements RDV (sandbox V1)(helper lib/paydunya.ts)
PostHogAnalyticsPOSTHOG_KEY, POSTHOG_HOST
SentryMonitoringSENTRY_DSN, SENTRY_AUTH_TOKEN
Cloudflare StreamVidéos vitrines/prestationsCLOUDFLARE_STREAM_TOKEN
FCM (Firebase)Push Androidgoogle-services.json mobile
APNs (Apple)Push iOScert 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.ts doit 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-224
  • architecture-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.