Skip to content

Guide d'implémentation - Système de suivi MVLS pour Inspire

Destinataires : Équipe de développement Inspire
Date : Décembre 2024
Version : 1.0

Vue d'ensemble

Ce document décrit comment implémenter la réception et l'envoi des messages de suivi MVLS entre Inspire et DEMA1N via RabbitMQ. Le système permet aux jeunes et aux bénévoles MVLS de répondre aux checkpoints directement depuis la plateforme Inspire.


Architecture

┌─────────────┐                    ┌─────────────┐
│   DEMA1N    │                    │   Inspire   │
│  (Backend)  │                    │  (Backend)  │
└──────┬──────┘                    └──────┬──────┘
       │                                   │
       │ 1. SUIVI_CREATED                  │
       │───────────────────────────────────>│
       │    (RabbitMQ)                      │
       │                                   │
       │                                   │ 2. Email Brevo avec URL
       │                                   │    inspire-orientation.org/mentorat/suivi/...
       │                                   │    ────────────────────────────────────────>│
       │                                   │                                    (Utilisateur)
       │                                   │
       │                                   │ 3. Clic sur le lien
       │                                   │<─────────────────────────────────────────────│
       │                                   │                                    (Utilisateur)
       │                                   │
       │                                   │ 4. Formulaire rempli
       │                                   │<─────────────────────────────────────────────│
       │                                   │                                    (Utilisateur)
       │                                   │
       │                                   │ 5. SUIVI_UPDATE_FROM_INSPIRE
       │                                   │───────────────────────────────────────────────>│
       │                                   │    (RabbitMQ)                                 │
       │                                   │
       │ 6. SUIVI_UPDATED                  │
       │───────────────────────────────────>│
       │    (RabbitMQ)                      │
       │                                   │

1. Configuration RabbitMQ

Exchange et Routing Key

  • Exchange : dema1n
  • Routing Key : user___mvls
  • Queue : Créez votre propre queue pour écouter les messages (ex: Inspire_mvls)

Configuration de la connexion

// Exemple de configuration RabbitMQ (à adapter selon votre stack)
const rabbitmqConfig = {
  url: process.env.RABBIT_MQ_URL, // Ex: amqp://user:password@host:5672
  exchange: 'dema1n',
  routingKey: 'user___mvls',
  queue: 'Inspire_mvls'
};

2. Réception des messages SUIVI_CREATED

Format du message

Lorsqu'un suivi MVLS est créé dans DEMA1N, vous recevrez un message SUIVI_CREATED :

{
  "header": {
    "name": "SUIVI_CREATED",
    "model": "SUIVI",
    "event": "CREATED",
    "date": "2025-12-12T10:00:00.000Z",
    "requestId": "abc123",
    "uniqid": "xyz789",
    "VERSION": "1.0",
    "producer": "dema1n"
  },
  "body": {
    "suivi": {
      "suivi_id": "123",
      "binome_id": "456",
      "step_id": "789",
      "day_step": 5,
      "status": "EN_ATTENTE",
      "status_jeune": "EN_ATTENTE",
      "status_benevole": "EN_ATTENTE",
      "creation_date": "2025-12-12T10:00:00.000Z",
      "status_update_date": "2025-12-12T10:00:00.000Z"
    }
  }
}

