Skip to Content
Gestion des Adresses

Gestion des Adresses

Ce document présente l’architecture, la logique métier et la spécification des API du module de gestion des adresses (apps/api/src/addresses). Ce module permet aux utilisateurs (acheteurs, vendeurs, administrateurs) de gérer leurs adresses de livraison et de facturation, indispensables pour passer des commandes.


1. Modèle de Données et Relations

Les adresses sont associées à un utilisateur unique et peuvent être référencées dans plusieurs commandes (Order). La structure repose sur l’entité Address définie dans le schéma Prisma :

Description des Champs de l’Entité Address

  • id (String, UUID) : Identifiant unique de l’adresse.
  • userId (String) : Identifiant de l’utilisateur propriétaire de l’adresse.
  • addressLine1 (String) : Ligne d’adresse principale (ex: “123 Rue de Paris”).
  • addressLine2 (String, optionnel) : Complément d’adresse (ex: “Appartement 4B”).
  • city (String) : Ville.
  • province (String, optionnel) : Région, département ou État.
  • country (String) : Pays en texte libre — champ historique, conservé tel quel pour compatibilité.
  • countryId (String, optionnel) : Relation vers Country — source de vérité pour tout calcul (TVA, frais de port, filtrage catalogue). Peut rester non résolue (null) pour les adresses créées avant l’introduction du modèle Country. Voir Gestion des Pays.
  • postalCode (String, optionnel) : Code postal.
  • latitude / longitude (Float, optionnels) : Géocodées automatiquement (best-effort, jamais bloquant) à partir de ville/province/pays. Voir §5 ci-dessous.
  • isDefault (Boolean) : Indique s’il s’agit de l’adresse par défaut de l’utilisateur.
  • createdAt / updatedAt (DateTime) : Métadonnées d’audit.

[!IMPORTANT] country (texte) vs countryId (relation) : les deux ne doivent jamais diverger. Si un countryId est fourni à la création, le nom du Country correspondant fait foi pour le champ texte libre country (voir addresses.service.ts) — mais l’inverse n’est jamais recalculé automatiquement.


2. Rôles et Autorisations (RBAC)

Tous les endpoints de la gestion des adresses sont protégés par le garde d’authentification JwtAuthGuard. De plus, l’accès à une ressource spécifique est restreint à son propriétaire :

EndpointMéthodeRôle RequisDescription / Règle métier
/addressesPOSTConnectéCréer une nouvelle adresse pour le compte utilisateur.
/addressesGETConnectéLister toutes les adresses associées à l’utilisateur connecté.
/addresses/:idGETPropriétaireObtenir le détail d’une adresse spécifique.
/addresses/:idPATCHPropriétaireModifier les détails d’une adresse ou la définir par défaut.
/addresses/:idDELETEPropriétaireSupprimer une adresse (avec transfert éventuel de l’adresse par défaut).

[!IMPORTANT] Contrôle de Propriété : Le système vérifie systématiquement que le userId de l’adresse ciblée correspond au userId extrait du JWT (address.userId === currentUserId). Si ce n’est pas le cas, une exception ForbiddenException (403) est renvoyée.


3. Logiques Métier Clés et Flux

A. Flux de Création d’une Adresse (POST /addresses)

Lorsqu’un utilisateur crée une adresse, le système applique la logique suivante dans une transaction :

  1. Comptage des adresses existantes de l’utilisateur.
  2. Si le compteur est égal à 0 ou si le payload spécifie "isDefault": true :
    • L’adresse en cours de création est marquée comme adresse par défaut.
    • Les autres adresses de cet utilisateur sont modifiées pour passer isDefault à false.
  3. Sinon, l’adresse est créée normalement.

B. Algorithme de Mutation de l’Adresse par Défaut (isDefault)

La gestion de l’état isDefault requiert des transactions Prisma pour garantir qu’un utilisateur possède toujours exactement une adresse par défaut (si sa liste d’adresses n’est pas vide).

1. À la modification (PATCH /addresses/:id)

Si l’utilisateur change isDefault à true :

  • Les autres adresses de l’utilisateur passent à false. Si l’utilisateur tente de passer isDefault à false sur son adresse par défaut actuelle :
  • Si l’utilisateur possède une autre adresse, le système promeut automatiquement l’une des autres adresses comme étant par défaut.
  • Si c’est sa seule adresse, elle reste par défaut (isDefault est forcé à true).

