Skip to content

Documentation MVLS - Intégration DEMA1N

Version : 1.0
Dernière mise à jour : 12/12/2025

Table des matières

  1. Vue d'ensemble
  2. Architecture
  3. Configuration
  4. Intégration RabbitMQ
  5. Templates Brevo
  6. Champs spécifiques MVLS
  7. Guide d'utilisation
  8. Exemples de code
  9. Dépannage

Vue d'ensemble

MVLS (Mentoré Lycéen) est une intégration spécifique de DEMA1N qui permet de gérer les jeunes lycéens et les bénévoles éclaireurs dans un contexte particulier. Cette intégration utilise :

  • RabbitMQ pour la synchronisation des données entre DEMA1N et la plateforme MVLS
  • Templates Brevo pour l'envoi d'emails personnalisés
  • Sandbox pour isoler les données MVLS des autres utilisateurs DEMA1N
  • Champs spécifiques dans externalInfos pour stocker les données MVLS

Concepts clés

  • Sandbox : Les utilisateurs MVLS ont sandbox === 'MVLS' pour les distinguer des autres utilisateurs DEMA1N
  • Lycéen : Jeune MVLS avec le rôle lyceen
  • Éclaireur : Bénévole MVLS avec le rôle eclaireur
  • Binôme MVLS : Relation entre un lycéen et un éclaireur MVLS

Architecture

Structure des modules

back/src/mvls/
├── mvls.module.ts          # Module NestJS MVLS
├── mvls.service.ts         # Service principal MVLS
├── mvls.controller.ts       # Contrôleur MVLS
└── mvls.interfaces.ts      # Interfaces TypeScript

Flux de données

┌─────────────┐
│   DEMA1N    │
│  (Backend)  │
└──────┬──────┘
       │
       │ RabbitMQ Messages
       │ (USER_CREATED, USER_UPDATED, USER_DELETED, BINOME_CREATED, BINOME_UPDATED)
       │
       ▼
┌─────────────┐
│   RabbitMQ  │
│   Exchange │
│   "dema1n" │
└──────┬──────┘
       │
       │ Routing Key: "user___mvls"
       │
       ▼
┌─────────────┐
│  Plateforme │
│     MVLS    │
└─────────────┘

Services impliqués

  • MvlsService : Gestion des messages RabbitMQ et filtrage des données
  • JeuneService : Gestion des jeunes MVLS et publication des messages
  • BenevolesService : Gestion des bénévoles MVLS et publication des messages
  • BinomeService : Gestion des binômes MVLS et publication des messages
  • MailService : Envoi d'emails via templates Brevo pour MVLS

Configuration

Variables d'environnement

# RabbitMQ
RABBIT_MQ_URL=amqp://user:password@host:5672
RABBIT_MQ_EXCHANGE_NAME=dema1n
RABBIT_MQ_EXCHANGE_TYPE=topic

# Brevo (Sendinblue)
BREVO_API_KEY=votre_clé_api_brevo
BREVO_URL=https://api.brevo.com/v3

# Producer (pour identifier les messages)
PRODUCER=dema1n

Configuration RabbitMQ

L'exchange RabbitMQ est configuré dans mvls.module.ts :

RabbitMQModule.forRootAsync({
  exchanges: [
    {
      name: configService.get<string>('RABBIT_MQ_EXCHANGE_NAME'),
      type: configService.get<string>('RABBIT_MQ_EXCHANGE_TYPE'),
      options: {
        durable: true
      }
    }
  ]
})

Migration base de données

Une migration a été ajoutée pour supporter les templates Brevo :

// Migration: 1765124120829-addedBrevoTemplateId.ts
ALTER TABLE `message_template` ADD `brevoTemplateId` bigint NULL;

Intégration RabbitMQ

Types de messages

MVLS utilise plusieurs types de messages RabbitMQ :

1. Messages utilisateurs

  • USER_CREATED : Création d'un jeune ou bénévole MVLS
  • USER_UPDATED : Mise à jour d'un jeune ou bénévole MVLS
  • USER_DELETED : Suppression d'un jeune ou bénévole MVLS

2. Messages binômes

  • BINOME_CREATED : Création d'un binôme MVLS
  • BINOME_UPDATED : Mise à jour d'un binôme MVLS (changement de statut/état)

Format des messages

Structure générale