Actions à effectuer

  1. Écouter les messages dans votre queue RabbitMQ avec le routing key user___mvls
  2. Filtrer les messages avec header.name === "SUIVI_CREATED"
  3. Extraire les données depuis msg.body.suivi
  4. Stocker les informations nécessaires pour afficher le formulaire :
  5. suivi_id : Identifiant unique du suivi (à conserver pour l'URL)
  6. binome_id : Identifiant du binôme
  7. day_step : Jour du checkpoint (0, 5, 20, 60, 90, 120, 150, 210, 240, 270, 300)
  8. status_jeune et status_benevole : Statuts initiaux (généralement 'EN_ATTENTE')

Exemple de code

// Exemple de handler RabbitMQ (à adapter selon votre stack)
async function handleSuiviCreated(msg: IMvlsRabbitMQMessage) {
  if (msg.header.name !== 'SUIVI_CREATED') {
    return;
  }

  const suivi = msg.body.suivi;

  // Stocker les informations du suivi dans votre base de données
  await storeSuiviInfo({
    suiviId: suivi.suivi_id,
    binomeId: suivi.binome_id,
    dayStep: suivi.day_step,
    statusJeune: suivi.status_jeune,
    statusBenevole: suivi.status_benevole,
    createdAt: suivi.creation_date
  });

  console.log(`[Inspire] Suivi créé: ${suivi.suivi_id} pour binôme ${suivi.binome_id}`);
}

3. Affichage du formulaire de suivi

Format des URLs dans les emails Brevo

Les emails Brevo envoyés par DEMA1N contiennent deux variables d'URL :

  • {{ params.contactok }} : URL pour répondre "Tout va bien" (RAS, actif = '1')
  • {{ params.nocontact }} : URL pour répondre "Il y a un problème" (Inactif, actif = '0')

Format des URLs :

https://inspire-orientation.org/mentorat/suivi/:token/:contactStatus/:suiviId/:type

Où : - token : Hash MD5 sécurisé généré par DEMA1N (différent pour chaque type de réponse) - contactStatus : 'jeune' ou 'benevole' - suiviId : Identifiant du suivi (reçu dans le message SUIVI_CREATED) - type : 'contactok' ou 'nocontact' selon le type de réponse

Actions à effectuer

  1. Extraire les paramètres de l'URL : ```typescript // Exemple avec une route Next.js/React // Route: /mentorat/suivi/[token]/[contactStatus]/[suiviId]/[type] const { token, contactStatus, suiviId, type } = router.query;

// type sera soit 'contactok' soit 'nocontact' ```

  1. Valider le token (optionnel mais recommandé pour la sécurité) : ```typescript // Reconstruire le hash attendu selon le type const expectedHash = md5(binomeId + '/suivi/' + suiviId + '/' + contactStatus + '/' + type);

if (token !== expectedHash) { // Token invalide, rediriger vers une page d'erreur return redirect('/erreur?message=Token invalide'); } ```

  1. Récupérer les informations du suivi depuis votre base de données : ```typescript const suiviInfo = await getSuiviInfo(suiviId);

if (!suiviInfo) { // Suivi non trouvé, rediriger vers une page d'erreur return redirect('/erreur?message=Suivi non trouvé'); } ```

  1. Déterminer le type de réponse selon le paramètre type :
  2. Si type === 'contactok' : L'utilisateur a cliqué sur le lien "Tout va bien" → actif = '1'
  3. Si type === 'nocontact' : L'utilisateur a cliqué sur le lien "Il y a un problème" → actif = '0'

  4. Afficher le formulaire de suivi :

  5. Si type === 'contactok' : Afficher un formulaire pré-rempli avec "Tout va bien" sélectionné
  6. Si type === 'nocontact' : Afficher un formulaire pré-rempli avec "Il y a un problème" sélectionné
  7. Objectif atteint (actif = '2') : Option supplémentaire "Nous avons atteint les objectifs" (disponible dans les deux cas)
  8. Commentaire (optionnel) : Champ texte pour ajouter un commentaire

Exemple de formulaire

// Exemple de composant React (à adapter selon votre stack)
function SuiviForm({ suiviId, contactStatus, token, type }) {
  // Déterminer la valeur par défaut selon le type d'URL
  const defaultActif = type === 'contactok' ? '1' : '0';
  const [actif, setActif] = useState<string>(defaultActif);
  const [comment, setComment] = useState<string>('');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    // Envoyer le message RabbitMQ (voir section suivante)
    await sendSuiviUpdate({
      suiviId,
      contactStatus,
      actif,
      comment
    });

    // Afficher un message de confirmation
    alert('Merci pour votre réponse !');
  };

  return (
    <form onSubmit={handleSubmit}>
      <h1>Point de suivi</h1>

      {type === 'contactok' && (
        <div>
          <p>Vous avez indiqué que tout va bien. Souhaitez-vous modifier votre réponse ?</p>
        </div>
      )}

      {type === 'nocontact' && (
        <div>
          <p>Vous avez indiqué qu'il y a un problème. Pouvez-vous nous en dire plus ?</p>
        </div>
      )}

      <div>
        <label>
          <input
            type="radio"
            name="actif"
            value="1"
            checked={actif === '1'}
            onChange={(e) => setActif(e.target.value)}
          />
          Tout va bien
        </label>
      </div>

      <div>
        <label>
          <input
            type="radio"
            name="actif"
            value="0"
            checked={actif === '0'}
            onChange={(e) => setActif(e.target.value)}
          />
          Il y a un problème
        </label>
      </div>

      <div>
        <label>
          <input
            type="radio"
            name="actif"
            value="2"
            checked={actif === '2'}
            onChange={(e) => setActif(e.target.value)}
          />
          Nous avons atteint les objectifs
        </label>
      </div>

      <div>
        <label>
          Commentaire (optionnel) :
          <textarea
            value={comment}
            onChange={(e) => setComment(e.target.value)}
          />
        </label>
      </div>

      <button type="submit">Envoyer</button>
    </form>
  );
}