2. À la suppression (DELETE /addresses/:id)

Si l’adresse supprimée était l’adresse par défaut :

  • Le service recherche l’adresse restante la plus récente de l’utilisateur (triée par createdAt desc) et la définit comme adresse par défaut.

Code d’implémentation clé (dans addresses.service.ts) :

// Extrait de la mise à jour d'une adresse par défaut let finalIsDefault = dto.isDefault; if (dto.isDefault === false && address.isDefault) { const otherAddress = await tx.address.findFirst({ where: { userId, NOT: { id } }, }); if (otherAddress) { await tx.address.update({ where: { id: otherAddress.id }, data: { isDefault: true }, }); } else { finalIsDefault = true; } }

4. Géocodage automatique (best-effort)

À la création ou modification d’une adresse, GeocodingService tente de résoudre latitude/longitude à partir de city + province + country (le nom résolu, pas le texte brut si un countryId est fourni), via Nominatim (OpenStreetMap) — gratuit, sans clé API.

[!WARNING] Best-effort, jamais bloquant : un échec de géocodage (service indisponible, adresse trop imprécise, quota dépassé) ne doit jamais empêcher la création/modification de l’adresse — latitude/longitude restent simplement null. Une adresse sans coordonnées n’apparaît pas comme repère sur la carte publique des producteurs (voir Annuaire public des vendeurs & carte producteurs), mais reste parfaitement valide pour la livraison.

[!NOTE] Nominatim impose un maximum d’1 requête/seconde et exige un User-Agent identifiable — respecté ici avec un appel unique par création/modification. Le script de rattrapage backfill-geocoding.ts applique le même délai pour géocoder en lot les adresses existantes qui n’ont pas encore de coordonnées.


5. Guide des Endpoints et Exemples de Payloads

Création d’une Adresse (POST /addresses)

  • Requête :
    { "addressLine1": "456 Avenue de la République", "addressLine2": "Bâtiment C, Appt 12", "city": "Lyon", "province": "Auvergne-Rhône-Alpes", "country": "France", "countryId": "cty-fr-uuid", "postalCode": "69002", "isDefault": true }
  • Réponse (201 Created) :
    { "id": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d", "userId": "usr-999-default-uuid", "addressLine1": "456 Avenue de la République", "addressLine2": "Bâtiment C, Appt 12", "city": "Lyon", "province": "Auvergne-Rhône-Alpes", "country": "France", "countryId": "cty-fr-uuid", "postalCode": "69002", "latitude": 45.764043, "longitude": 4.835659, "isDefault": true, "createdAt": "2026-06-24T22:00:00.000Z", "updatedAt": "2026-06-24T22:00:00.000Z" }

Liste des Adresses (GET /addresses)

  • Réponse (200 OK) :
    [ { "id": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d", "userId": "usr-999-default-uuid", "addressLine1": "456 Avenue de la République", "addressLine2": "Bâtiment C, Appt 12", "city": "Lyon", "province": "Auvergne-Rhône-Alpes", "country": "France", "postalCode": "69002", "isDefault": true, "createdAt": "2026-06-24T22:00:00.000Z", "updatedAt": "2026-06-24T22:00:00.000Z" } ]

Modification d’une Adresse (PATCH /addresses/:id)

  • Requête (ex: mise à jour partielle et modification du statut de défaut) :
    { "addressLine2": "Bâtiment C, Appt 14 (Nouveau)", "isDefault": false }
  • Réponse (200 OK) :
    { "id": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d", "userId": "usr-999-default-uuid", "addressLine1": "456 Avenue de la République", "addressLine2": "Bâtiment C, Appt 14 (Nouveau)", "city": "Lyon", "province": "Auvergne-Rhône-Alpes", "country": "France", "postalCode": "69002", "isDefault": false, "createdAt": "2026-06-24T22:00:00.000Z", "updatedAt": "2026-06-24T22:15:00.000Z" }

Suppression d’une Adresse (DELETE /addresses/:id)

  • Réponse (204 No Content) : (Aucun contenu renvoyé)
Last updated on