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
Use case
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:
Client, Type: IntegernonLus
- Relation:
fourni
sseur, 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 :
- Accédez à “Settings” > “Users & Permissions Plugins” > “Roles”.
- Appuyez sur “Add new role” puis renseignez “Fournisseur” dans le champ “Name”.
- Dans la section “Conversation” et “Message”, assurez-vous que les autorisations suivantes sont correctement activées : “Create”, “Find” et “FindOne”.
- Répétez la même procédure pour le rôle “Client”.
- 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.
Architecte logiciel, Développeur d'application diplomé 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...