interface IMvlsRabbitMQMessage {
  header: {
    name: string;              // "USER_CREATED", "USER_UPDATED", "USER_DELETED", "BINOME_CREATED", "BINOME_UPDATED"
    model: string;             // "USER" ou "BINOME"
    event: string;             // "CREATED", "UPDATED", "DELETED"
    date: string;              // Date ISO 8601
    requestId: string;         // Identifiant unique de la requête
    uniqid: string;            // Identifiant unique du message
    VERSION: string;           // "1.0"
    producer: string;          // "dema1n"
  };
  body: IFilterLyceenDataForMvlsResult | IFilterEclaireurDataForMvlsResult | IUserDeletedDataForMvlsResult | IBinomeDataForMvlsResult;
}

Message USER_CREATED / USER_UPDATED (Lycéen)

{
  "header": {
    "name": "USER_CREATED",
    "model": "USER",
    "event": "CREATED",
    "date": "2025-12-12T10:00:00.000Z",
    "requestId": "abc123",
    "uniqid": "xyz789",
    "VERSION": "1.0",
    "producer": "dema1n"
  },
  "body": {
    "charte": true,
    "notification": true,
    "newEmail": null,
    "user": {
      "gender": "M",
      "birthdate": "2007-05-15",
      "firstname": "Jean",
      "lastname": "Dupont",
      "phoneNumber": "+33612345678",
      "roles": ["lyceen"],
      "userJwt": {
        "username": "jean.dupont@example.com",
        "ssoId": "sso-123",
        "activationToken": "token-abc"
      },
      "mvlsLyceens": [{
        "createdAt": "2025-12-01T10:00:00.000Z",
        "mentor_sexe": "M",
        "etudesSup": "totaly",
        "travailEfficace": "kinda",
        "preparationSup": "yes",
        "nomClasse": "Terminale S",
        "domaines": ["Sciences", "Technologie"],
        "formations": ["Bac+3", "Bac+5"],
        "communication": ["email", "phone"],
        "statut": "EN_ATTENTE",
        "state": "ACTIF"
      }],
      "parcoursLyceens": [{
        "createdAt": "2025-12-01T10:00:00.000Z",
        "filiere": "Scientifique",
        "isBoursierSecondaire": "OUI",
        "etablissement": {
          "departmentCode": "75",
          "nom": "Lycée Victor Hugo"
        }
      }]
    }
  }
}

Champ newEmail (optionnel) : - Type : string | null - Emplacement : Au niveau du body (racine du body) - Description : Nouvel email de l'utilisateur si différent de user.userJwt.username. Si présent et différent de username, l'email de l'utilisateur sera mis à jour dans DEMA1N avec le préfixe mvls_ (ex: mvls_nouvel.email@example.com). - Exemple avec changement d'email :

{
  "body": {
    "charte": true,
    "notification": true,
    "newEmail": "nouvel.email@example.com",
    "user": {
      "userJwt": {
        "username": "ancien.email@example.com"
      }
    }
  }
}

Message USER_DELETED

{
  "header": {
    "name": "USER_DELETED",
    "model": "USER",
    "event": "DELETED",
    "date": "2025-12-12T10:00:00.000Z",
    "requestId": "abc123",
    "uniqid": "xyz789",
    "VERSION": "1.0",
    "producer": "dema1n"
  },
  "body": {
    "email": "jean.dupont@example.com"
  }
}

Message BINOME_CREATED / BINOME_UPDATED

{
  "header": {
    "name": "BINOME_CREATED",
    "model": "BINOME",
    "event": "CREATED",
    "date": "2025-12-12T10:00:00.000Z",
    "requestId": "abc123",
    "uniqid": "xyz789",
    "VERSION": "1.0",
    "producer": "dema1n"
  },
  "body": {
    "binome_id": "123",
    "jeune_id": "456",
    "benevole_id": "789",
    "jeune_email": "jean.dupont@example.com",
    "benevole_email": "marie.martin@example.com",
    "status": "EN_ATTENTE",
    "state": "ACTIF",
    "creationDate": "2025-12-12T10:00:00.000Z",
    "buildDate": null,
    "statusUpdateDate": "2025-12-12T10:00:00.000Z"
  }
}

Routing Key

Tous les messages MVLS utilisent le même routing key : user___mvls

Quand les messages sont envoyés

