Strapi v4 : Comment dynamiser un content-Type en réaction à des événements émis par d’autres content-types

Introduction

Au cours de ce guide, nous plongerons dans le processus de création d’un système réactif au sein de votre application Strapi. Nous découvrirons comment tirer parti des événements émis par un Content-Type pour déclencher des actions dynamiques sur un autre. Nous allons étudier notre sujet d’aujourd’hui à travers la mise en place d’un système de chat privé entre un utilisateur de type fournisseur et un autre de type client.

Prérequis

Avant de commencer, assurez-vous d’avoir installé Node.js et yarn. Vous pouvez installer Strapi globalement avec la commande suivante :

yarn create strapi-app my-project --ts --quickstart

Cas d’utilisation

Le cas d’utilisation que nous explorons aujourd’hui concerne la mise en place d’un système de chat privé au sein d’une application Strapi. Dans ce scénario, nous avons deux types d’utilisateurs distincts : les fournisseurs et les clients. L’objectif principal est de permettre la communication directe entre un utilisateur de type fournisseur et un utilisateur de type client, en utilisant un système de chat privé.

L’envoi d’un message doit déclencher une mise à jour de notre conversation associée. Afin d’optimiser l’expérience utilisateur, il est essentiel de mettre à jour le compteur de messages non lus. Cette mise à jour revêt une importance particulière pour les deux parties, le fournisseur et le client, car elle indique le nombre de notifications non lues dans la conversation.

Création des content-types

Le type de contenu “User” étant disponible par défaut sur Strapi, nous allons maintenant créer les types de contenu Conversation et Message.

Conversation

Pour le content-type Conversation nous aurons les champs ci-dessous :

  • Field: nonLusFournisseur, Type: Integer
  • Field: nonLusClient, Type: Integer
  • Relation: fournisseur, Type: Has many
  • Relation: client, Type: Has many

Exécutez la commande et suivez les instructions ci-dessous pour créer l’entité Conversation. Comme le type de champ relation n’est pas disponible dans la console, il sera ajouté ultérieurement via l’interface.

yarn strapi generate content-type

# Répondez de la manière suivante :
Content type display name : Conversation
Content type singular name : conversation
Content type plural name : conversations
Please choose the model type Collection : Type
Use draft and publish? No
Do you want to add attributes? Yes
Name of attribute : nonLusFournisseur
What type of attribute : integer
Do you want to add another attribute? Yes
Name of attribute : nonLusClient
What type of attribute : integer
Do you want to add another attribute? No
Where do you want to add this model? Add model to new API
Name of the new API? conversation
Bootstrap API related files? Yes
✔  ++ /api/conversation/content-types/conversation/schema.json
✔  +- /api/conversation/content-types/conversation/schema.json
✔  ++ /api/conversation/controllers/conversation.ts
✔  ++ /api/conversation/services/conversation.ts
✔  ++ /api/conversation/routes/conversation.ts

Message

Pour le content-type Message nous aurons les champs ci-dessous :

  • Field: type, Type: enumeration (FOURNISSEUR, CLIENT)
  • Field: text, Type: text
  • Field: lu, Type: Boolean
yarn strapi generate content-type

# Répondez de la manière suivante :
Content type display name : Message
Content type singular name : message
Content type plural name : messages
Please choose the model type Collection : Type
Use draft and publish? No
Do you want to add attributes? Yes
Name of attribute : text
What type of attribute : text
Do you want to add attributes? Yes
Name of attribute : type
What type of attribute : enumeration
Add values separated by a comma : FOURNISSEUR,CLIENT
Do you want to add another attribute? Yes
Name of attribute : lu
What type of attribute : boolean
Do you want to add another attribute? No
Where do you want to add this model? Add model to new API
Name of the new API? (message) message
Bootstrap API related files? Yes
✔  ++ /api/message/content-types/message/schema.json
✔  +- /api/message/content-types/message/schema.json
✔  ++ /api/message/controllers/message.ts
✔  ++ /api/message/services/message.ts
✔  ++ /api/message/routes/message.ts

Se rendre sur le menu Content-type Builder > Conversation afin d’ajouter eux nouveau champ de type relation.

Configuration des permissions

Pour commencer, créons les rôles “Fournisseur” et “Client” et attribuons les droits comme suit :

  1. Accédez à “Settings” > “Users & Permissions Plugins” > “Roles”.
  2. Appuyez sur “Add new role” puis renseignez “Fournisseur” dans le champ “Name”.
  3. Dans la section “Conversation” et “Message”, assurez-vous que les autorisations suivantes sont correctement activées : “Create”, “Find” et “FindOne”.
  4. Répétez la même procédure pour le rôle “Client”.
  5. Revenez sur “Settings” > “Users & Permissions Plugins” > “Roles”, puis sélectionnez le rôle “Public”. Assurez-vous que les autorisations suivantes sont correctement activées : “CallBack” et “Connect” pour permettre aux utilisateurs de se connecter.

Remplissage des données

Créons deux comptes utilisateurs (un fournisseur et un client) en insérant les informations nécessaires via le panneau d’administration Strapi.

Créer un espace de conversation entre le profil Fournisseur et le Client.

Création du hook générique

Nous allons mettre en place un hook global qui nous permettra d’écouter les événements de notre base de données, réagir en conséquence, et mettre à jour notre Content-Type Conversation. Dans notre cas d’utilisation, nous allons écouter l’événement afterCreate de notre Content-Type “Message”.

