diff --git a/.gitea/workflows/pr-endpoint-check.yml b/.gitea/workflows/pr-endpoint-check.yml index 1aba34b..9cd7435 100644 --- a/.gitea/workflows/pr-endpoint-check.yml +++ b/.gitea/workflows/pr-endpoint-check.yml @@ -74,7 +74,6 @@ jobs: run: | chmod +x scripts/test-endpoints.sh bash scripts/test-endpoints.sh http://localhost:5038 1000 2>&1 | tee /tmp/webzine_endpoint_output.txt - EXIT_CODE=${PIPESTATUS[0]} FAIL_COUNT=$(grep -cE "^\[ÉCHEC\]" /tmp/webzine_endpoint_output.txt 2>/dev/null || echo 0) SLOW_COUNT=$(grep -cE "^\[LENT\]" /tmp/webzine_endpoint_output.txt 2>/dev/null || echo 0) @@ -137,10 +136,4 @@ jobs: -H "Authorization: token $GITEA_TOKEN" \ -H "Content-Type: application/json" \ -d "$(jq -n --arg body "$BODY" '{body: $body}')" \ - "$GITEA_SERVER_URL/api/v1/repos/$REPO/issues/$PR_NUMBER/comments" - - - name: Fail job if performance issues detected - if: steps.perf_test.outputs.failed > 0 || steps.perf_test.outputs.slow > 0 - run: | - echo "❌ Job failed due to performance issues" - exit 1 \ No newline at end of file + "$GITEA_SERVER_URL/api/v1/repos/$REPO/issues/$PR_NUMBER/comments" \ No newline at end of file diff --git a/Webzine.Business.Contracts/IDashboardService.cs b/Webzine.Business.Contracts/IDashboardService.cs index 8bde456..423880f 100644 --- a/Webzine.Business.Contracts/IDashboardService.cs +++ b/Webzine.Business.Contracts/IDashboardService.cs @@ -1,7 +1,7 @@ -using Webzine.Business.Contracts.Dto; - namespace Webzine.Business.Contracts; +using Webzine.Business.Contracts.Dto; + /// /// Service responsable du calcul des statistiques affichées sur le tableau de bord d'administration. /// Agrège les données provenant de plusieurs repositories pour produire un résumé cohérent. diff --git a/Webzine.Business/DashboardService.cs b/Webzine.Business/DashboardService.cs index d9e9812..f1b566d 100644 --- a/Webzine.Business/DashboardService.cs +++ b/Webzine.Business/DashboardService.cs @@ -1,10 +1,9 @@ -using Webzine.Business.Contracts; -using Webzine.Entity; -using Webzine.Repository.Contracts; - namespace Webzine.Business; -using Contracts.Dto; +using Webzine.Business.Contracts; +using Webzine.Business.Contracts.Dto; +using Webzine.Entity; +using Webzine.Repository.Contracts; /// /// Implémentation de . diff --git a/Webzine.Documentation/git_hooks/commit-msg b/Webzine.Documentation/git_hooks/commit-msg index eb15525..6d124fa 100644 --- a/Webzine.Documentation/git_hooks/commit-msg +++ b/Webzine.Documentation/git_hooks/commit-msg @@ -6,6 +6,11 @@ COMMIT_MSG=$(cat "$1") +# Skip validation for rebase or CI commits +if echo "$COMMIT_MSG" | grep -qiE "(Rebase|rebase|CI|merge|Merge)"; then + exit 0 +fi + if [ ${#COMMIT_MSG} -le 10 ]; then echo "❌ Erreur : Le message doit faire plus de 10 caractères." exit 1 diff --git a/Webzine.Repository.Contracts/IArtisteRepository.cs b/Webzine.Repository.Contracts/IArtisteRepository.cs index ee170b5..1985ddc 100644 --- a/Webzine.Repository.Contracts/IArtisteRepository.cs +++ b/Webzine.Repository.Contracts/IArtisteRepository.cs @@ -3,7 +3,7 @@ namespace Webzine.Repository.Contracts using Webzine.Entity; /// - /// Défini une interface pour gérer les opérations de base de données liées aux artistes. + /// Défini une interface pour gérer les opérations des artistes dans la source de données. /// public interface IArtisteRepository { @@ -23,7 +23,7 @@ namespace Webzine.Repository.Contracts /// Récupère un artiste par son identifiant unique. Si aucun artiste n'est trouvé, retourne null. /// /// L'identifiant de l'artiste. - /// + /// L'artiste trouvé ou null. Artiste Find(int id); /// diff --git a/Webzine.Repository.Contracts/ICommentaireRepository.cs b/Webzine.Repository.Contracts/ICommentaireRepository.cs index 3529d83..c87fb9e 100644 --- a/Webzine.Repository.Contracts/ICommentaireRepository.cs +++ b/Webzine.Repository.Contracts/ICommentaireRepository.cs @@ -2,14 +2,43 @@ namespace Webzine.Repository.Contracts { using Webzine.Entity; + /// + /// Interface de repository pour les commentaires. + /// public interface ICommentaireRepository { + /// + /// Ajoute un commentaire à la source de données. + /// + /// Commentaire à ajouter. void Add(Commentaire commentaire); + /// + /// Supprime un commentaire de la source de données. + /// + /// Commentaire à supprimer. void Delete(Commentaire commentaire); + /// + /// Trouve un commentaire par son ID. + /// + /// ID du commentaire à trouver. + /// Le commentaire trouvé, ou null si non trouvé. Commentaire Find(int id); + /// + /// Retourne tous les commentaires de la source de données. + /// + /// Une collection de commentaires. IEnumerable FindAll(); + + /// + /// Retourne une collection de commentaires paginée à partir de la source de données. + /// + /// Le nombre de commentaires à ignorer avant de commencer à + /// récupérer les commentaires. + /// Le nombre maximum de commentaires à récupérer. + /// Une collection de commentaires paginée. + IEnumerable FindCommentaires(int offset, int limit); } } \ No newline at end of file diff --git a/Webzine.Repository/DbArtisteRepository.cs b/Webzine.Repository/DbArtisteRepository.cs index f6f868b..01bc409 100644 --- a/Webzine.Repository/DbArtisteRepository.cs +++ b/Webzine.Repository/DbArtisteRepository.cs @@ -13,7 +13,6 @@ namespace Webzine.Repository /// /// Initialise une classe qui implémente l'interface pour gérer les opérations de base de données liées aux artistes. - /// Utilise en injection de dépendances. /// public class DbArtisteRepository : IArtisteRepository { @@ -23,8 +22,8 @@ namespace Webzine.Repository /// /// Initializes a new instance of the class. /// - /// Le contexte de base de données à utiliser pour accéder aux entités et effectuer des opérations de - /// persistance. Ne peut pas être null. + /// Le contexte de base de données à utiliser pour accéder aux entités et effectuer des opérations de persistance. + /// Le service de journalisation. public DbArtisteRepository(WebzineDbContext context, ILogger logger) { this.logger = logger; @@ -56,11 +55,6 @@ namespace Webzine.Repository { try { - if (artiste == null) - { - throw new ArgumentNullException(nameof(artiste), "L'artiste à supprimer ne peut pas être null."); - } - this.context.Artistes.Remove(artiste); this.context.SaveChanges(); this.logger.LogDebug("L'artiste {IdArtiste} a bien été supprimé", artiste.IdArtiste); @@ -84,7 +78,7 @@ namespace Webzine.Repository { Artiste artiste = this.context.Artistes .Include(a => a.Titres) - .FirstOrDefault(a => a.IdArtiste == id); + .SingleOrDefault(a => a.IdArtiste == id); return artiste; } catch (Exception ex) @@ -118,27 +112,33 @@ namespace Webzine.Repository try { // .AsNoTracking() rend la requête beaucoup plus rapide pour de la lecture - var artistes = this.context.Artistes.AsNoTracking().Include(t => t.Titres).ToList(); - this.logger.LogDebug("{Count} artistes récupérés de la base.", artistes.Count); + // Pas besoin de faire un ToList() ici, car on retourne un IEnumerable et EF Core gère l'exécution différée de la requête. + var artistes = this.context.Artistes + .AsNoTracking() + .Include(t => t.Titres); + + this.logger.LogDebug("La liste d'artistes a été récupérée de la base."); return artistes; } catch (Exception ex) { this.logger.LogError(ex, "Erreur lors de la récupération de tous les artistes."); - return Enumerable.Empty(); // Retourne une liste vide au lieu de faire crash l'UI + throw; } } /// public void Update(Artiste artiste) { - if (artiste == null) - { - throw new ArgumentNullException(nameof(artiste)); - } - try { + Artiste existingArtiste = this.Find(artiste.IdArtiste); // Vérifie que l'artiste existe avant de tenter de le mettre à jour + if (existingArtiste == null) + { + this.logger.LogWarning("L'artiste {Id} n'a pas été trouvé pour l'update.", artiste.IdArtiste); + throw new InvalidOperationException($"L'artiste avec l'ID {artiste.IdArtiste} n'a pas été trouvé pour la mise à jour."); + } + this.context.Artistes.Update(artiste); this.context.SaveChanges(); this.logger.LogDebug("Artiste {Id} ({Nom}) mis à jour avec succès.", artiste.IdArtiste, artiste.Nom); @@ -167,8 +167,7 @@ namespace Webzine.Repository var artiste = this.context.Artistes .Where(a => a.Nom.ToLower().Contains(mot.ToLower())) .Include(t => t.Titres) - .AsNoTracking() - .ToList(); + .AsNoTracking(); return artiste; } catch (Exception ex) diff --git a/Webzine.Repository/DbCommentaireRepository.cs b/Webzine.Repository/DbCommentaireRepository.cs index bfc5727..a101dfd 100644 --- a/Webzine.Repository/DbCommentaireRepository.cs +++ b/Webzine.Repository/DbCommentaireRepository.cs @@ -16,10 +16,9 @@ public class DbCommentaireRepository : ICommentaireRepository private readonly WebzineDbContext context; /// - /// Initializes a new instance of the class. /// Initialisation de . /// - /// Le service de journalisation injecté pour suivre les opérations du repository. + /// Le service de journalisation. /// Le contexte de base de données injecté. public DbCommentaireRepository(ILogger logger, WebzineDbContext context) { @@ -33,7 +32,6 @@ public class DbCommentaireRepository : ICommentaireRepository { try { - this.logger.LogDebug("Ajout d'un nouveau commentaire de l'auteur : {Auteur}", commentaire.Auteur); this.context.Commentaires.Add(commentaire); this.context.SaveChanges(); this.logger.LogDebug("Commentaire ajouté avec l'id : {Id}", commentaire.IdCommentaire); @@ -55,11 +53,6 @@ public class DbCommentaireRepository : ICommentaireRepository { try { - if (commentaire == null) - { - throw new ArgumentNullException(nameof(commentaire), "Le commentaire à supprimer ne peut pas être null."); - } - this.context.Commentaires.Remove(commentaire); this.context.SaveChanges(); this.logger.LogDebug("Le commentaire {IdCommentaire} a bien été supprimé", commentaire.IdCommentaire); @@ -76,14 +69,6 @@ public class DbCommentaireRepository : ICommentaireRepository } } - /// - public int Count() - { - var count = this.context.Commentaires.Count(); - this.logger.LogDebug("Compte total des commentaires : {Count}", count); - return count; - } - /// public Commentaire Find(int idCommentaire) { @@ -92,50 +77,41 @@ public class DbCommentaireRepository : ICommentaireRepository // On inclut le titre car il est souvent affiché avec le commentaire return this.context.Commentaires .Include(c => c.Titre) - .FirstOrDefault(c => c.IdCommentaire == idCommentaire); + .SingleOrDefault(c => c.IdCommentaire == idCommentaire); } /// public IEnumerable FindAll() { - this.logger.LogDebug("Récupération de tous les commentaires"); - var commentaires = this.context.Commentaires + .AsNoTracking() .Include(c => c.Titre) - .OrderByDescending(c => c.DateCreation) - .ToList(); + .OrderByDescending(c => c.DateCreation); - this.logger.LogDebug("Nombre de commentaires trouvés : {Count}", commentaires.Count); + this.logger.LogDebug("La liste de commentaires a été récupérée."); return commentaires; } /// public IEnumerable FindCommentaires(int offset, int limit) { - this.logger.LogDebug("Recherche paginée des commentaires (offset : {Offset}, limit : {Limit})", offset, limit); + try + { + this.logger.LogDebug("Recherche paginée des commentaires (offset : {Offset}, limit : {Limit})", offset, limit); - var commentaires = this.context.Commentaires - .Include(c => c.Titre) - .OrderByDescending(c => c.DateCreation) - .Skip(offset) - .Take(limit) - .ToList(); + var commentaires = this.context.Commentaires + .AsNoTracking() + .Include(c => c.Titre) + .OrderByDescending(c => c.DateCreation) + .Skip(offset) + .Take(limit); - this.logger.LogDebug("{Count} commentaires trouvés pour cette page", commentaires.Count); - return commentaires; - } - - /// - public IEnumerable FindByIdTitre(int idTitre) - { - this.logger.LogDebug("Recherche des commentaires pour le titre ID : {IdTitre}", idTitre); - - var commentaires = this.context.Commentaires - .Where(c => c.Titre.IdTitre == idTitre) - .OrderByDescending(c => c.DateCreation) - .ToList(); - - this.logger.LogDebug($"{commentaires.Count} commentaires trouvés pour l'ID de titre : {idTitre}"); - return commentaires; + return commentaires; + } + catch (Exception ex) + { + this.logger.LogError(ex, "Erreur lors de la pagination des commentaires (offset : {Offset}, limit : {Limit})", offset, limit); + throw new Exception("Une erreur est survenue lors de la pagination des commentaires.", ex); + } } } \ No newline at end of file diff --git a/Webzine.Repository/DbEntityRepository.cs b/Webzine.Repository/DbEntityRepository.cs index 1558cc6..9368fb6 100644 --- a/Webzine.Repository/DbEntityRepository.cs +++ b/Webzine.Repository/DbEntityRepository.cs @@ -8,10 +8,17 @@ namespace Webzine.Repository using Webzine.Entity; using Webzine.Entity.Fixtures; + /// + /// Classe de repository pour les entités de la base de données. + /// public class DbEntityRepository { private readonly WebzineDbContext context; + /// + /// Constructeur de DbEntityRepository. + /// + /// DB context. public DbEntityRepository(WebzineDbContext context) { this.context = context; diff --git a/Webzine.Repository/DbStyleRepository.cs b/Webzine.Repository/DbStyleRepository.cs index a189ffd..0412a90 100644 --- a/Webzine.Repository/DbStyleRepository.cs +++ b/Webzine.Repository/DbStyleRepository.cs @@ -93,27 +93,18 @@ public class DbStyleRepository : IStyleRepository { this.logger.LogDebug("Recherche du style avec l'ID: {Id}", id); - if (id <= 0) - { - this.logger.LogWarning("Tentative de recherche d'un style avec un Id invalide: {Id}", id); - return new Style(); - } - - this.logger.LogDebug("Préparation de la requête avec inclusion des titres"); var style = this.context.Styles + .AsNoTracking() .Include(s => s.Titres) - .FirstOrDefault(s => s.IdStyle == id); + .SingleOrDefault(s => s.IdStyle == id); if (style == null) { this.logger.LogWarning("Style avec l'ID {Id} non trouvé", id); - style = new Style(); - } - else - { - this.logger.LogDebug("Style trouvé: {Libelle}", style.Libelle); + return null; } + this.logger.LogDebug("Style trouvé: {Libelle}", style.Libelle); return style; } catch (Exception ex) @@ -132,10 +123,10 @@ public class DbStyleRepository : IStyleRepository this.logger.LogDebug("Tri des styles par libellé"); var styles = this.context.Styles - .OrderBy(s => s.Libelle) - .ToList(); + .AsNoTracking() + .OrderBy(s => s.Libelle); - this.logger.LogDebug("{Count} styles récupérés", styles.Count); + this.logger.LogDebug("La liste de styles a été récupérée."); return styles; } catch (Exception ex) @@ -151,18 +142,15 @@ public class DbStyleRepository : IStyleRepository try { this.logger.LogInformation("Mise à jour du style avec l'ID: {IdStyle}", style.IdStyle); - this.logger.LogDebug("Recherche du style en base de données"); + Style existingStyle = this.Find(style.IdStyle); // Vérifie que le style existe avant de tenter de le mettre à jour - var existingStyle = this.context.Styles.Find(style.IdStyle); if (existingStyle == null) { - this.logger.LogWarning("Style avec l'ID {IdStyle} non trouvé pour la mise à jour", style.IdStyle); - throw new InvalidOperationException($"Style avec l'ID {style.IdStyle} non trouvé."); + this.logger.LogWarning("Style avec l'ID {IdStyle} non trouvé pour l'update.", style.IdStyle); + throw new InvalidOperationException($"Style avec l'ID {style.IdStyle} non trouvé pour la mise à jour."); } - // Update properties - this.logger.LogDebug("Style trouvé, mise à jour des propriétés"); - existingStyle.Libelle = style.Libelle; + this.context.Styles.Update(style); this.context.SaveChanges(); this.logger.LogDebug("Style mis à jour avec succès: {IdStyle}", style.IdStyle); diff --git a/Webzine.Repository/DbTitreRepository.cs b/Webzine.Repository/DbTitreRepository.cs index 1c199a1..19bae9d 100644 --- a/Webzine.Repository/DbTitreRepository.cs +++ b/Webzine.Repository/DbTitreRepository.cs @@ -192,14 +192,28 @@ public class DbTitreRepository : ITitreRepository try { this.logger.LogInformation("Mise à jour du titre avec l'ID: {IdTitre}", titre.IdTitre); - var existingTitre = this.context.Titres.Find(titre.IdTitre); + + Titre existingTitre = this.Find(titre.IdTitre); if (existingTitre != null) { - this.logger.LogWarning("Aucun titre trouvé avec l'ID: {IdTitre}", titre.IdTitre); - } + this.context.Entry(existingTitre).CurrentValues.SetValues(titre); - this.context.SaveChanges(); - this.logger.LogDebug("Titre mis à jour avec succès: {IdTitre}", titre.IdTitre); + // Relation many-to-many + this.context.Entry(existingTitre).Collection(t => t.Styles).Load(); + existingTitre.Styles.Clear(); + foreach (var style in titre.Styles) + { + existingTitre.Styles.Add(style); + } + + this.context.SaveChanges(); + this.logger.LogDebug("Titre mis à jour avec succès: {IdTitre}", titre.IdTitre); + } + else + { + this.logger.LogWarning("Titre avec l'ID {IdTitre} non trouvé pour la mise à jour", titre.IdTitre); + throw new InvalidOperationException($"Titre avec l'ID {titre.IdTitre} non trouvé."); + } } catch (DbUpdateException ex) { diff --git a/Webzine.Repository/LocalArtisteRepository.cs b/Webzine.Repository/LocalArtisteRepository.cs index 0388da3..402aca1 100644 --- a/Webzine.Repository/LocalArtisteRepository.cs +++ b/Webzine.Repository/LocalArtisteRepository.cs @@ -10,22 +10,21 @@ namespace Webzine.Repository using Webzine.Repository.Contracts; /// - /// Initialise une classe qui implémente l'interface pour gérer les opérations de base de données liées aux artistes. - /// Utilise en injection de dépendances. + /// Initialise une classe qui implémente l'interface . + /// Gère les opérations liées aux artistes en utilisant une source de données locale (en mémoire). /// public class LocalArtisteRepository : IArtisteRepository { private readonly ILogger logger; - // private readonly List artistes; private readonly InMemoryDataStore dataStore; /// /// Initializes a new instance of the class. /// Est liéee à une liste d'artistes en local et utilise un logger pour enregistrer les opérations effectuées sur les artistes. /// - /// La liste des artistes à initialiser. Ne peut pas être null. /// Le logger à utiliser pour enregistrer les messages de journalisation. Ne peut pas être null. + /// Le magasin de données en mémoire. public LocalArtisteRepository(InMemoryDataStore dataStore, ILogger logger) { this.logger = logger; @@ -37,25 +36,19 @@ namespace Webzine.Repository /// public void Add(Artiste artiste) { - throw new NotSupportedException("Mode Local"); + this.dataStore.Artistes.Add(artiste); } /// public void Delete(Artiste artiste) { - throw new NotSupportedException("Mode Local"); + this.dataStore.Artistes.Remove(artiste); } /// public Artiste Find(int id) { - var artiste = this.dataStore.Artistes.First(a => a.IdArtiste == id); - if (artiste == null) - { - return new Artiste(); - } - - return artiste; + return this.dataStore.Artistes.SingleOrDefault(a => a.IdArtiste == id); } /// @@ -74,7 +67,6 @@ namespace Webzine.Repository } /// - /// La liste retournée est une copie de la liste interne, donc elle ne peut être nulle. public IEnumerable FindAll() { return this.dataStore.Artistes; @@ -83,7 +75,16 @@ namespace Webzine.Repository /// public void Update(Artiste artiste) { - throw new NotSupportedException("Mode Local"); + Artiste existingArtiste = this.Find(artiste.IdArtiste); + if (existingArtiste == null) + { + this.logger.LogWarning("L'artiste {Id} n'a pas été trouvé pour l'update.", artiste.IdArtiste); + return; + } + + existingArtiste.Nom = artiste.Nom; + existingArtiste.Biographie = artiste.Biographie; + existingArtiste.Titres = artiste.Titres; } /// diff --git a/Webzine.Repository/LocalCommentaireRepository.cs b/Webzine.Repository/LocalCommentaireRepository.cs index 8a91ab8..4e2ad79 100644 --- a/Webzine.Repository/LocalCommentaireRepository.cs +++ b/Webzine.Repository/LocalCommentaireRepository.cs @@ -4,7 +4,6 @@ namespace Webzine.Repository { - using System; using System.Collections.Generic; using System.Linq; @@ -15,7 +14,6 @@ namespace Webzine.Repository /// /// Initialise une classe qui implémente l'interface pour gérer les opérations liées aux commentaires. - /// Utilise en injection de dépendances. /// public class LocalCommentaireRepository : ICommentaireRepository { @@ -23,9 +21,8 @@ namespace Webzine.Repository private readonly InMemoryDataStore dataStore; /// - /// Initializes a new instance of the class. /// Initialise une nouvelle instance du . - /// Est liée à un magasin de données en mémoire et utilise un logger pour enregistrer les opérations. + /// Gère les opérations liées aux commentaires en utilisant une source de données locale (en mémoire). /// /// Le magasin de données en mémoire. Ne peut pas être null. /// Le logger à utiliser pour enregistrer les messages de journalisation. Ne peut pas être null. @@ -38,31 +35,19 @@ namespace Webzine.Repository /// public void Add(Commentaire commentaire) { - throw new NotSupportedException("Mode Local"); + this.dataStore.Commentaires.Add(commentaire); } /// public void Delete(Commentaire commentaire) { - throw new NotSupportedException("Mode Local"); - } - - /// - public int Count() - { - return this.dataStore.Commentaires.Count; + this.dataStore.Commentaires.Remove(commentaire); } /// public Commentaire Find(int idCommentaire) { - var commentaire = this.dataStore.Commentaires.FirstOrDefault(c => c.IdCommentaire == idCommentaire); - if (commentaire == null) - { - return new Commentaire(); - } - - return commentaire; + return this.dataStore.Commentaires.SingleOrDefault(c => c.IdCommentaire == idCommentaire); } /// @@ -76,25 +61,10 @@ namespace Webzine.Repository /// public IEnumerable FindCommentaires(int offset, int limit) { - if (offset < 0 || limit <= 0) - { - return Enumerable.Empty(); - } - return this.dataStore.Commentaires .OrderByDescending(c => c.DateCreation) .Skip(offset) - .Take(limit) - .ToList(); - } - - /// - public IEnumerable FindByIdTitre(int idTitre) - { - return this.dataStore.Commentaires - .Where(c => c.Titre != null && c.Titre.IdTitre == idTitre) - .OrderByDescending(c => c.DateCreation) - .ToList(); + .Take(limit); } } } \ No newline at end of file diff --git a/Webzine.Repository/LocalStyleRepository.cs b/Webzine.Repository/LocalStyleRepository.cs index e750ff2..ed26315 100644 --- a/Webzine.Repository/LocalStyleRepository.cs +++ b/Webzine.Repository/LocalStyleRepository.cs @@ -17,9 +17,10 @@ public class LocalStyleRepository : IStyleRepository /// /// Initializes a new instance of the class. + /// Gère les opérations liées aux styles en utilisant une source de données locale (en mémoire). /// /// Le service de journalisation injecté pour suivre les opérations du repository. - /// La liste de styles à utiliser comme source de données pour le repository. + /// Les données en mémoire. public LocalStyleRepository(ILogger logger, InMemoryDataStore dataStore) { this.logger = logger; @@ -30,19 +31,19 @@ public class LocalStyleRepository : IStyleRepository /// public void Add(Style style) { - throw new NotSupportedException("Mode local"); + this.dataStore.Styles.Add(style); } /// public void Delete(Style style) { - throw new NotSupportedException("Mode local"); + this.dataStore.Styles.Remove(style); } /// public Style Find(int id) { - return this.dataStore.Styles.Find(s => s.IdStyle == id); + return this.dataStore.Styles.SingleOrDefault(s => s.IdStyle == id); } /// @@ -54,7 +55,15 @@ public class LocalStyleRepository : IStyleRepository /// public void Update(Style style) { - throw new NotSupportedException("Mode local"); + Style existingStyle = this.Find(style.IdStyle); + if (existingStyle == null) + { + this.logger.LogWarning("Style with id {IdStyle} not found for update.", style.IdStyle); + return; + } + + existingStyle.Libelle = style.Libelle; + existingStyle.Titres = style.Titres; } /// diff --git a/Webzine.Repository/LocalTitreRepository.cs b/Webzine.Repository/LocalTitreRepository.cs index ff8832c..de237ef 100644 --- a/Webzine.Repository/LocalTitreRepository.cs +++ b/Webzine.Repository/LocalTitreRepository.cs @@ -28,20 +28,20 @@ public class LocalTitreRepository : ITitreRepository /// public void Add(Titre titre) { - throw new NotSupportedException("Mode local"); + this.dataStore.Titres.Add(titre); } /// public int Count() { - var count = this.dataStore.Titres.Count(); - return count; + // On appelle directement LINQ count pour ne pas confondre avec la méthode Count() de l'interface ITitreRepository + return Enumerable.Count(this.dataStore.Titres); } /// public void Delete(Titre titre) { - throw new NotSupportedException("Mode Local"); + this.dataStore.Titres.Remove(titre); } /// @@ -56,27 +56,47 @@ public class LocalTitreRepository : ITitreRepository /// public void IncrementNbLectures(Titre titre) { - titre.NbLectures++; + var stored = this.dataStore.Titres.FirstOrDefault(t => t.IdTitre == titre.IdTitre); + if (stored == null) + { + this.logger.LogWarning("Titre avec l'ID {Id} non trouvé pour incrémenter le nombre de lectures.", titre.IdTitre); + return; + } + + stored.NbLectures++; } /// public void IncrementNbLikes(Titre titre) { - titre.NbLikes++; + var stored = this.dataStore.Titres.FirstOrDefault(t => t.IdTitre == titre.IdTitre); + if (stored == null) + { + this.logger.LogWarning("Titre avec l'ID {Id} non trouvé pour incrémenter le nombre de likes.", titre.IdTitre); + return; + } + + stored.NbLikes++; } /// public IEnumerable Search(string mot) { + if (string.IsNullOrWhiteSpace(mot)) + { + return Enumerable.Empty(); + } + return this.dataStore.Titres - .Where(t => t.Libelle != null && t.Libelle.Contains(mot)); + .Where(t => t.Libelle.ToLower().Contains(mot.ToLower())) + .ToList(); } /// public Titre Find(int idTitre) { return this.dataStore.Titres - .First(t => t.IdTitre == idTitre); + .SingleOrDefault(t => t.IdTitre == idTitre); } /// @@ -95,6 +115,20 @@ public class LocalTitreRepository : ITitreRepository /// public void Update(Titre titre) { - throw new NotSupportedException("Mode local"); + // On trouve le titre stocké pour mettre à jour ses propriétés avec la méthode Find du repository + // pour éviter la duplication de code. + Titre existingTitre = this.Find(titre.IdTitre); + if (existingTitre == null) + { + this.logger.LogWarning("Titre avec l'ID {Id} non trouvé pour mise à jour.", titre.IdTitre); + return; + } + + existingTitre.Libelle = titre.Libelle; + existingTitre.DateCreation = titre.DateCreation; + existingTitre.NbLectures = titre.NbLectures; + existingTitre.NbLikes = titre.NbLikes; + existingTitre.IdArtiste = titre.IdArtiste; + existingTitre.Styles = titre.Styles; } } \ No newline at end of file diff --git a/Webzine.WebApplication/Areas/Administration/Controllers/ArtisteController.cs b/Webzine.WebApplication/Areas/Administration/Controllers/ArtisteController.cs index 89cbbfb..c68e99e 100644 --- a/Webzine.WebApplication/Areas/Administration/Controllers/ArtisteController.cs +++ b/Webzine.WebApplication/Areas/Administration/Controllers/ArtisteController.cs @@ -31,8 +31,7 @@ public class ArtisteController : Controller } /// - /// Affiche la liste des artistes. Pour l'instant, les artistes sont générés à partir de noms prédéfinis via la méthode SeedArtisteByName de la classe ArtisteFactory. - /// Chaque artiste est ensuite ajouté à une liste d'artistes qui est passée à la vue. + /// Affiche la liste des artistes. /// /// Redirection. public IActionResult Index() @@ -148,7 +147,6 @@ public class ArtisteController : Controller this.artisteRepository.Delete(artiste); } - // 3. Redirect back to the list (or wherever you want them to go after) return this.RedirectToAction("Index"); } } \ No newline at end of file diff --git a/Webzine.WebApplication/Areas/Administration/Controllers/CommentaireController.cs b/Webzine.WebApplication/Areas/Administration/Controllers/CommentaireController.cs index cca385a..67460d2 100644 --- a/Webzine.WebApplication/Areas/Administration/Controllers/CommentaireController.cs +++ b/Webzine.WebApplication/Areas/Administration/Controllers/CommentaireController.cs @@ -6,7 +6,7 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers using Webzine.WebApplication.Areas.Administration.ViewModels.Commentaire; /// - /// + /// Contrôleur pour la gestion des commentaires dans l'administration du webzine. Ce contrôleur permet d'afficher la liste des commentaires, de supprimer un commentaire spécifique et de gérer les interactions liées aux commentaires dans l'interface d'administration. /// [Area("Administration")] public class CommentaireController : Controller diff --git a/Webzine.WebApplication/Areas/Administration/Controllers/DashboardController.cs b/Webzine.WebApplication/Areas/Administration/Controllers/DashboardController.cs index 213498f..560533e 100644 --- a/Webzine.WebApplication/Areas/Administration/Controllers/DashboardController.cs +++ b/Webzine.WebApplication/Areas/Administration/Controllers/DashboardController.cs @@ -1,10 +1,13 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers; +using Microsoft.AspNetCore.Mvc; + using Webzine.Business.Contracts; using Webzine.Business.Contracts.Dto; -using Microsoft.AspNetCore.Mvc; -using Webzine.Repository.Contracts; +/// +/// Contrôleur pour gérer le tableau de bord de l'administration. +/// [Area("Administration")] public class DashboardController : Controller { diff --git a/Webzine.WebApplication/Areas/Administration/Controllers/TitreController.cs b/Webzine.WebApplication/Areas/Administration/Controllers/TitreController.cs index d6952f2..e3f7bf2 100644 --- a/Webzine.WebApplication/Areas/Administration/Controllers/TitreController.cs +++ b/Webzine.WebApplication/Areas/Administration/Controllers/TitreController.cs @@ -184,6 +184,12 @@ public class TitreController : Controller public IActionResult Delete(AdminTitreDelete model) { var titre = this.titreRepository.Find(model.Id); + + if (!this.ModelState.IsValid) + { + return this.View(model); + } + if (titre != null) { this.titreRepository.Delete(titre); diff --git a/Webzine.WebApplication/Controllers/ArtisteController.cs b/Webzine.WebApplication/Controllers/ArtisteController.cs index f3cb9ba..9980afa 100644 --- a/Webzine.WebApplication/Controllers/ArtisteController.cs +++ b/Webzine.WebApplication/Controllers/ArtisteController.cs @@ -6,7 +6,7 @@ using Webzine.WebApplication.ViewModels.Artiste; /// - /// + /// Contrôleur pour la gestion des artistes dans l'administration du webzine. Ce contrôleur gère les opérations de création, modification, suppression et affichage des artistes dans l'interface d'administration du webzine. Chaque action du contrôleur prépare un ViewModel spécifique pour la vue correspondante, permettant ainsi une séparation claire entre la logique métier et la présentation des données. /// public class ArtisteController : Controller { @@ -15,11 +15,10 @@ private readonly IArtisteRepository artisteRepository; /// - /// Initializes a new instance of the class. /// Initialise une nouvelle instance du . avec un service de journalisation injecté. /// /// Service de journalisation injecté pour enregistrer les événements et les erreurs. - /// + /// Repository pour accéder aux données des artistes, injecté pour permettre les opérations de création, modification, suppression et affichage des artistes. public ArtisteController( ILogger logger, IArtisteRepository artisteRepository) @@ -30,7 +29,7 @@ } /// - /// Prend en paramètre le nom de l'artiste (ex: "fatal-bazooka"), utilise la factory pour trouver l'artiste correspondant, et affiche sa page dédiée. + /// Affiche la liste des artistes. /// /// Le nom de l'artiste à rechercher, formaté en kebab-case (ex: "fatal-bazooka"). /// La vue de l'artiste avec son ViewModel, ou une redirection vers l'accueil si le nom est vide, ou une erreur 404 si l'artiste n'est pas trouvé. @@ -44,14 +43,12 @@ return this.RedirectToAction("Index", "Accueil"); } - // On transforme "fatal-bazooka" en "Fatal Bazooka" pour la factory + // On transforme "fatal-bazooka" en "Fatal Bazooka" string nomPropre = System.Globalization.CultureInfo.CurrentCulture.TextInfo .ToTitleCase(nom.Replace("-", " ")); - // On appelle la factory pour obtenir l'artiste unique var artiste = this.artisteRepository.FindByName(nomPropre); - // Check if artiste was found if (artiste == null) { this.logger.LogWarning("Artiste non trouvé avec le nom : {NomArtiste}", nomPropre); diff --git a/Webzine.WebApplication/Controllers/RechercheController.cs b/Webzine.WebApplication/Controllers/RechercheController.cs index 27027c0..be5b50f 100644 --- a/Webzine.WebApplication/Controllers/RechercheController.cs +++ b/Webzine.WebApplication/Controllers/RechercheController.cs @@ -10,7 +10,7 @@ namespace Webzine.WebApplication.Controllers using Webzine.WebApplication.ViewModels.Recherche; /// - /// + /// Controller de la page de recherche d'artistes et de titres. /// public class RechercheController : Controller { @@ -19,11 +19,11 @@ namespace Webzine.WebApplication.Controllers private readonly IArtisteRepository artisteRepository; /// - /// Initialise une nouvelle instance de la classe . + /// Constructeur du controller de la page de recherche d'artistes et de titres. /// - /// - /// - /// + /// Le logger pour enregistrer les événements. + /// Le repository pour gérer les opérations sur les titres. + /// Le repository pour gérer les opérations sur les artistes. public RechercheController( ILogger logger, ITitreRepository titreRepository, diff --git a/Webzine.WebApplication/Controllers/TitreController.cs b/Webzine.WebApplication/Controllers/TitreController.cs index 73746b7..d5ee70b 100644 --- a/Webzine.WebApplication/Controllers/TitreController.cs +++ b/Webzine.WebApplication/Controllers/TitreController.cs @@ -39,7 +39,6 @@ namespace Webzine.WebApplication.Controllers /// /// Identifiant du titre. /// Vue des details ou 404 si introuvable. - public IActionResult Index(int id) { this.logger.LogInformation("Demande d'affichage du detail pour le titre ID {Id}.", id); diff --git a/Webzine.WebApplication/Extensions/RouteConfiguration.cs b/Webzine.WebApplication/Extensions/RouteConfiguration.cs index d7f43b8..0b9b428 100644 --- a/Webzine.WebApplication/Extensions/RouteConfiguration.cs +++ b/Webzine.WebApplication/Extensions/RouteConfiguration.cs @@ -23,9 +23,7 @@ public static class RouteConfiguration pattern: "artiste/{nom}", defaults: new { controller = "Artiste", action = "Index" }); - // ----------- ADMIN ----------- - var adminRoutes = new Dictionary { { "artistes", "Artiste" }, { "commentaires", "Commentaire" }, { "styles", "Style" }, { "titres", "Titre" }, @@ -39,9 +37,7 @@ public static class RouteConfiguration defaults: new { area = "Administration", controller = route.Value, action = "Index" }); } - // --- AUTRE PROUTES --- - endpoints.MapControllerRoute( name: "areas", pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}"); diff --git a/Webzine.WebApplication/Middlewares/LogTempsExecutionMiddleware.cs b/Webzine.WebApplication/Middlewares/LogTempsExecutionMiddleware.cs new file mode 100644 index 0000000..1b86308 --- /dev/null +++ b/Webzine.WebApplication/Middlewares/LogTempsExecutionMiddleware.cs @@ -0,0 +1,63 @@ +namespace Webzine.WebApplication.Middlewares +{ + using System.Diagnostics; + + public class LogTempsExecutionMiddleware + { + /// + /// log à chaque requete http. + /// + // _next représente le maillon suivant dans la chaîne (le prochain middleware ou le contrôleur) + private readonly RequestDelegate next; + private readonly ILogger logger; + + // Le constructeur récupère "_next" et le Logger + public LogTempsExecutionMiddleware(RequestDelegate next, ILogger logger) + { + this.next = next; + this.logger = logger; + } + + // méthode appelée à chaque requête HTTP + + /// + /// Middleware chargé de journaliser le cycle de vie d'une requête HTTP (entrée, exécution, sortie et temps de réponse). + /// + /// Le contexte HTTP encapsulant toutes les informations de la requête et de la réponse. + /// Une tâche () représentant l'opération asynchrone. + public async Task InvokeAsync(HttpContext context) + { + // (Avant le contrôleur) + var chronometre = Stopwatch.StartNew(); // lance le chrono + + // --- IN --- + var methode = context.Request.Method; + var endpoint = context.Request.Path; + var traceId = context.TraceIdentifier; // Identifiant unique généré par .NET + + this.logger.LogInformation("[IN] TraceId: {traceId} | Méthode: {methode} | Endpoint: {endpoint}", traceId, methode, endpoint); + + await this.next(context); + + // (Après le contrôleur) + chronometre.Stop(); // arrête le chrono + var tempsEcoule = chronometre.ElapsedMilliseconds; + + var httpCode = context.Response.StatusCode; // exemple: 200, 404, 500 + + // --- OUT --- + if (httpCode >= 500) + { + this.logger.LogError("[OUT] TraceId: {traceId} | HTTP {httpCode} | Temps: {tempsEcoule} ms | Endpoint: {endpoint}", traceId, httpCode, tempsEcoule, endpoint); + } + else if (httpCode >= 400) + { + this.logger.LogWarning("[OUT] TraceId: {traceId} | HTTP {httpCode} | Temps: {tempsEcoule} ms | Endpoint: {endpoint}", traceId, httpCode, tempsEcoule, endpoint); + } + else + { + this.logger.LogInformation("[OUT] TraceId: {traceId} | HTTP {httpCode} | Temps: {tempsEcoule} ms | Endpoint: {endpoint}", traceId, httpCode, tempsEcoule, endpoint); + } + } + } +} \ No newline at end of file diff --git a/Webzine.WebApplication/Program.cs b/Webzine.WebApplication/Program.cs index 72fc4ba..6ad2b22 100644 --- a/Webzine.WebApplication/Program.cs +++ b/Webzine.WebApplication/Program.cs @@ -100,6 +100,8 @@ try var app = builder.Build(); + app.UseMiddleware(); + if (repositoryType == RepositoryType.Db) { using (var scope = app.Services.CreateScope()) diff --git a/Webzine.WebApplication/nlog.config b/Webzine.WebApplication/nlog.config index 2fc8c32..c2ba55e 100644 --- a/Webzine.WebApplication/nlog.config +++ b/Webzine.WebApplication/nlog.config @@ -28,7 +28,7 @@ - + diff --git a/scripts/test-endpoints.sh b/scripts/test-endpoints.sh index 0c96983..4eb567e 100644 --- a/scripts/test-endpoints.sh +++ b/scripts/test-endpoints.sh @@ -221,7 +221,4 @@ cat >> "$RAPPORT_FICHIER" <