4. Envoi des mises à jour via RabbitMQ

Format du message à envoyer

Lorsque l'utilisateur soumet le formulaire, vous devez envoyer un message SUIVI_UPDATE_FROM_INSPIRE :

{
  "header": {
    "name": "SUIVI_UPDATE_FROM_INSPIRE",
    "model": "SUIVI",
    "event": "UPDATED",
    "date": "2025-12-12T10:00:00.000Z",
    "requestId": "abc123",
    "uniqid": "xyz789",
    "VERSION": "1.0",
    "producer": "inspire"
  },
  "body": {
    "suivi_update": {
      "suivi_id": "789",
      "contact_status": "jeune",
      "actif": "1",
      "comment": "Tout se passe très bien !"
    }
  }
}

Champs obligatoires

  • suivi_id (string) : Identifiant du suivi reçu dans l'URL
  • contact_status (string) : 'jeune' ou 'benevole' (reçu dans l'URL)
  • actif (string) : '1' pour RAS, '0' pour Inactif, '2' pour objectif atteint
  • comment (string, optionnel) : Commentaire optionnel

Exemple de code pour envoyer le message

import * as uniqid from 'uniqid'; // ou votre bibliothèque pour générer des IDs uniques

async function sendSuiviUpdate({
  suiviId,
  contactStatus,
  actif,
  comment
}: {
  suiviId: string;
  contactStatus: 'jeune' | 'benevole';
  actif: '1' | '0' | '2';
  comment?: string;
}) {
  const message = {
    header: {
      name: 'SUIVI_UPDATE_FROM_INSPIRE',
      model: 'SUIVI',
      event: 'UPDATED',
      date: new Date().toISOString(),
      requestId: uniqid(),
      uniqid: uniqid(),
      VERSION: '1.0',
      producer: 'inspire'
    },
    body: {
      suivi_update: {
        suivi_id: suiviId,
        contact_status: contactStatus,
        actif: actif,
        comment: comment || null
      }
    }
  };

  // Publier le message dans RabbitMQ
  await rabbitmqChannel.publish(
    'dema1n',           // Exchange
    'user___mvls',      // Routing key
    message
  );

  console.log(`[Inspire] Message SUIVI_UPDATE_FROM_INSPIRE envoyé pour suivi ${suiviId}`);
}

Exemple avec amqplib (Node.js)

import amqp from 'amqplib';
import * as uniqid from 'uniqid';

async function sendSuiviUpdate({
  suiviId,
  contactStatus,
  actif,
  comment
}: {
  suiviId: string;
  contactStatus: 'jeune' | 'benevole';
  actif: '1' | '0' | '2';
  comment?: string;
}) {
  const connection = await amqp.connect(process.env.RABBIT_MQ_URL);
  const channel = await connection.createChannel();

  const message = {
    header: {
      name: 'SUIVI_UPDATE_FROM_INSPIRE',
      model: 'SUIVI',
      event: 'UPDATED',
      date: new Date().toISOString(),
      requestId: uniqid(),
      uniqid: uniqid(),
      VERSION: '1.0',
      producer: 'inspire'
    },
    body: {
      suivi_update: {
        suivi_id: suiviId,
        contact_status: contactStatus,
        actif: actif,
        comment: comment || null
      }
    }
  };

  await channel.assertExchange('dema1n', 'topic', { durable: true });
  await channel.publish(
    'dema1n',
    'user___mvls',
    Buffer.from(JSON.stringify(message)),
    { persistent: true }
  );

  await channel.close();
  await connection.close();
}

5. Réception des messages SUIVI_UPDATED

Format du message

Lorsqu'un suivi est mis à jour (soit depuis Inspire, soit depuis DEMA1N), vous recevrez un message SUIVI_UPDATED :

{
  "header": {
    "name": "SUIVI_UPDATED",
    "model": "SUIVI",
    "event": "UPDATED",
    "date": "2025-12-12T10:00:00.000Z",
    "requestId": "abc123",
    "uniqid": "xyz789",
    "VERSION": "1.0",
    "producer": "dema1n"
  },
  "body": {
    "suivi": {
      "suivi_id": "123",
      "binome_id": "456",
      "step_id": "789",
      "day_step": 5,
      "status": "ACTIF",
      "status_jeune": "RAS",
      "status_benevole": "EN_ATTENTE",
      "creation_date": "2025-12-12T10:00:00.000Z",
      "status_update_date": "2025-12-12T11:00:00.000Z"
    }
  }
}

Actions recommandées

  1. Écouter les messages avec header.name === "SUIVI_UPDATED"
  2. Mettre à jour votre base de données avec les nouvelles informations
  3. Synchroniser l'affichage si l'utilisateur a la page ouverte
  4. Notifier l'utilisateur si le statut a changé (optionnel)

Exemple de code

async function handleSuiviUpdated(msg: IMvlsRabbitMQMessage) {
  if (msg.header.name !== 'SUIVI_UPDATED') {
    return;
  }

  const suivi = msg.body.suivi;

  // Mettre à jour les informations du suivi dans votre base de données
  await updateSuiviInfo({
    suiviId: suivi.suivi_id,
    status: suivi.status,
    statusJeune: suivi.status_jeune,
    statusBenevole: suivi.status_benevole,
    updatedAt: suivi.status_update_date
  });

  // Optionnel : Notifier l'utilisateur si nécessaire
  if (suivi.status === 'ACTIF') {
    // Le suivi est maintenant actif, peut-être notifier l'autre partie du binôme
  }

  console.log(`[Inspire] Suivi mis à jour: ${suivi.suivi_id}`);
}

6. Gestion des erreurs

Erreurs possibles

  1. Message malformé : Vérifier que le format du message correspond exactement au schéma
  2. Suivi non trouvé : Si vous recevez un SUIVI_UPDATED pour un suivi que vous n'avez pas, vous pouvez l'ignorer ou le logger
  3. Erreur de connexion RabbitMQ : Implémenter une logique de retry avec backoff exponentiel

Recommandations

  • Logger tous les messages reçus et envoyés pour faciliter le debugging
  • Implémenter une gestion d'erreurs robuste avec retry pour les messages critiques
  • Valider les données avant d'envoyer les messages
  • Ne pas bloquer l'utilisateur si l'envoi échoue (par exemple, permettre de réessayer)

Exemple de gestion d'erreurs

async function sendSuiviUpdateWithRetry(params: SuiviUpdateParams, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      await sendSuiviUpdate(params);
      return; // Succès
    } catch (error) {
      console.error(`[Inspire] Tentative ${attempt}/${maxRetries} échouée:`, error);

      if (attempt === maxRetries) {
        // Dernière tentative échouée, logger l'erreur
        await logError({
          type: 'SUIVI_UPDATE_FAILED',
          params,
          error: error.message
        });
        throw error;
      }

      // Attendre avant de réessayer (backoff exponentiel)
      await sleep(1000 * Math.pow(2, attempt - 1));
    }
  }
}