Sur Strapi, pour écouter les événements liés à un Content-Type particulier, il est nécessaire de créer un hook dans le dossier du Content-Type en question, par exemple : /src/api/[api-name]/content-types/[content-type-name]/lifecycles.ts. Bien que notre cas d’utilisation actuel se limite à l’écoute des événements liés à notre Content-Type Message, le code sera rédigé de manière à ce que nous puissions écouter plusieurs événements de différents Content-Types afin de mettre à jour le Content-Type Conversation.

Pour atteindre cet objectif, nous devons créer notre déclencheur au niveau du fichier ./src/index.ts. Pour des raisons de maintenabilité et d’organisation du code, nous allons créer un service que nous localiserons dans le dossier de notre Content-Type Conversation, puis nous l’appellerons dans ./src/index.ts. Cette approche nous permet de concentrer le code pertinent pour le Content-Type “Conversation” dans le dossier correspondant, évitant ainsi de surcharger le fichier ./src/index.ts. De plus, cela facilitera l’ajout d’autres cas d’utilisation à ce fichier au fur et à mesure de l’évolution de notre application.

Création du service

Pour créer le service notification, saisir la commande ci dessous :

yarn strapi generate service

# Répondez de la manière suivante :
Service name : notifcation
Where do you want to add this service? Add service to an existing API
Which API is this for? conversation
✔  ++ /api/conversation/services/notification.ts

Ajouter le code ci-dessous pour la gestion de notre use case dans le fichier /api/conversation/services/notification.ts

import { factories } from '@strapi/strapi';

export default factories.createCoreService('api::conversation.conversation', ({ strapi }) => ({
    subscribe(event){
        const { model, result } = event;
        if (!(
            ["api::message.message"].includes(model.uid) && 
            ["afterCreate"].includes(event.action)
        )){
            return;
        }
        this.handle(event);
    },
    async handle(event) {
        const { result, model } = event;
        switch (model.uid) {
            case 'api::message.message':
                await this.fromMessage(result);
                break;
        }
    },
    async fromMessage(result) {
        // Récupérer le message avec les données de la conversation
        const message = await strapi.entityService.findOne(
            "api::message.message",
            result.id,
            { populate: ["conversation"] }
        );

        if (!message) {
            console.error('Message not found');
            return;
        }

        let fieldToUpdate = 'nonLusFournisseur';
        let valueFieldToUpdate = message.conversation.nonLusFournisseur;

        if (message.type === 'CLIENT') {
            fieldToUpdate = 'nonLusClient';
            valueFieldToUpdate = message.conversation.nonLusClient;
        }
        
        // Mettre à jour la conversation avec le nouveau nombre de messages non lus
        await strapi.entityService.update(
            "api::conversation.conversation",
            message.conversation.id,
            {
                data: {
                    [fieldToUpdate]: valueFieldToUpdate + 1
                }
            }
        );

    },

}));

Nous allons à présent appeler notre service depuis ./src/index.ts. Pour ce faire, nous aurons besoin, tout d’abord, de trouver le nom système de notre service précédemment créé.

yarn strapi services:list

Invocation de notre service en réponse aux événements émis par la base de données :

export default {

  register(/*{ strapi }*/) {},

  bootstrap({ strapi }) {
    strapi.db.lifecycles.subscribe((event) => {
      // Appel du service Notification 
      strapi.service('api::conversation.notification').subscribe(event);
    });
  },
  
};

Tester

D’abord se connecter pour obtenir un token d’authentification.

curl -X POST http://localhost:1337/api/auth/local -H "Content-Type: application/json" -d '{
  "identifier": "[email protected]",
  "password": "123456"
}';

Publiez un message afin de vérifier que le champ nonLusFournisseur du content-type Conversation est bien incrémenté :

curl -X POST http://localhost:1337/api/messages \
-H "Content-Type: application/json" \
-H "Authorization: Bearer VOTRE_JETON" \
-d '{
    "data": {
        "text": "Merci de me rappeler",
        "lu": false,
        "type": "FOURNISSEUR",
        "conversation": [1]
    }
}'

Conclusion

L’utilisation des hooks au niveau global nous a permis de réagir de manière dynamique aux événements de la base de données, en particulier à travers l’événement “afterCreate” du Content-Type “Message”. Cette approche flexible, bien que centrée sur notre cas d’utilisation actuel, offre également la possibilité d’écouter différents événements de plusieurs Content-Types pour mettre à jour le Content-Type “Conversation”. En adoptant une approche organisée et maintenable, nous avons créé un service dédié au Content-Type “Conversation”, localisé dans son propre dossier. En l’appelant depuis le fichier ./src/index.ts, nous avons assuré une structure de code claire et extensible pour répondre à d’autres cas d’utilisation à mesure que notre application évolue.

Mamadou Diagne
Mamadou Diagne
Architecte logiciel & CTO

Diplômé d'ETNA, la filière d'alternance d'Epitech, j'ai acquis une expertise solide dans le développement d'applications, travaillant sur des projets complexes et techniquement diversifiés. Mon expérience englobe l'utilisation de divers frameworks et langages, notamment Symfony, Api Platform, Drupal, Zend, React Native, Angular, Vue.js, Shell, Pro*C...

0 0 votes
Évaluation de l'article
guest
0 Commentaires
Commentaires en ligne
Afficher tous les commentaires

Ingénierie informatique (SSII)

Applize crée des logiciels métiers pour accompagner les entreprises dans la transition vers le zéro papier.


Avez-vous un projet en tête ? Discutons-en.