Strapi v4 : Tags avec garantie d’unicité

Introduction

La fonctionnalité de tag est souvent indispensable lors de la création d’applications web. Elle offre une méthode pour organiser et catégoriser le contenu, que ce soit dans un blog ou tout autre système de gestion de contenu. En permettant aux utilisateurs d’associer des mots-clés à leur contenu, les tags facilitent la recherche, la navigation et la découverte de contenu pertinent. Dans cet article, nous allons étudier comment gérer efficacement un système de tag en utilisant le CMS Strapi, en mettant l’accent sur l’importance de l’unicité des tags, même lorsque ceux-ci sont utilisés par plusieurs types de contenu. Nous discuterons également des avantages et des meilleures pratiques associés à l’utilisation des tags.

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

Supposons que nous développons un blog avec Strapi où les utilisateurs peuvent publier des articles sur différents sujets. Nous souhaitons permettre aux auteurs de catégoriser leurs articles en utilisant des tags afin que les lecteurs puissent facilement trouver du contenu sur des sujets qui les intéressent.

Lors de la création d’un article, l’utilisateur doit avoir la possibilité de spécifier un tag existant ou d’en créer un nouveau. Si le tag spécifié n’existe pas déjà, il sera automatiquement créé dans la table des tags. Cependant, un contrôle d’unicité est mis en place pour éviter la duplication des tags dans cette table.

D’autre part, il est également possible de créer un tag directement dans la table des tags avec une vérification d’unicité également. Cela garantit que chaque tag ajouté à la base de données est unique, évitant ainsi les doublons et assurant la cohérence de la classification du contenu.

Création des content-types

Nous devons créer les deux types de contenu : “Tag” et “Article”.

Tag

Nous allons créer un content type appelé “Tag” dans Strapi. Ce modèle comprend les champs ci dessus :

  • name, Type: string
  • code, Type: string
? Content type display name : Tag
? Content type singular name : tag
? Content type plural name : tags
? Please choose the model type : Collection Type
? Use draft and publish? No
? Do you want to add attributes? Yes
? Name of attribute : name
? What type of attribute : string
? Do you want to add another attribute? Yes
? Name of attribute : code
? What type of attribute : string
? 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? tag
? Bootstrap API related files? Yes
√  ++ \api\tag\content-types\tag\schema.json
√  +- \api\tag\content-types\tag\schema.json
√  ++ \api\tag\controllers\tag.ts
√  ++ \api\tag\services\tag.ts
√  ++ \api\tag\routes\tag.ts

Article

Nous allons créer un content type appelé “Article” dans Strapi. Ce modèle comprend les champs ci dessus :

  • title, Type: string
  • content, Type: text
  • Tags, Type: has and belongs to many
? Content type display name : Article
? Content type singular name : article
? Content type plural name : articles
? Please choose the model type : Collection Type
? Use draft and publish? No
? Do you want to add attributes? Yes
? Name of attribute : title
? What type of attribute : string
? Do you want to add another attribute? Yes
? Name of attribute : content
? What type of attribute : text
? 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? article
? Bootstrap API related files? Yes
√  ++ \api\article\content-types\article\schema.json
√  +- \api\article\content-types\article\schema.json
√  ++ \api\article\controllers\article.ts
√  ++ \api\article\services\article.ts
√  ++ \api\article\routes\article.ts

Maintenant, ajoutons manuellement la relation au niveau du type de contenu Article.

Configurer les permissions

  1. Allez dans la section “Settings” > “Roles”.
  2. Sélectionnez le rôle “Public”.
  3. Assurez-vous que dans la section Article et Tag, les permissions update, create, find, findOne est correctement sélectionnée.

Remplissage des données

Dans cette section, nous allons procéder à la création d’un enregistrement dans notre entité Tag afin de préparer nos tests pour les étapes à venir.

Création du service de gestion des Tags

Nous allons mettre en place un service dans Strapi pour garantir l’unicité des tags, car chaque tag doit être unique au sein de la table des tags. Pour cela, nous avons introduit un champ supplémentaire nommé “code”, qui stockera le tag en majuscules. Cette approche assure une unicité insensible à la casse.

Notre objectif est de créer un service standard qui sera utilisé dans deux cas principaux : lors de la soumission d’un article, pour gérer les tags associés lors de la création ou de l’édition d’un article, et lors de la manipulation des tags eux-mêmes, pour vérifier si le tag en cours d’insertion ou de modification n’existe pas déjà dans la table.

Cette stratégie nous permettra de maintenir la cohérence des tags dans notre système, en évitant les doublons et en assurant que chaque tag est unique.

Pour créer le service saisissez la commande ci dessous :

yarn strapi generate service
? Service name : tagManager
? Where do you want to add this service? Add service to an existing API
? Which API is this for? tag
√  ++ \api\tag\services\tagManager.ts

Et pour finir déposez le contenu ci-dessous dans le fichier \api\tag\services\tagManager.ts

/**
 * tagManager service
 */
import { factories } from '@strapi/strapi';

interface TagData {
    id?: string;
    name?: string;
}