Messages utilisateurs

  • USER_CREATED : Lors de la création d'un jeune ou bénévole MVLS
  • USER_UPDATED :
  • Lors de la mise à jour d'un jeune MVLS via JeuneService.updateAdmin()
  • Lors de la mise à jour d'un bénévole MVLS via BenevolesService.updateAdmin()
  • USER_DELETED :
  • Lors de la suppression d'un jeune MVLS via JeuneService.deleteJeune()
  • Lors de la suppression d'un bénévole MVLS via BenevolesService.deleteBenevole()

Messages binômes

  • BINOME_CREATED : Lors de la création d'un binôme MVLS via BinomeService.createBinome()
  • BINOME_UPDATED : Lors de la mise à jour d'un binôme MVLS (changement de statut ou d'état)

Messages suivis

  • SUIVI_CREATED : Lors de la création d'un suivi MVLS (checkpoint) via SuivistatusService.createSuivistatus()
  • SUIVI_UPDATED : Lors de la mise à jour d'un suivi MVLS via SuivistatusService.changeSuivistatus()

Format du message SUIVI_CREATED / SUIVI_UPDATED :

{
  "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"
    }
  }
}

Réception des messages

Le service MVLS écoute les messages entrants via RabbitMQ :

@RabbitRPC({
  exchange: 'dema1n',
  queue: 'Dema1n_mvls',
  routingKey: 'user___mvls'
})
public async pubSubHandler(msg: IMvlsRabbitMQMessage) {
  // Traitement des messages reçus
}

Gestion du champ newEmail dans USER_UPDATED et USER_CREATED

Lors de la réception d'un message USER_UPDATED ou USER_CREATED, le champ newEmail peut être présent au niveau du body pour indiquer un changement d'email :

  • Si newEmail est présent et différent de user.userJwt.username :
  • L'utilisateur est recherché par son ancien email (avec préfixe mvls_) : mvls_ + user.userJwt.username
  • L'email de l'utilisateur est mis à jour avec le nouvel email (avec préfixe mvls_) : mvls_ + newEmail
  • Les autres champs de l'utilisateur sont également mis à jour selon les données du message

  • Si newEmail est null ou identique à username :

  • Aucune mise à jour de l'email n'est effectuée
  • Seuls les autres champs sont mis à jour

Exemple de traitement :

// Message reçu avec newEmail différent de username
{
  "body": {
    "newEmail": "nouvel.email@example.com",
    "user": {
      "userJwt": {
        "username": "ancien.email@example.com"
      }
    }
  }
}

// Comportement :
// 1. Recherche de l'utilisateur : findByEmailMvls('mvls_ancien.email@example.com')
// 2. Mise à jour de l'email : user.email = 'mvls_nouvel.email@example.com'
// 3. Mise à jour des autres champs (firstName, lastName, etc.)

Système de suivi MVLS avec Inspire

Vue d'ensemble

Pour les binômes MVLS, les emails de checkpoint (steps avec suiviActivated = true) contiennent des liens vers Inspire Orientation qui permettent aux jeunes et aux bénévoles de répondre directement sur la plateforme Inspire.

URLs dans les emails Brevo

