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
- Écouter les messages dans votre queue RabbitMQ avec le routing key
user___mvls - Filtrer les messages avec
header.name === "SUIVI_CREATED" - Extraire les données depuis
msg.body.suivi - Stocker les informations nécessaires pour afficher le formulaire :
suivi_id: Identifiant unique du suivi (à conserver pour l'URL)binome_id: Identifiant du binômeday_step: Jour du checkpoint (0, 5, 20, 60, 90, 120, 150, 210, 240, 270, 300)status_jeuneetstatus_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
- 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' ```
- 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'); } ```
- 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é'); } ```
- Déterminer le type de réponse selon le paramètre
type: - Si
type === 'contactok': L'utilisateur a cliqué sur le lien "Tout va bien" →actif = '1' -
Si
type === 'nocontact': L'utilisateur a cliqué sur le lien "Il y a un problème" →actif = '0' -
Afficher le formulaire de suivi :
- Si
type === 'contactok': Afficher un formulaire pré-rempli avec "Tout va bien" sélectionné - Si
type === 'nocontact': Afficher un formulaire pré-rempli avec "Il y a un problème" sélectionné - Objectif atteint (
actif = '2') : Option supplémentaire "Nous avons atteint les objectifs" (disponible dans les deux cas) - 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'URLcontact_status(string) :'jeune'ou'benevole'(reçu dans l'URL)actif(string) :'1'pour RAS,'0'pour Inactif,'2'pour objectif atteintcomment(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
- Écouter les messages avec
header.name === "SUIVI_UPDATED" - Mettre à jour votre base de données avec les nouvelles informations
- Synchroniser l'affichage si l'utilisateur a la page ouverte
- 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
- Message malformé : Vérifier que le format du message correspond exactement au schéma
- Suivi non trouvé : Si vous recevez un
SUIVI_UPDATEDpour un suivi que vous n'avez pas, vous pouvez l'ignorer ou le logger - 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
- DEMA1N crée un suivi (jour 5) :
- DEMA1N envoie
SUIVI_CREATEDvia RabbitMQ -
DEMA1N envoie un email Brevo au jeune avec l'URL :
https://inspire-orientation.org/mentorat/suivi/abc123/jeune/789 -
Inspire reçoit le message :
- Votre handler RabbitMQ reçoit
SUIVI_CREATED -
Vous stockez les informations du suivi dans votre base de données
-
Le jeune clique sur le lien :
- Votre application extrait
token,contactStatus('jeune'), etsuiviId('789') de l'URL - Vous validez le token (optionnel)
-
Vous affichez le formulaire de suivi
-
Le jeune remplit le formulaire :
- Il sélectionne "Tout va bien" (
actif = '1') - Il ajoute un commentaire : "Tout se passe très bien !"
-
Il clique sur "Envoyer"
-
Inspire envoie la mise à jour :
-
Votre application envoie
SUIVI_UPDATE_FROM_INSPIREvia RabbitMQ avec :json { "suivi_update": { "suivi_id": "789", "contact_status": "jeune", "actif": "1", "comment": "Tout se passe très bien !" } } -
DEMA1N traite la mise à jour :
- DEMA1N reçoit le message et met à jour le suivi
-
DEMA1N envoie
SUIVI_UPDATEDvia RabbitMQ -
Inspire reçoit la confirmation :
- Votre handler reçoit
SUIVI_UPDATED - Vous mettez à jour votre base de données
- 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_INSPIREvia 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