7. Exemple de flux complet

Scénario : Un jeune répond à un checkpoint

  1. DEMA1N crée un suivi (jour 5) :
  2. DEMA1N envoie SUIVI_CREATED via RabbitMQ
  3. DEMA1N envoie un email Brevo au jeune avec l'URL : https://inspire-orientation.org/mentorat/suivi/abc123/jeune/789

  4. Inspire reçoit le message :

  5. Votre handler RabbitMQ reçoit SUIVI_CREATED
  6. Vous stockez les informations du suivi dans votre base de données

  7. Le jeune clique sur le lien :

  8. Votre application extrait token, contactStatus ('jeune'), et suiviId ('789') de l'URL
  9. Vous validez le token (optionnel)
  10. Vous affichez le formulaire de suivi

  11. Le jeune remplit le formulaire :

  12. Il sélectionne "Tout va bien" (actif = '1')
  13. Il ajoute un commentaire : "Tout se passe très bien !"
  14. Il clique sur "Envoyer"

  15. Inspire envoie la mise à jour :

  16. Votre application envoie SUIVI_UPDATE_FROM_INSPIRE via RabbitMQ avec : json { "suivi_update": { "suivi_id": "789", "contact_status": "jeune", "actif": "1", "comment": "Tout se passe très bien !" } }

  17. DEMA1N traite la mise à jour :

  18. DEMA1N reçoit le message et met à jour le suivi
  19. DEMA1N envoie SUIVI_UPDATED via RabbitMQ

  20. Inspire reçoit la confirmation :

  21. Votre handler reçoit SUIVI_UPDATED
  22. Vous mettez à jour votre base de données
  23. Vous pouvez afficher un message de confirmation à l'utilisateur