Lors de l'envoi d'un email de checkpoint MVLS, les variables suivantes sont ajoutées aux templates Brevo :

  • {{ 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é comme suit : - Pour contactok : md5(binomeId + '/suivi/' + suiviId + '/' + contactStatus + '/contactok') - Pour nocontact : md5(binomeId + '/suivi/' + suiviId + '/' + contactStatus + '/nocontact') - contactStatus : 'jeune' ou 'benevole' - suiviId : Identifiant du suivi (Suivistatus.id) - type : 'contactok' ou 'nocontact' selon le type de réponse

Implémentation côté Inspire

1. Réception des messages RabbitMQ SUIVI_CREATED

Lorsqu'un suivi MVLS est créé, Inspire reçoit un message SUIVI_CREATED via RabbitMQ. Ce message contient toutes les informations nécessaires pour afficher le formulaire de suivi.

Actions à effectuer côté Inspire :

  1. Écouter les messages SUIVI_CREATED dans la queue RabbitMQ avec le routing key user___mvls
  2. Extraire les données du suivi depuis msg.body.suivi
  3. Stocker les informations nécessaires pour afficher le formulaire :
  4. suivi_id : Identifiant unique du suivi
  5. binome_id : Identifiant du binôme
  6. day_step : Jour du checkpoint (0, 5, 20, 60, etc.)
  7. status_jeune et status_benevole : Statuts initiaux (généralement 'EN_ATTENTE')

2. Affichage du formulaire de suivi

Quand un utilisateur clique sur le lien dans l'email Brevo, Inspire doit :

  1. Extraire les paramètres de l'URL :
  2. token : Hash de sécurité
  3. contactStatus : 'jeune' ou 'benevole'
  4. suiviId : Identifiant du suivi

  5. Valider le token (optionnel côté Inspire, mais recommandé pour la sécurité) :

  6. Reconstruire le hash attendu : md5(binomeId + '/suivi/' + suiviId + '/' + contactStatus)
  7. Comparer avec le token reçu

  8. Afficher le formulaire de suivi avec les options suivantes :

  9. RAS (actif = '1') : "Tout va bien"
  10. Inactif (actif = '0') : "Il y a un problème"
  11. Objectif atteint (actif = '2') : "Nous avons atteint les objectifs"
  12. Commentaire (optionnel) : Champ texte pour ajouter un commentaire

3. Envoi de la réponse à DEMA1N via RabbitMQ

Lorsque l'utilisateur soumet le formulaire, Inspire doit envoyer un message RabbitMQ à DEMA1N :

Type de message : SUIVI_UPDATE_FROM_INSPIRE

Exchange : dema1n

Routing Key : user___mvls

Format du message :

{
  "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 du body.suivi_update : - suivi_id (string, obligatoire) : Identifiant du suivi reçu dans l'URL - contact_status (string, obligatoire) : 'jeune' ou 'benevole' (reçu dans l'URL originale) - actif (string, obligatoire) : '1' pour RAS, '0' pour Inactif, '2' pour objectif atteint - comment (string, optionnel) : Commentaire optionnel

Exemple de code pour envoyer le message :

// Exemple avec la bibliothèque RabbitMQ de votre choix
const message = {
  header: {
    name: 'SUIVI_UPDATE_FROM_INSPIRE',
    model: 'SUIVI',
    event: 'UPDATED',
    date: new Date().toISOString(),
    requestId: generateUniqueId(),
    uniqid: generateUniqueId(),
    VERSION: '1.0',
    producer: 'inspire'
  },
  body: {
    suivi_update: {
      suivi_id: suiviId,
      contact_status: contactStatus, // 'jeune' ou 'benevole'
      actif: actif, // '1', '0', ou '2'
      comment: comment || null
    }
  }
};

await rabbitmqChannel.publish('dema1n', 'user___mvls', message);

4. Gestion des erreurs

Si DEMA1N ne peut pas traiter le message (suivi non trouvé, binôme non MVLS, etc.), le message sera loggé mais ne retournera pas d'erreur explicite via RabbitMQ.

Actions recommandées côté Inspire : - Logger tous les messages envoyés pour le debugging - Surveiller les logs DEMA1N si nécessaire pour vérifier que les messages sont bien traités - Ne pas bloquer l'utilisateur si possible (par exemple, permettre de réessayer)

5. Réception des messages SUIVI_UPDATED

Lorsqu'un suivi est mis à jour (soit depuis Inspire, soit depuis DEMA1N), Inspire reçoit un message SUIVI_UPDATED via RabbitMQ.

Actions recommandées côté Inspire : - Mettre à jour l'affichage du suivi si nécessaire - Notifier l'utilisateur si le statut a changé - Synchroniser les données locales avec les données reçues

Exemple de flux complet

  1. Création du suivi :
  2. DEMA1N crée un suivi pour un checkpoint MVLS (jour 5 par exemple)
  3. DEMA1N envoie un message SUIVI_CREATED via RabbitMQ
  4. DEMA1N envoie un email Brevo au jeune avec deux URLs :

    • {{ params.contactok }} : https://inspire-orientation.org/mentorat/suivi/abc123/jeune/789/contactok
    • {{ params.nocontact }} : https://inspire-orientation.org/mentorat/suivi/def456/jeune/789/nocontact
  5. Réception côté Inspire :

  6. Inspire reçoit le message SUIVI_CREATED et stocke les informations
  7. L'utilisateur clique sur l'un des deux liens dans l'email (contactok ou nocontact)
  8. Inspire affiche le formulaire de suivi avec la réponse pré-sélectionnée selon le lien cliqué

  9. Soumission du formulaire :

  10. L'utilisateur remplit le formulaire et clique sur "Envoyer"
  11. Inspire envoie un message SUIVI_UPDATE_FROM_INSPIRE via RabbitMQ à DEMA1N

  12. Mise à jour côté DEMA1N :

  13. DEMA1N reçoit le message SUIVI_UPDATE_FROM_INSPIRE via RabbitMQ
  14. DEMA1N valide les données et met à jour le suivi
  15. DEMA1N envoie un message SUIVI_UPDATED via RabbitMQ pour notifier la mise à jour

  16. Confirmation côté Inspire :

  17. Inspire peut écouter le message SUIVI_UPDATED pour confirmer la mise à jour
  18. Inspire affiche un message de confirmation à l'utilisateur
  19. Inspire peut mettre à jour l'affichage si nécessaire

Templates Brevo

Configuration

Les templates Brevo sont configurés dans la table message_template avec le champ brevoTemplateId :

ALTER TABLE `message_template` ADD `brevoTemplateId` bigint NULL;

Utilisation

Lors de l'envoi d'un email via MailService.send(), si le template a un brevoTemplateId, l'email est envoyé via l'API Brevo au lieu de SMTP classique :

if (template.brevoTemplateId) {
  const brevoResult = await this.sendForTemplate(
    cleanedMailTo,
    template.brevoTemplateId,
    data,
    view,        // Slug pour le log
    template.subject  // Sujet pour le log
  );
}

Format des données envoyées à Brevo

{
  templateId: number,
  messageVersions: [
    {
      to: [{ email: "destinataire@example.com" }],
      params: {
        // Variables du template Brevo
        prenom: "Jean",
        nom: "Dupont",
        // ... autres variables
      }
    }
  ]
}

Logs MAIL

Les emails envoyés via Brevo sont loggés dans la table log-mail avec : - Le slug du template - Le destinataire (sans préfixe mvls_) - Le sujet du template - Un HTML contenant les variables transmises et la réponse Brevo API

Préfixe mvls_

Les emails MVLS peuvent avoir un préfixe mvls_ dans DEMA1N (ex: mvls_jean.dupont@example.com). Ce préfixe est automatiquement retiré avant l'envoi à Brevo pour garantir la compatibilité.


Champs spécifiques MVLS

Structure externalInfos

Les données spécifiques MVLS sont stockées dans le champ externalInfos (JSON) des entités Jeune et Benevole.

Champs lycéen (Jeune MVLS)

{
  // Scolarité
  nomClasse: string | null;              // Nom de la classe
  domaines: string[] | null;             // Domaines d'intérêt
  domainesAutre: string | null;          // Autre domaine (si "Autre" sélectionné)
  formations: string[] | null;           // Formations souhaitées
  formationsAutre: string | null;        // Autre formation (si "Autre" sélectionné)
  alternance: boolean | null;             // Intérêt pour l'alternance

  // Questions scolarité
  etudesSup: string | null;              // "totaly" | "kinda" | "notreally" | "no"
  travailEfficace: string | null;        // "totaly" | "kinda" | "notreally" | "no"
  preparationSup: string | null;         // "yes" | "kinda" | "no"

  // Mentorat
  mentor_sexe: string | null;            // Préférence de genre du mentor
  communication: string[] | null;        // Préférences de communication

  // Représentant légal
  legalRepresentativeEmail: string | null;
  legalRepresentativePhone: string | null;

  // Adresse
  streetNumber: string | null;
  street: string | null;
  zipcode: string | null;
  city: string | null;

  // Autres
  voeux_formation: string | null;
  besoins: string[] | null;
  statut: string | null;
  state: string | null;
  dateUpdateStatut: Date | string | null;
  dateUpdateState: Date | string | null;
}

Champs éclaireur (Bénévole MVLS)

{
  // Formations
  formations: string[] | null;
  formationsAutre: string | null;

  // Domaines
  domaines: string | string[] | null;
  domainesAutre: string | null;

  // Autres
  casierJudiciaire: boolean | null;
  precision: string | null;
  besoins: string[] | null;
  alternance: boolean | null;
  annee: string | null;
  sourceOfFinanceStudies: string | null;
  statut: string | null;
  state: string | null;
  dateUpdateStatut: Date | string | null;
  dateUpdateState: Date | string | null;
}

Options des champs scolarité

affirmationOptions (pour etudesSup et travailEfficace)

[
  { label: 'Oui, totalement', value: 'totaly' },
  { label: 'Oui, plutôt', value: 'kinda' },
  { label: 'Non, pas vraiment', value: 'notreally' },
  { label: 'Non, pas du tout', value: 'no' }
]

preparationSupOptions (pour preparationSup)

[
  { label: 'Oui, suffisamment', value: 'yes' },
  { label: 'Oui, mais pas assez', value: 'kinda' },
  { label: 'Non, jamais', value: 'no' }
]

Guide d'utilisation

Pour les développeurs

1. Créer un jeune MVLS

// Dans JeuneService
const jeune = await this.jeuneRepository.create({
  // ... champs standard
  user: {
    sandbox: 'MVLS',
    // ... autres champs user
  },
  externalInfos: {
    nomClasse: 'Terminale S',
    domaines: ['Sciences', 'Technologie'],
    etudesSup: 'totaly',
    travailEfficace: 'kinda',
    preparationSup: 'yes',
    // ... autres champs MVLS
  }
});

const jeuneSaved = await this.jeuneRepository.save(jeune);

// Publier le message RabbitMQ
if (jeuneSaved.user.sandbox === 'MVLS') {
  await this.mvlsService.publishJeuneUpdatedMessage(
    jeuneSaved.user,
    jeuneSaved
  );
}

2. Mettre à jour un jeune MVLS

// Dans JeuneService.updateAdmin()
const jeune = await this.jeuneRepository.findOne({
  where: { id: jeuneId },
  relations: ['user']
});

// Mise à jour des champs
jeune.externalInfos.etudesSup = 'kinda';
await this.jeuneRepository.save(jeune);

// Publier le message RabbitMQ
if (jeune.user.sandbox === 'MVLS') {
  const jeuneReloaded = await this.jeuneRepository.findOne({
    where: { id: jeune.id },
    relations: ['user']
  });

  await this.mvlsService.publishJeuneUpdatedMessage(
    jeuneReloaded.user,
    jeuneReloaded
  );
}

3. Supprimer un jeune MVLS

// Dans JeuneService.deleteJeune()
const jeune = await this.jeuneRepository.findOne({
  where: { id: jeuneId },
  relations: ['user']
});

if (jeune.user.sandbox === 'MVLS') {
  // Envoyer le message USER_DELETED AVANT la suppression
  await this.mvlsService.publishUserDeletedMessage(jeune.user.email);
}

// Supprimer l'utilisateur
await this.userRepository.delete(jeune.user.id);

4. Créer un binôme MVLS

// Dans BinomeService.createBinome()
const binome = await this.binomeRepository.create({
  jeune: jeuneMVLS,
  benevole: benevoleMVLS,
  sandbox: 'MVLS',
  // ... autres champs
});

const binomeSaved = await this.binomeRepository.save(binome);

// Publier le message RabbitMQ
if (binomeSaved.sandbox === 'MVLS') {
  const binomeReloaded = await this.binomeRepository.findOne({
    where: { id: binomeSaved.id },
    relations: ['jeune', 'benevole', 'jeune.user', 'benevole.user']
  });

  await this.mvlsService.publishBinomeMessage(binomeReloaded, 'CREATED');
}

5. Envoyer un email via template Brevo

// Dans MailService.send()
const template = await this.messageTemplateRepository.findOne({
  where: { name: 'jeune-mvls-inscrit' }
});

if (template.brevoTemplateId) {
  await this.sendForTemplate(
    'jean.dupont@example.com',
    template.brevoTemplateId,
    {
      jeune: jeune,
      prenom: jeune.user.firstName,
      nom: jeune.user.lastName,
      // ... autres variables
    },
    'jeune-mvls-inscrit',  // Slug pour le log
    template.subject        // Sujet pour le log
  );
}

Pour les administrateurs

Accéder aux champs MVLS dans le BO

  1. Jeunes MVLS : /bo/jeunes/{id}
  2. Section "SCOLARITÉ" contient les champs spécifiques MVLS
  3. Les 3 questions scolarité sont visibles uniquement pour les MVLS

  4. Bénévoles MVLS : /bo/benevoles/{id}

  5. Section spécifique MVLS avec les champs éclaireur

Vérifier les messages RabbitMQ

Les messages RabbitMQ sont loggés dans les logs de l'application :

docker-compose logs -f api | grep -i "mvls\|rabbitmq"

Vérifier les logs MAIL

Les emails envoyés via Brevo sont loggés dans la table log-mail :

SELECT * FROM `log-mail` 
WHERE slug LIKE '%mvls%' 
ORDER BY date_envoi DESC 
LIMIT 10;

Exemples de code

Exemple complet : Création et mise à jour d'un jeune MVLS

// 1. Créer un jeune MVLS
const jeune = await jeuneRepository.create({
  user: {
    email: 'mvls_jean.dupont@example.com',
    firstName: 'Jean',
    lastName: 'Dupont',
    sandbox: 'MVLS',
    roles: ['lyceen']
  },
  externalInfos: {
    nomClasse: 'Terminale S',
    domaines: ['Sciences', 'Technologie'],
    formations: ['Bac+3', 'Bac+5'],
    etudesSup: 'totaly',
    travailEfficace: 'kinda',
    preparationSup: 'yes',
    mentor_sexe: 'M',
    communication: ['email', 'phone']
  }
});

const jeuneSaved = await jeuneRepository.save(jeune);

// 2. Publier le message USER_CREATED
if (jeuneSaved.user.sandbox === 'MVLS') {
  try {
    await mvlsService.publishJeuneUpdatedMessage(
      jeuneSaved.user,
      jeuneSaved
    );
  } catch (error) {
    console.error('Erreur publication RabbitMQ:', error);
    // Ne pas faire échouer la création si la publication échoue
  }
}

// 3. Mettre à jour les champs scolarité
jeuneSaved.externalInfos.etudesSup = 'kinda';
await jeuneRepository.save(jeuneSaved);

// 4. Publier le message USER_UPDATED
if (jeuneSaved.user.sandbox === 'MVLS') {
  const jeuneReloaded = await jeuneRepository.findOne({
    where: { id: jeuneSaved.id },
    relations: ['user']
  });

  await mvlsService.publishJeuneUpdatedMessage(
    jeuneReloaded.user,
    jeuneReloaded
  );
}

Exemple : Envoi d'email via Brevo

// Dans MailService
async sendWelcomeEmailToMvlsJeune(jeune: Jeune) {
  const template = await this.messageTemplateRepository.findOne({
    where: { name: 'jeune-mvls-inscrit' }
  });

  if (!template) {
    throw new Error('Template jeune-mvls-inscrit non trouvé');
  }

  // Si le template a un brevoTemplateId, utiliser Brevo
  if (template.brevoTemplateId) {
    await this.sendForTemplate(
      jeune.user.email.replace('mvls_', ''), // Retirer le préfixe
      template.brevoTemplateId,
      {
        jeune: jeune,
        prenom: jeune.user.firstName,
        nom: jeune.user.lastName,
        nomClasse: jeune.externalInfos?.nomClasse || '',
        // ... autres variables pour le template Brevo
      },
      'jeune-mvls-inscrit',
      template.subject
    );
  } else {
    // Sinon, utiliser SMTP classique
    await this.send(
      jeune.user.email,
      'jeune-mvls-inscrit',
      { jeune: jeune }
    );
  }
}

Dépannage

Les messages RabbitMQ ne sont pas envoyés

  1. Vérifier le sandbox : S'assurer que user.sandbox === 'MVLS'
  2. Vérifier les logs : docker-compose logs -f api | grep -i "mvls"
  3. Vérifier la connexion RabbitMQ : Vérifier que RABBIT_MQ_URL est correct
  4. Vérifier l'exchange : Vérifier que l'exchange dema1n existe dans RabbitMQ

Les emails Brevo ne sont pas envoyés

  1. Vérifier le brevoTemplateId : S'assurer que le template a un brevoTemplateId défini
  2. Vérifier les logs MAIL : Vérifier dans la table log-mail si l'email a été loggé
  3. Vérifier les logs Brevo : docker-compose logs -f api | grep -i "brevo"
  4. Vérifier la clé API : Vérifier que BREVO_API_KEY est correcte

Les champs MVLS ne s'affichent pas dans le BO

  1. Vérifier le sandbox : S'assurer que jeune.user.sandbox === 'MVLS'
  2. Vérifier externalInfos : S'assurer que jeune.externalInfos existe et contient les champs
  3. Vérifier l'initialisation : Vérifier que les champs sont initialisés dans fetch() du composant Vue

Erreur "Cannot find module '@nestjs/common'"

Ces erreurs sont des faux positifs du linter TypeScript dans l'environnement de développement. Le code compile correctement dans Docker. Pour vérifier :

docker exec -it Dema1n_api npm run build

Ressources supplémentaires


Support

Pour toute question ou problème concernant l'intégration MVLS, contactez l'équipe technique DEMA1N.

Version du document : 1.0
Dernière mise à jour : 12/12/2025