# Support de l'historique de liens de contenu Document de présentation technique afin d’ajouter le support de l’historique des liens à travers les contenus Radio-Canadiens ## Problématique rencontrée L'équipe de Mordu.ca fait face à certains problèmes concernant le manque de support d'historique des liens de contenus. Pour exemple, si un utilisateur sauvegarde un lien de recette dans les favoris de son navigateur et que le lien (principalement le slug) de cette recette change entre le partage de son lien et maintenant, l'accès à la recette via l'ancien lien dans ses favoris retournera une erreur 404 puisque l'application Mordu.ca ainsi que son BFF ne sont pas en mesure d'obtenir l'historique des liens valides pour une recette et ainsi retourner le contenu demandé. Le même problème est présent pour les partages sur les réseaux sociaux et tout autre cas où un lien doit pouvoir perdurer dans le temps. Afin de palier à ce manque d'historique des liens de contenu, une partie de la solution a déjà été implémenté via les `CanonicalWebLinkHistory` pour les contenus de type `Longform`. Cependant, l'application de la mécanique des `CanonicalWebLinkHistory` à travers tous les types de contenus serait impensable et exagérée puisque pour les `CanonicalWebLinkHistory`, c'est tout le contenu qui est versionné et non seulement les éléments composants l'url. ## Solutions proposées Afin de palier à ce problème, nous proposons 2 solutions possibles à implémentés afin de supporter un changement partiel (le slug uniquement) d'un lien de contenu ou de supporter un changement complet (le slug et le schéma d'url) d'un lien de contenu. ### Solution 1 - Support de l'historique des slugs Cette approche propose simplement de conserver une trace des changements de slugs sans prendre en compte les changements possibles des schémas d'url. Ces derniers sont tout de même présents dans l'historique des liens, mais sans support de leur historique et sous forme de string. Cette approche est la plus simple, mais n'offre pas de support en cas de modification d'un schéma d'url. Dans le projet CMS, présentement les schémas d'url sont codés en dur et sont non dynamiques (voir [RC.RcGraph.DomainModel/Shared/UrlPatterns/UrlPatterns.cs](https://dev.azure.com/rcmn/_git/CMS?path=RcGraph/RC.RcGraph.DomainModel/Shared/UrlPatterns/UrlPatterns.cs)). Ils sont donc moins appelés à être modifiés. Voici, sous forme de diagramme de séquence, un exemple de requête pour un contenu de type recette. ![Flow - Historique des liens de contenu - Solution 1](https://i.imgur.com/Bx1EXyH.png) Afin de supporter cette approche, au niveau du domaine Athena, les deux types suivants seraient à ajouter. ```graphql # Représente l'historique des liens pour un contenu type ContentWebLinkHistory { # Identifiant du contenu auquel l'historique des liens fait référence id: Int! # Identifiant du type de contenu auquel l'historique des liens fait référence contentTypeId: Int! # Historique des liens du contenu ordonnées par numéro de version contentWebLinks: [ContentWebLinkItem!]! } # Représente un lien pour un contenu type ContentWebLinkItem { # Identifiant du contenu auquel le lien fait référence id: Int! # Identifiant du type de contenu auquel le lien fait référence contentTypeId: Int! # Partie personnalisable du lien slug: String! # Lien complet link: String! # Numéro de version du lien versionNumber: Int! # Le schéma d'url du lien urlPattern: String! } ``` L'ajout et support de ces types dans les différents éléments du domaine ayant un lien de contenu pourrait ensuite se faire de façon progressive et sans impact. Pour sa part, dans RCGraph, la sauvegarde de l'historique se ferait directement au niveau des documents de contenus lorsqu'un document est sauvegardé. Par exemple pour un contenu de type recette (`ContentsOfType123`) ```json { "_id": 1321, "Title": "Mousse de foie de volaille", "Slug": { "IsOverridden": true, "OverriddenValue": "mousse-foie-volaille", "DefaultValue": "mousse-de-foie-de-volaille" }, "Availability": { "StartsAt": null, "EndsAt": null }, "UrlPatternId": 31, "ContentWebLinkHistory": [ { "Slug": "mousse-de-foie-de-volaille", "Link": "http://ici.radio-canada.ca/alimentation/recette/1321/mousse-de-foie-de-volaille", "VersionNumber": 1, "UrlPattern": "/mordu/recettes/${id}/${slug}" }, { "Slug": "mousse-foie-volaille", "Link": "http://ici.radio-canada.ca/alimentation/recette/1321/mousse-foie-volaille", "VersionNumber": 2, "UrlPattern": "/mordu/recettes/${id}/${slug}" } ], ... } ``` ### Solution 2 - Support de l'historique des slugs et des schémas d'url La deuxième approche propose non seulement de supporter l'historique des slugs, mais aussi des schémas d'url. Ainsi, si le schéma d'url change pour un type de contenu, tous les liens utilisants l'ancien schéma d'url continuerons de pointer vers le bon url. Cette approche est plus complexe et potentiellement inutile si les schémas d'url ne sont jamais appelés à être changés, contrairement au slugs. Cependant, elle mérite tout de même d'être abordé afin d'offrir une couverture complète du support de changements d'urls. Présentement, les schémas d'url sont codés en dur dans les différents produits, dont le client Mordu.ca. Ainsi, si le schéma d'url change pour l'un des types de contenu utilisé par un produit, ce produit (ainsi que tous les produits utilisant ce type de contenu) devra mettre à jour son schéma d'url afin de pouvoir supporter le changement. En exposant un historique des schémas d'url, il serait possible de dynamiser les routeurs au niveaux des produits afin de supporter les changements de schémas de façon automatique et sans déploiement. Voici, sous forme de diagramme de séquence, un exemple de requête pour un contenu de type recette. ![Flow - Historique des liens de contenu - Solution 2](https://i.imgur.com/dNWizm2.png) Afin de supporter cette approche, au niveau du domaine Athena, les types et queries suivantes seraient à ajouter. ```graphql # Représente l'historique des liens pour un contenu type ContentWebLinkHistory { # Identifiant du contenu auquel l'historique des liens fait référence id: Int! # Identifiant du type de contenu auquel l'historique des liens fait référence contentTypeId: Int! # Historique des liens du contenu ordonnées par numéro de version contentWebLinks: [ContentWebLinkItem!]! } # Représente un lien pour un contenu type ContentWebLinkItem { # Identifiant du contenu auquel le lien fait référence id: Int! # Identifiant du type de contenu auquel le lien fait référence contentTypeId: Int! # Partie personnalisable du lien slug: String! # Lien complet link: String! # Numéro de version du lien versionNumber: Int! # Le schéma d'url du lien urlPattern: String! # Le schéma d'url du lien } # Représente le schéma d'url pour un type de contenu type UrlPattern { # Identifiant du schéma d'url id: Int! # Identifiant du type de contenu sur lequel est appliqué le schéma d'url contentTypeId: Int! # Numéro de version du schéma d'url versionNumber: Int! # Le schéma d'url pattern: String! } # Représente l'historique des liens pour un contenu type UrlPatternHistory { # Identifiant du schéma d'url id: Int! # Identifiant du type de contenu sur lequel est appliqué le schéma d'url contentTypeId: Int! # Historique des schémas d'url ordonnés par numéro de version urlPatterns: [UrlPattern!]! } # Paramètres de recherche pour une requête de `urlPatternHistory` input UrlPatternHistoryInput { urlPatternId: Int! } # Paramètres de recherche pour une requête de `urlPatternHistories` input UrlPatternHistoriesInput { urlPatternIds: [Int!]! } type Query { # Requête pour avoir un `UrlPatternHistory` urlPatternHistory(params: UrlPatternHistoryInput!): UrlPatternHistory # Requête pour avoir une liste de `UrlPatternHistory` urlPatternHistories(params: UrlPatternHistoriesInput!): [UrlPatternHistory]! } ``` Au niveau de RCGraph maintenant, ce changement demanderait de déplacer les informations des schémas d'url vers un nouveau type de document afin de pouvoir en gérer leur historique. Puisque les modifications au niveau des schémas d'url seraient rares, une entrée manuelle pourrait être une solution envisageable. Au niveau des documents impactés par ce changement de schémas d'url, puisqu'un schéma d'url est lié à un type de document (`ContentsOfTypeXXXX`), une écriture en masse dans les documents du type impacté devrait être fait afin d'ajouter à l'historique de chacun des documents un nouvel élément contenant le nouveau schéma d'url. Par exemple pour un contenu de type recette ```json { "ContentsOfType123": [ { "_id": 1321, "Title": "Mousse de foie de volaille", "Slug": { "IsOverridden": true, "OverriddenValue": "mousse-foie-volaille", "DefaultValue": "mousse-de-foie-de-volaille" }, "Availability": { "StartsAt": null, "EndsAt": null }, "UrlPatternId": 31, "ContentWebLinkHistory": [ { "Slug": "mousse-de-foie-de-volaille", "Link": "http://ici.radio-canada.ca/mordu/recette/1321/mousse-de-foie-de-volaille", "VersionNumber": 1, "UrlPattern": "/mordu/recette/${id}/${slug}" }, { "Slug": "mousse-de-foie-de-volaille", "Link": "http://ici.radio-canada.ca/mordu/recettes/1321/mousse-de-foie-de-volaille", "VersionNumber": 2, "UrlPattern": "/mordu/recettes/${id}/${slug}" }, { "Slug": "mousse-foie-volaille", "Link": "http://ici.radio-canada.ca/mordu/recettes/1321/mousse-foie-volaille", "VersionNumber": 3, "UrlPattern": "/mordu/recettes/${id}/${slug}" } ] }, ... ], // Le nouveau type de contenu UrlPattern "ContentsOfTypeXXXX": [ { "_id": 31, "ContentTypeId": 123, "Name": "Recette", "ScoopSection": 14, "UrlPattern": "/mordu/recettes/${id}/${slug}", "UrlPatternHistory": [ { "VersionNumber": 1, "UrlPattern": "/mordu/recette/${id}/${slug}" }, { "VersionNumber": 2, "UrlPattern": "/mordu/recettes/${id}/${slug}" } ] }, ... ] } ``` ## Adoptions et limitations L'avantage des deux solutions est qu'elles peuvent être intégrées de façon graduelles pour chacun des types de contenus où l'on juge qu'un historique des liens serait pertinent. Concernant la choix de solution, si la solution 1 est choisi, il sera tout de même possible plus tard de passer à la solution 2 puisque cette dernière est complémentaire à la solution 1. Au niveau des limitations, la deuxième solution présente un risque de conflit en cas de changement de positionnement des `id` et `slug` dans le schémas d'url. Les changements de schémas d'url ne devraient donc n'être qu'orthographique. Dans l'exemple ci-dessous, puisque les routeurs écouterons autant les appels sur `/mordu/recettes/${id}/${slug}` que sur `/mordu/recettes/${slug}/${id}`, il ne sera pas possible de déterminer le positionnement de notre `id` et `slug`. ```json { "ContentsOfType123": [ { "_id": 1321, "Title": "Mousse de foie de volaille", "Slug": { "IsOverridden": true, "OverriddenValue": "mousse-foie-volaille", "DefaultValue": "mousse-de-foie-de-volaille" }, "Availability": { "StartsAt": null, "EndsAt": null }, "UrlPatternId": 31, "ContentWebLinkHistory": [ { "Slug": "mousse-de-foie-de-volaille", "Link": "http://ici.radio-canada.ca/mordu/recette/1321/mousse-de-foie-de-volaille", "VersionNumber": 1, "UrlPattern": "/mordu/recette/${id}/${slug}" }, { "Slug": "mousse-de-foie-de-volaille", "Link": "http://ici.radio-canada.ca/mordu/recettes/1321/mousse-de-foie-de-volaille", "VersionNumber": 2, "UrlPattern": "/mordu/recettes/${id}/${slug}" }, { "Slug": "mousse-foie-volaille", "Link": "http://ici.radio-canada.ca/mordu/recettes/1321/mousse-foie-volaille", "VersionNumber": 3, "UrlPattern": "/mordu/recettes/${id}/${slug}" }, { "Slug": "mousse-foie-volaille", "Link": "http://ici.radio-canada.ca/mordu/recettes/mousse-foie-volaille/1321", "VersionNumber": 4, "UrlPattern": "/mordu/recette/${slug}/${id}" } ] } ], "ContentsOfTypeXXXX": [ { "_id": 31, "ContentTypeId": 123, "Name": "Recette", "ScoopSection": 14, "UrlPattern": "/mordu/recettes/${id}/${slug}", "UrlPatternHistory": [ { "VersionNumber": 1, "UrlPattern": "/mordu/recette/${id}/${slug}" }, { "VersionNumber": 2, "UrlPattern": "/mordu/recettes/${id}/${slug}" }, { "VersionNumber": 3, "UrlPattern": "/mordu/recettes/${slug}/${id}" } ] } ] } ```