export default factories.createCoreService('api::tag.tag', ({ strapi }) => ({
    
    async createOrLinkList(tags: TagData[]) {
        if (tags?.length) {
            return await Promise.all(tags.map(
              tagData => this.createOrLinkOne(tagData)
            ));
        }
        return [];
    },

    async createOrLinkOne(tagData: TagData){
        const { name, id } = tagData;
        if (name || id) {
            if (id) return id;

            const tag = await this.getTagByName(name);

            if (tag) return tag.id;
            else {
                const formattedTag = this.formatTag(tagData);
                const createdTag = await strapi.entityService.create(
                  'api::tag.tag', 
                  { data: formattedTag }
                );
                return createdTag.id;
            }
        }
        return null;
    },

    async isTagExist(tagData: TagData) {
        const { name, id } = tagData;
        if (name || id) {
            const nameUpperCase = name.toUpperCase();
            let filters = { code: { $eq: nameUpperCase } };

            if (id) filters['id'] = { $ne: id };

            const count = await strapi.db.query("api::tag.tag").count({ 
              where: { ...filters } 
            });

            return count > 0;
        }
        return false;
    },

    async getTagByName(name: string){
        const nameUpperCase = name.toUpperCase();
        const [tag] = await strapi.entityService.findMany('api::tag.tag', {
            limit: 1,
            filters: { code: nameUpperCase },
            populate: {},
        });

        return tag || null;
    },

    formatTag(tagData: TagData){
        return {
            ...tagData,
            code: tagData.name.toUpperCase()
        };
    }
    
}));

Traitement des tags en tant que relation

Comme évoqué précédemment, cette section se concentre sur le traitement des tags lorsqu’ils sont créés à partir du type de contenu “article”. À cette fin, nous utiliserons un middleware qui sera invoqué lors de la création et de la modification d’un article.

Pour créer le middleware saisissez la commande ci-dessous :

strapi generate middleware
? Middleware name : writeOne
? Where do you want to add this middleware? Add middleware to an existing API
? Which API is this for? article
√  ++ \api\article\middlewares\writeOne.ts

Déposez le contenu ci-dessous dans le fichier \api\article\middlewares\writeOne.ts

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

export default (config, { strapi }: { strapi: Strapi }) => {
  return async (ctx, next) => {
    ctx.request.body.data = strapi.service('api::tag.tag-manager').formatTag(ctx.request.body.data);
    await next();
  };
};

Pour invoquer le middleware lors de la création et de l’édition d’un article ouvrez le fichier \api\article\routes\article.ts et ajoutez-y le code ci-dessous :

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

export default factories.createCoreRouter('api::article.article', {
    config: {
        create: {
            middlewares: ["api::article.article.write-one"],
        },
        update: {
            middlewares: ["api::article.article.write-one"],
        }
    },
});

Traitement des tags

Gestion de l’unicité

Pour assurer l’unicité des tags au niveau du type de contenu “Tag”, nous allons créer une policy qui permettra la création ou la modification d’un tag uniquement s’il n’existe pas déjà. Dans le cas où le tag existe déjà, la création sera interdite.

strapi generate policy
? Policy name : unicityCheck
? Where do you want to add this policy? Add policy to an existing API
? Which API is this for? tag
√  ++ \api\tag\policies\unicityCheck.ts

Déposez le contenu ci-dessous dans le fichier \api\article\middlewares\writeOne.ts

import { errors } from '@strapi/utils';
const { ApplicationError } = errors;


export default async  (policyContext, config, { strapi }) => {
    const { id }  = policyContext.params;
    const { data } = policyContext.request.body;

    if (id) data['id'] = id;
    const isExist = await strapi.service('api::tag.tag-manager').isTagExist(data);
    
    if (isExist) {
      throw new ApplicationError('Tag already exists', {
        resource: 'Tag',
        policy: 'UnicityCheck',
        code: 'DUPLICATE_TAG'
      });
    }

    return true;
};

Génération du code du tag

Pour plus de commodité, nous avons décidé de régénérer automatiquement la valeur du champ “code” de notre entité Tag à chaque fois que notre API Tag est invoquée pour une opération de modification ou de création. Le champ “code” de notre entité Tag, comme vous vous en souvenez, enregistre le tag en majuscules, et c’est sur cette valeur que repose notre fonction de contrôle d’unicité. Pour ce faire, nous allons mettre en place un middleware qui modifie notre charge utile (payload) en remplissant le champ “code” avec la valeur en majuscules du nom du tag.

strapi generate middleware
? Middleware name writeOne
? Which API is this for? tag
√  ++ \api\tag\middlewares\writeOne.ts

Déposez le contenu ci-dessous dans le fichier \api\tag\middlewares\writeOne.ts

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

export default (config, { strapi }: { strapi: Strapi }) => {
  return async (ctx, next) => {
    ctx.request.body.data = strapi.service('api::tag.tag-manager').formatTag(ctx.request.body.data);
    await next();
  };
};

Pour invoquer la policy et le middleware lors de la création et de l’édition d’un Tag ouvrez le fichier \api\tag\routes\tag.ts et ajoutez-y le code ci-dessous :

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

export default factories.createCoreRouter('api::tag.tag', {
    config: {
        create: {
            policies: ["api::tag.unicity-check"],
            middlewares: ["api::tag.write-one"]
        },
        update: {
            policies: ["api::tag.unicity-check"],
            middlewares: ["api::tag.write-one"]
        }
    },
});

Tester

Pour tester vous pouvez utiliser Postman avec la configuration ci-dessous :

Publication d’un article

Pour l’édition, la méthode POST doit être remplacée par PUT et l’URL devient http://localhost:1337/api/articles/[id_article].

Publication d’un tag

Pour l’édition, la méthode POST doit être remplacée par PUT et l’URL devient http://localhost:1337/api/tags/[id_tag].

Conclusion

En optimisant le traitement des tags, nous offrons une expérience utilisateur améliorée qui facilite la recherche et la navigation à travers le contenu. De plus, en suivant les meilleures pratiques de développement, nous assurons la robustesse et la fiabilité de notre application.

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.