8. Checklist d'implémentation

  • [ ] Configuration de la connexion RabbitMQ
  • [ ] Implémentation du listener pour SUIVI_CREATED
  • [ ] Stockage des informations de suivi dans votre base de données
  • [ ] Création de la route/page pour afficher le formulaire (/mentorat/suivi/[token]/[contactStatus]/[suiviId])
  • [ ] Validation du token (optionnel mais recommandé)
  • [ ] Affichage du formulaire avec les 3 options (RAS, Inactif, Objectif atteint)
  • [ ] Champ de commentaire optionnel
  • [ ] Implémentation de l'envoi de SUIVI_UPDATE_FROM_INSPIRE via RabbitMQ
  • [ ] Implémentation du listener pour SUIVI_UPDATED (optionnel mais recommandé)
  • [ ] Gestion des erreurs et retry logic
  • [ ] Logging des messages reçus et envoyés
  • [ ] Tests de bout en bout

9. Support et questions

Pour toute question ou problème lors de l'implémentation, contactez l'équipe DEMA1N.

Points importants à retenir : - Tous les messages utilisent le même exchange (dema1n) et routing key (user___mvls) - Le producer dans le header doit être 'inspire' pour vos messages - Les dates doivent être au format ISO 8601 - Les IDs (requestId, uniqid) doivent être uniques


Document créé le : Décembre 2024
Dernière mise à jour : Décembre 2024