Merge branch 'dev' into j2/test/pr

# Conflicts:
#	Webzine.WebApplication/Controllers/ArtisteController.cs
This commit is contained in:
mirage
2026-03-27 11:23:42 +01:00
69 changed files with 2176 additions and 7074 deletions

View File

@@ -18,7 +18,7 @@
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE

View File

@@ -49,7 +49,7 @@ namespace Webzine.EntitiesContext
entity.HasMany(a => a.Titres)
.WithOne(t => t.Artiste)
.HasForeignKey(t => t.IdArtiste)
.OnDelete(DeleteBehavior.Restrict);
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<Style>(entity =>

View File

@@ -0,0 +1,13 @@
// <copyright file="CommentaireFactory.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
using Bogus;
namespace Webzine.Entity.Fixtures
{
public class CommentaireFactory
{
}
}

View File

@@ -1,6 +1,153 @@
namespace Webzine.Entity.Fixtures;
// <copyright file="SeedDataLocal.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
public class SeedDataLocal
namespace Webzine.Entity.Fixtures
{
using Bogus;
public class SeedDataLocal
{
public SeedDataLocal()
{
}
/// <summary>
/// Generer une liste d'artiste.
/// </summary>
/// <param name="nombre">Nombre d'artiste.</param>
/// <returns>Liste d'artiste.</returns>
public static List<Artiste> GenererListeArtiste(int nombre)
{
Faker<Artiste> artistes = new Faker<Artiste>("fr")
.RuleFor(a => a.Nom, f => f.Person.FullName)
.RuleFor(a => a.Biographie, f => f.Lorem.Paragraph(2));
return artistes.Generate(nombre);
}
/// <summary>
/// Generer une liste de titre.
/// </summary>
/// <param name="count">Nombre de titre à créer.</param>
/// <param name="artistes">Liste d'artiste.</param>
/// <param name="styles">Liste de style.</param>
/// <returns>Liste de titre.</returns>
public static List<Titre> GenererListeTitre(
int count,
List<Artiste> artistes,
List<Style> styles,
List<string> albums)
{
Random random = new Random();
Faker<Titre> faker = new Faker<Titre>("fr")
.RuleFor(t => t.Libelle, f => f.Lorem.Sentence(3).Replace(".", string.Empty))
.RuleFor(t => t.Chronique, f => f.Lorem.Paragraphs(3))
.RuleFor(t => t.DateCreation, f => f.Date.Recent(120))
.RuleFor(t => t.DateSortie, (f, t) => f.Date.Past(10, t.DateCreation))
.RuleFor(t => t.Duree, f => f.Random.Int(120, 420))
.RuleFor(t => t.UrlJaquette, f => $"https://picsum.photos/seed/{Guid.NewGuid():N}/640/640")
.RuleFor(t => t.UrlEcoute, f => $"https://example.com/listen/{Guid.NewGuid():N}")
.RuleFor(t => t.NbLectures, f => f.Random.Int(0, 5000))
.RuleFor(t => t.NbLikes, f => f.Random.Int(0, 1000))
.RuleFor(t => t.Album, f => f.PickRandom(albums))
.RuleFor(t => t.Artiste, f => f.PickRandom(artistes));
List<Titre> titres = faker.Generate(count);
foreach (Titre titre in titres)
{
int nbStyles = random.Next(1, 4);
titre.Styles = styles
.OrderBy(_ => Guid.NewGuid())
.Take(nbStyles)
.ToList();
titre.IdArtiste = titre.Artiste.IdArtiste;
}
return titres;
}
/// <summary>
/// Générer une liste de style pour seeder la base
/// de données.
/// </summary>
/// <returns>Liste de style.</returns>
public static List<Style> GenererListeStyle(int minCount = 15, int maxCount = 20)
{
List<string> libelles = new List<string>
{
"Pop",
"Rock",
"Jazz",
"Blues",
"Hip-Hop",
"Rap",
"Electro",
"Techno",
"House",
"Metal",
"Funk",
"Soul",
"R&B",
"Classique",
"Reggae",
"Punk",
"Folk",
"Disco",
"Ambient",
"Indie",
};
Random random = new Random();
int count = random.Next(minCount, maxCount + 1);
return libelles
.Take(count)
.Select(libelle => new Style
{
Libelle = libelle,
})
.ToList();
}
/// <summary>
/// Seeder pour la base de données.
/// </summary>
/// <param name="titre">Titre.</param>
/// <param name="min">Min.</param>
/// <param name="max">Max.</param>
/// <returns>Liste de commentaire.</returns>
public static List<Commentaire> GenererListeCommentaire(Titre titre, int min = 0, int max = 5)
{
Random random = new Random();
int count = random.Next(min, max + 1);
Faker<Commentaire> faker = new Faker<Commentaire>("fr")
.RuleFor(c => c.Auteur, f => f.Internet.UserName())
.RuleFor(c => c.Contenu, f => f.Lorem.Sentences(2))
.RuleFor(c => c.DateCreation, f => f.Date.Recent(60))
.RuleFor(c => c.Titre, _ => titre)
.RuleFor(c => c.IdTitre, _ => titre.IdTitre);
return faker.Generate(count);
}
public static List<string> GenererListeAlbums(int nombre)
{
Faker faker = new Faker("fr");
HashSet<string> albums = new HashSet<string>();
while (albums.Count < nombre)
{
albums.Add(faker.Company.CatchPhrase());
}
return albums.ToList();
}
}
}

View File

@@ -0,0 +1,12 @@
// <copyright file="StyleFactory.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace Webzine.Entity.Fixtures
{
using Webzine.Entity;
public class StyleFactory
{
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Bogus;
using Faker;
using System;
using System.Collections.Generic;
using Webzine.Entity;
namespace Webzine.Repository.Fake

View File

@@ -1,17 +1,47 @@
// using Webzine.Entity;
namespace Webzine.Repository.Contracts
{
using Webzine.Entity;
/// <summary>
/// Défini une interface <see cref="IArtisteRepository"/> pour gérer les opérations de base de données liées aux artistes.
/// </summary>
public interface IArtisteRepository
{
// void Add(Artiste artiste);
/// <summary>
/// Ajoute un nouvel artiste.
/// </summary>
/// <param name="artiste">L'artiste à ajouter à la collection. Ne peut pas être null.</param>
void Add(Artiste artiste);
// void Delete(Artiste artiste);
/// <summary>
/// Supprime un artiste.
/// </summary>
/// <param name="artiste">L'artiste à supprimer.</param>
void Delete(Artiste artiste);
// Artiste Find(int id);
/// <summary>
/// Récupère un artiste par son identifiant unique. Si aucun artiste n'est trouvé, retourne null.
/// </summary>
/// <param name="id">L'identifiant de l'artiste.</param>
/// <returns></returns>
Artiste Find(int id);
// IEnumerable<Artiste> FindAll();
/// <summary>
/// Récupère un artiste par son nom. Si aucun artiste n'est trouvé, retourne null.
/// </summary>
/// <param name="name">Le nom de l'artiste.</param>
/// <returns>L'artiste recherché ou null.</returns>
Artiste FindByName(string name);
// void Update(Artiste artiste);
/// <summary>
/// Récupère tous les artistes disponibles dans la collection. Si aucun artiste n'est trouvé, retourne une collection vide.
/// </summary>
/// <returns>Retourne une collection d'artistes.</returns>
IEnumerable<Artiste> FindAll();
/// <summary>
/// Met à jour les informations d'un artiste existant dans la collection.
/// </summary>
/// <param name="artiste">L'artiste à mettre à jour.</param>
void Update(Artiste artiste);
}
}

View File

@@ -1,15 +1,15 @@
// using Webzine.Entity;
using Webzine.Entity;
namespace Webzine.Repository.Contracts
{
public interface ICommentaireRepository
{
// void Add(Commentaire commentaire);
void Add(Commentaire commentaire);
// void Delete(Commentaire commentaire);
void Delete(Commentaire commentaire);
// Commentaire Find(int id);
Commentaire Find(int id);
// IEnumerable<Commentaire> FindAll();
IEnumerable<Commentaire> FindAll();
}
}

View File

@@ -1,17 +1,41 @@
// using Webzine.Entity;
using Webzine.Entity;
namespace Webzine.Repository.Contracts
{
/// <summary>
/// Interface définissant les opérations de base pour le repository de styles, permettant d'ajouter, supprimer, trouver et mettre à jour des styles dans la source de données.
/// </summary>
public interface IStyleRepository
{
// void Add(Style style);
/// <summary>
/// Ajoute un style à la liste des styles.
/// </summary>
/// <param name="style">L'objet style à ajouter.</param>
void Add(Style style);
// void Delete(Style style);
/// <summary>
/// Supprime un style de la liste des styles.
/// </summary>
/// <param name="style">L'objet style à supprimer.</param>
void Delete(Style style);
// Style Find(int id);
/// <summary>
/// Trouve un style dans la liste des styles en fonction de son identifiant.
/// </summary>
/// <param name="id">L'identifiant du style à trouver.</param>
/// <returns>Le style correspondant à l'identifiant fourni, ou null si aucun style n'est trouvé.</returns>
Style Find(int id);
// IEnumerable<Style> FindAll();
/// <summary>
/// Trouve tous les styles dans la liste des styles.
/// </summary>
/// <returns>Une collection de tous les styles présents dans la liste.</returns>
IEnumerable<Style> FindAll();
// void Update(Style style);
/// <summary>
/// Met à jour un style dans la liste des styles en fonction de son identifiant.
/// </summary>
/// <param name="style">L'objet style à mettre à jour.</param
void Update(Style style);
}
}

View File

@@ -0,0 +1,81 @@
using Webzine.Entity;
namespace Webzine.Repository.Contracts
{
/// <summary>
/// Interface qui définit les opérations de base pour la gestion des titres dans une source de données.
/// </summary>
public interface ITitreRepository
{
/// <summary>
/// Ajoute un titre à la liste des titres.
/// </summary>
/// <param name="titre">L'objet titre à ajouter.</param>
void Add(Titre titre);
/// <summary>
/// Remonte le nombre de titres.
/// </summary>
/// <returns>Le nombre total de titres présents dans la liste après l'incrémentation du nombre de lectures.</returns>
int Count();
/// <summary>
/// Supprime un titre de la liste des titres.
/// </summary>
/// <param name="titre">L'objet titre à supprimer.</param>
void Delete(Titre titre);
/// <summary>
/// Trouve un titre dans la liste des titres en fonction de son identifiant.
/// </summary>
/// <param name="idTitre">L'identifiant du titre à trouver.</param>
/// <returns>Le titre correspondant à l'identifiant fourni, ou null si aucun titre n'est trouvé.</returns>
Titre Find(int idTitre);
/// <summary>
/// Recherche les titres dans la liste des titres en fonction de l'offset et de la limite spécifiés, permettant ainsi une pagination des résultats.
/// </summary>
/// <param name="offset">Le nombre de titres à ignorer avant de commencer à retourner les résultats.</param>
/// <param name="limit">Le nombre maximum de titres à retourner.</param>
/// <returns>Une collection de titres correspondant au critère de pagination, triée par libellé.</returns>
IEnumerable<Titre> FindTitres(int offset, int limit);
/// <summary>
/// Trouve tous les titres dans la liste des titres.
/// </summary>
/// <returns>Une collection de tous les titres présents dans la liste.</returns>
IEnumerable<Titre> FindAll();
/// <summary>
/// Incrémente le nombre de lectures d'un titre donné. Si le titre est null, un message d'avertissement est enregistré dans les logs et aucune action n'est effectuée.
/// </summary>
/// <param name="titre">L'objet titre dont le nombre de lectures doit être incrémenté.</param>
void IncrementNbLectures(Titre titre);
/// <summary>
/// Incrémente le nombre de likes d'un titre donné. Si le titre est null, un message d'avertissement est enregistré dans les logs et aucune action n'est effectuée.
/// </summary>
/// <param name="titre">L'objet titre dont le nombre de likes doit être incrémenté.</param>
void IncrementNbLikes(Titre titre);
/// <summary>
/// Recherche les titres dont le libellé contient le mot spécifié, en ignorant la casse.
/// </summary>
/// <param name="mot">Le mot à rechercher dans les libellés des titres.</param>
/// <returns>Une collection de titres correspondant au critère de recherche, triée par libellé.</returns>
IEnumerable<Titre> Search(string mot);
/// <summary>
/// Recherche les titres associés à un style dont le libellé contient la chaîne spécifiée, en ignorant la casse.
/// </summary>
/// <param name="libelle">Le libellé du style à rechercher dans les titres.</param>
/// <returns>Une collection de titres correspondant au critère de recherche, triée par libellé.</returns>
IEnumerable<Titre> SearchByStyle(string libelle);
/// <summary>
/// Met à jour un titre dans la liste des titres en fonction de son identifiant. Si aucun titre correspondant à l'identifiant du titre fourni n'est trouvé, un message d'avertissement est enregistré dans les logs et aucune mise à jour n'est effectuée.
/// </summary>
/// <param name="titre">L'objet titre à mettre à jour.</param>
void Update(Titre titre);
}
}

View File

@@ -1,29 +0,0 @@
using Webzine.Entity;
namespace Webzine.Repository.Contracts
{
public interface ITitreRepository
{
// void Add(Titre titre);
// int Count();
// void Delete(Titre titre);
Titre? Find(int idTitre);
// IEnumerable<Titre> FindTitres(int offset, int limit);
IEnumerable<Titre> FindAll();
// void IncrementNbLectures(Titre titre);
// void IncrementNbLikes(Titre titre);
IEnumerable<Titre> Search(string mot);
IEnumerable<Titre> SearchByStyle(string libelle);
// void Update(Titre titre);
}
}

View File

@@ -0,0 +1,164 @@
// <copyright file="DbArtisteRepository.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace Webzine.Repository
{
using System.Data.Common;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Webzine.EntitiesContext;
using Webzine.Entity;
using Webzine.Repository.Contracts;
/// <summary>
/// Initialise une classe <see cref="DbArtisteRepository"/> qui implémente l'interface <see cref="IArtisteRepository"/> pour gérer les opérations de base de données liées aux artistes.
/// Utilise <see cref="IArtisteRepository"/> en injection de dépendances.
/// </summary>
public class DbArtisteRepository : IArtisteRepository
{
private WebzineDbContext context;
private readonly ILogger<LocalArtisteRepository> logger;
/// <summary>
/// Initializes a new instance of the <see cref="DbArtisteRepository"/> class.
/// </summary>
/// <param name="context">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.</param>
public DbArtisteRepository(WebzineDbContext context, ILogger<LocalArtisteRepository> logger)
{
this.logger = logger;
this.context = context;
}
/// <inheritdoc/>
public void Add(Artiste artiste)
{
try
{
this.context.Artistes.Add(artiste);
this.context.SaveChanges();
}
catch (DbUpdateException dbex)
{
this.logger.LogError(dbex, "Erreur de base de données lors de l'ajout de l'artiste: {id}", artiste.IdArtiste);
throw;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Une erreur est survenue lors de l'ajout de l'artiste {Nom}.", artiste?.Nom);
throw new Exception("Une erreur est survenue lors de l'ajout de l'artiste.", ex);
}
}
/// <inheritdoc/>
public void Delete(Artiste artiste)
{
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);
}
catch (DbUpdateException dbex)
{
this.logger.LogError(dbex, "Erreur de base de données lors de la suppression de l'artiste: {Id}", artiste.IdArtiste);
throw;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Une erreur est survenue lors de la suppression de l'artiste {Nom}.", artiste?.Nom);
throw new Exception("Une erreur est survenue lors de la suppression de l'artiste.", ex);
}
}
/// <inheritdoc/>
public Artiste Find(int id)
{
try
{
Artiste artiste = this.context.Artistes
.Include(a => a.Titres)
.First(a => a.IdArtiste == id);
return artiste;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de la recherche de l'artiste: {Id}", id);
throw;
}
}
/// <inheritdoc/>
public Artiste FindByName(string nom)
{
try
{
var artiste = this.context.Artistes
.Include(a => a.Titres)
.FirstOrDefault(a => a.Nom == nom);
if (artiste == null)
{
this.logger.LogWarning("Pas d'artiste au nom {Nom}", nom);
artiste = new Artiste();
}
return artiste;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de la recherche de l'artiste avec le nom: {Nom}", nom);
throw;
}
}
/// <inheritdoc/>
public IEnumerable<Artiste> FindAll()
{
try
{
// .AsNoTracking() rend la requête beaucoup plus rapide pour de la lecture
var artistes = this.context.Artistes.AsNoTracking().ToList();
this.logger.LogDebug("{Count} artistes récupérés de la base.", artistes.Count);
return artistes;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de la récupération de tous les artistes.");
return Enumerable.Empty<Artiste>(); // Retourne une liste vide au lieu de faire crash l'UI
}
}
/// <inheritdoc/>
public void Update(Artiste artiste)
{
if (artiste == null)
{
throw new ArgumentNullException(nameof(artiste));
}
try
{
this.context.Artistes.Update(artiste);
this.context.SaveChanges();
this.logger.LogDebug("Artiste {Id} ({Nom}) mis à jour avec succès.", artiste.IdArtiste, artiste.Nom);
}
catch (DbUpdateException ex)
{
this.logger.LogError(ex, "Erreur de base de données lors de la mise à jour de l'artiste ID: {IdArtiste}", artiste.IdArtiste);
throw;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de la mise à jour de l'artiste {Id}.", artiste.IdArtiste);
throw;
}
}
}
}

View File

@@ -1,6 +1,73 @@
namespace Webzine.Repository;
// <copyright file="DbEntityRepository.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
public class DbEntityRepository
namespace Webzine.Repository
{
using Webzine.EntitiesContext;
using Webzine.Entity;
using Webzine.Entity.Fixtures;
public class DbEntityRepository
{
private readonly WebzineDbContext context;
public DbEntityRepository(WebzineDbContext context)
{
this.context = context;
}
/// <summary>
/// Seed la base de donnée à l'aide de SeedDataLocal.
/// </summary>
/// <param name="nbArtistes">Nombre d'artiste.</param>
/// <param name="nbTitres">Nombre de titre.</param>
/// <param name="minStyles">Nombre min de style.</param>
/// <param name="maxStyles">Nombre mac de style.</param>
/// <param name="minCommentairesParTitre">Min commentaire par titre.</param>
/// <param name="maxCommentairesParTitre">Max commentaire par titre</param>
public void SeedBaseDeDonnees(
int nbArtistes = 100,
int nbTitres = 500,
int minStyles = 15,
int maxStyles = 20,
int minCommentairesParTitre = 0,
int maxCommentairesParTitre = 5)
{
if (this.context.Artistes.Any() ||
this.context.Titres.Any() ||
this.context.Styles.Any() ||
this.context.Commentaires.Any())
{
return;
}
List<Artiste> artistes = SeedDataLocal.GenererListeArtiste(nbArtistes);
List<Style> styles = SeedDataLocal.GenererListeStyle(minStyles, maxStyles);
this.context.Artistes.AddRange(artistes);
this.context.Styles.AddRange(styles);
this.context.SaveChanges();
List<string> albums = SeedDataLocal.GenererListeAlbums(3);
List<Titre> titres = SeedDataLocal.GenererListeTitre(nbTitres, artistes, styles, albums);
this.context.Titres.AddRange(titres);
this.context.SaveChanges();
List<Commentaire> commentaires = new List<Commentaire>();
foreach (Titre titre in titres)
{
commentaires.AddRange(
SeedDataLocal.GenererListeCommentaire(
titre,
minCommentairesParTitre,
maxCommentairesParTitre));
}
this.context.Commentaires.AddRange(commentaires);
this.context.SaveChanges();
}
}
}

View File

@@ -0,0 +1,180 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Webzine.EntitiesContext;
using Webzine.Entity;
using Webzine.Repository.Contracts;
namespace Webzine.Repository;
/// <summary>
/// Classe qui implémente le repository pour les styles en utilisant une base de données.
/// </summary>
public class DbStyleRepository : IStyleRepository
{
private readonly ILogger<DbStyleRepository> logger;
private readonly WebzineDbContext context;
/// <summary>
/// Initializes a new instance of the <see cref="DbStyleRepository"/> class.
/// </summary>
/// <param name="logger">Le service de journalisation injecté pour suivre les opérations du repository.</param>
/// <param name="context">Le contexte de base de données injecté.</param>
public DbStyleRepository(ILogger<DbStyleRepository> logger, WebzineDbContext context)
{
this.logger = logger;
this.context = context;
this.logger.LogDebug(1, "NLog injecté dans DbStyleRepository");
}
/// <inheritdoc/>
public void Add(Style style)
{
try
{
this.logger.LogInformation("Ajout d'un nouveau style: {Libelle}", style.Libelle);
this.logger.LogDebug("Début de l'ajout du style en base de données");
this.context.Styles.Add(style);
this.context.SaveChanges();
this.logger.LogDebug("Style ajouté avec succès avec l'ID: {IdStyle}", style.IdStyle);
}
catch (DbUpdateException ex)
{
this.logger.LogError(ex, "Erreur de base de données lors de l'ajout du style: {Libelle}", style.Libelle);
throw;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de l'ajout du style: {Libelle}", style.Libelle);
throw;
}
}
/// <inheritdoc/>
public void Delete(Style style)
{
try
{
this.logger.LogInformation("Suppression du style avec l'ID: {IdStyle}", style.IdStyle);
this.logger.LogDebug("Vérification de l'existence du style en base de données");
// Check if style exists
var existingStyle = this.context.Styles.Find(style.IdStyle);
if (existingStyle == null)
{
this.logger.LogWarning("Style avec l'ID {IdStyle} non trouvé pour la suppression", style.IdStyle);
throw new InvalidOperationException($"Style avec l'ID {style.IdStyle} non trouvé.");
}
this.logger.LogDebug("Style trouvé, suppression en cours");
this.context.Styles.Remove(existingStyle);
this.context.SaveChanges();
this.logger.LogDebug("Style supprimé avec succès: {IdStyle}", style.IdStyle);
}
catch (DbUpdateException ex)
{
this.logger.LogError(ex, "Erreur de base de données lors de la suppression du style ID: {IdStyle}", style.IdStyle);
throw;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de la suppression du style ID: {IdStyle}", style.IdStyle);
throw;
}
}
/// <inheritdoc/>
public Style Find(int id)
{
try
{
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
.Include(s => s.Titres)
.FirstOrDefault(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 style;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de la recherche du style avec l'ID: {Id}", id);
throw;
}
}
/// <inheritdoc/>
public IEnumerable<Style> FindAll()
{
try
{
this.logger.LogDebug("Récupération de tous les styles");
this.logger.LogDebug("Tri des styles par libellé");
var styles = this.context.Styles
.OrderBy(s => s.Libelle)
.ToList();
this.logger.LogDebug("{Count} styles récupérés", styles.Count);
return styles;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de la récupération de tous les styles");
throw;
}
}
/// <inheritdoc/>
public void Update(Style style)
{
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");
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é.");
}
// Update properties
this.logger.LogDebug("Style trouvé, mise à jour des propriétés");
existingStyle.Libelle = style.Libelle;
this.context.SaveChanges();
this.logger.LogDebug("Style mis à jour avec succès: {IdStyle}", style.IdStyle);
}
catch (DbUpdateException ex)
{
this.logger.LogError(ex, "Erreur de base de données lors de la mise à jour du style ID: {IdStyle}", style.IdStyle);
throw;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de la mise à jour du style ID: {IdStyle}", style.IdStyle);
throw;
}
}
}

View File

@@ -0,0 +1,335 @@
using Microsoft.EntityFrameworkCore;
using Webzine.EntitiesContext;
using Microsoft.Extensions.Logging;
using Webzine.Entity;
using Webzine.Repository.Contracts;
namespace Webzine.Repository;
/// <summary>
/// Classe qui implémente le repository pour les titres en utilisant une base de données.
/// </summary>
public class DbTitreRepository : ITitreRepository
{
private readonly ILogger<DbTitreRepository> logger;
private readonly WebzineDbContext context;
/// <summary>
/// Initializes a new instance of the <see cref="DbTitreRepository"/> class.
/// </summary>
/// <param name="logger">Le service de journalisation injecté pour suivre les opérations du repository.</param>
/// <param name="context">Le contexte de base de données injecté.</param>
public DbTitreRepository(ILogger<DbTitreRepository> logger, WebzineDbContext context)
{
this.logger = logger;
this.context = context;
this.logger.LogDebug(1, "NLog injecté dans DbTitreRepository");
}
/// <inheritdoc/>
public void Add(Titre titre)
{
try
{
this.logger.LogInformation("Ajout d'un nouveau titre: {Libelle}", titre.Libelle);
this.logger.LogDebug("Début de l'ajout du titre en base de données");
this.context.Titres.Add(titre);
this.context.SaveChanges();
this.logger.LogDebug("Titre ajouté avec succès avec l'ID: {IdTitre}", titre.IdTitre);
}
catch (DbUpdateException ex)
{
this.logger.LogError(ex, "Erreur de base de données lors de l'ajout du titre: {Libelle}", titre.Libelle);
throw;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de l'ajout du titre: {Libelle}", titre.Libelle);
throw;
}
}
/// <inheritdoc/>
public int Count()
{
try
{
this.logger.LogDebug("Comptage des titres en base de données");
var count = this.context.Titres.Count();
this.logger.LogDebug("Nombre total de titres: {Count}", count);
return count;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors du comptage des titres");
throw;
}
}
/// <inheritdoc/>
public void Delete(Titre titre)
{
try
{
this.logger.LogInformation("Suppression du titre avec l'ID: {IdTitre}", titre.IdTitre);
this.logger.LogDebug("Début de la suppression du titre en base de données");
this.context.Titres.Remove(titre);
this.context.SaveChanges();
this.logger.LogDebug("Titre supprimé avec succès: {IdTitre}", titre.IdTitre);
}
catch (DbUpdateException ex)
{
this.logger.LogError(ex, "Erreur de base de données lors de la suppression du titre ID: {IdTitre}", titre.IdTitre);
throw;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de la suppression du titre ID: {IdTitre}", titre.IdTitre);
throw;
}
}
/// <inheritdoc/>
public IEnumerable<Titre> FindTitres(int offset, int limit)
{
try
{
this.logger.LogDebug("Recherche des titres avec offset: {Offset}, limit: {Limit}", offset, limit);
this.logger.LogDebug("Préparation de la requête avec les inclusions Artiste et Styles");
var titres = this.context.Titres
.Include(t => t.Artiste)
.Include(t => t.Styles)
.OrderBy(t => t.Libelle)
.Skip(offset)
.Take(limit)
.ToList();
this.logger.LogDebug("{Count} titres trouvés", titres.Count);
return titres;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de la recherche des titres avec offset: {Offset}, limit: {Limit}", offset, limit);
throw;
}
}
/// <inheritdoc/>
public void IncrementNbLectures(Titre titre)
{
try
{
this.logger.LogInformation("Incrémentation du nombre de lectures pour le titre ID: {IdTitre}", titre.IdTitre);
this.logger.LogDebug("Recherche du titre en base de données");
var existingTitre = this.context.Titres.Find(titre.IdTitre);
if (existingTitre != null)
{
this.logger.LogDebug("Titre trouvé, incrémentation du compteur de lectures");
existingTitre.NbLectures++;
this.context.SaveChanges();
this.logger.LogDebug("Nouveau nombre de lectures: {NbLectures}", existingTitre.NbLectures);
}
else
{
this.logger.LogWarning("Titre avec l'ID {IdTitre} non trouvé pour l'incrémentation des lectures", titre.IdTitre);
}
}
catch (DbUpdateException ex)
{
this.logger.LogError(ex, "Erreur de base de données lors de l'incrémentation des lectures pour le titre ID: {IdTitre}", titre.IdTitre);
throw;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de l'incrémentation des lectures pour le titre ID: {IdTitre}", titre.IdTitre);
throw;
}
}
/// <inheritdoc/>
public void IncrementNbLikes(Titre titre)
{
try
{
this.logger.LogInformation("Incrémentation du nombre de likes pour le titre ID: {IdTitre}", titre.IdTitre);
this.logger.LogDebug("Recherche du titre en base de données");
var existingTitre = this.context.Titres.Find(titre.IdTitre);
if (existingTitre != null)
{
this.logger.LogDebug("Titre trouvé, incrémentation du compteur de likes");
existingTitre.NbLikes++;
this.context.SaveChanges();
this.logger.LogDebug("Nouveau nombre de likes: {NbLikes}", existingTitre.NbLikes);
}
else
{
this.logger.LogWarning("Titre avec l'ID {IdTitre} non trouvé pour l'incrémentation des likes", titre.IdTitre);
}
}
catch (DbUpdateException ex)
{
this.logger.LogError(ex, "Erreur de base de données lors de l'incrémentation des likes pour le titre ID: {IdTitre}", titre.IdTitre);
throw;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de l'incrémentation des likes pour le titre ID: {IdTitre}", titre.IdTitre);
throw;
}
}
/// <inheritdoc/>
public void Update(Titre titre)
{
try
{
this.logger.LogInformation("Mise à jour du titre avec l'ID: {IdTitre}", titre.IdTitre);
this.logger.LogDebug("Début de la mise à jour du titre en base de données");
var existingTitre = this.context.Titres.Find(titre.IdTitre);
if (existingTitre != null)
{
this.logger.LogDebug("Titre trouvé, mise à jour des propriétés");
this.context.Entry(existingTitre).CurrentValues.SetValues(titre);
// Handle many-to-many relationships
this.logger.LogDebug("Mise à jour des relations many-to-many (Styles)");
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)
{
this.logger.LogError(ex, "Erreur de base de données lors de la mise à jour du titre ID: {IdTitre}", titre.IdTitre);
throw;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de la mise à jour du titre ID: {IdTitre}", titre.IdTitre);
throw;
}
}
/// <inheritdoc/>
public IEnumerable<Titre> Search(string mot)
{
try
{
this.logger.LogInformation("Recherche des titres avec le mot-clé: {Mot}", mot);
this.logger.LogDebug("Préparation de la requête de recherche avec les inclusions");
var titres = this.context.Titres
.Include(t => t.Artiste)
.Include(t => t.Styles)
.Where(t => t.Libelle.ToLower().Contains(mot.ToLower()))
.OrderBy(t => t.Libelle)
.ToList();
this.logger.LogDebug("{Count} titres trouvés correspondant à '{Mot}'", titres.Count, mot);
return titres;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de la recherche des titres avec le mot-clé: {Mot}", mot);
throw;
}
}
/// <inheritdoc/>
public Titre Find(int idTitre)
{
try
{
this.logger.LogDebug("Recherche du titre avec l'ID: {IdTitre}", idTitre);
this.logger.LogDebug("Préparation de la requête avec les inclusions Artiste, Styles et Commentaires");
var titre = this.context.Titres
.Include(t => t.Artiste)
.Include(t => t.Styles)
.Include(t => t.Commentaires)
.First(t => t.IdTitre == idTitre);
this.logger.LogDebug("Titre trouvé: {Libelle}", titre.Libelle);
return titre;
}
catch (InvalidOperationException ex)
{
this.logger.LogWarning(ex, "Aucun titre trouvé avec l'ID: {IdTitre}", idTitre);
throw;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de la recherche du titre avec l'ID: {IdTitre}", idTitre);
throw;
}
}
/// <inheritdoc/>
public IEnumerable<Titre> FindAll()
{
try
{
this.logger.LogDebug("Récupération de tous les titres");
this.logger.LogDebug("Préparation de la requête avec les inclusions Artiste et Styles");
var titres = this.context.Titres
.Include(t => t.Artiste)
.Include(t => t.Styles)
.Include(t => t.Commentaires)
.OrderBy(t => t.Libelle)
.ToList();
this.logger.LogDebug("{Count} titres récupérés", titres.Count);
return titres;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de la récupération de tous les titres");
throw;
}
}
/// <inheritdoc/>
public IEnumerable<Titre> SearchByStyle(string libelle)
{
try
{
this.logger.LogInformation("Recherche des titres par style: {Libelle}", libelle);
this.logger.LogDebug("Préparation de la requête de recherche par style");
var titres = this.context.Titres
.Include(t => t.Artiste)
.Include(t => t.Styles)
.Where(t => t.Styles.Any(s => s.Libelle.ToLower() == libelle.ToLower()))
.OrderBy(t => t.Libelle)
.ToList();
this.logger.LogDebug("{Count} titres trouvés pour le style '{Libelle}'", titres.Count, libelle);
return titres;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Erreur lors de la recherche des titres par style: {Libelle}", libelle);
throw;
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
using Webzine.Entity;
namespace Webzine.Repository
{
public class InMemoryDataStore
{
public List<Artiste> Artistes { get; set; } = new();
public List<Titre> Titres { get; set; } = new();
public List<Style> Styles { get; set; } = new();
public List<Commentaire> Commentaires { get; set; } = new();
}
}

View File

@@ -0,0 +1,78 @@
// <copyright file="LocalArtisteRepository.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace Webzine.Repository
{
using Microsoft.Extensions.Logging;
using Webzine.Entity;
using Webzine.Repository.Contracts;
/// <summary>
/// Initialise une classe <see cref="LocalArtisteRepository"/> qui implémente l'interface <see cref="IArtisteRepository"/> pour gérer les opérations de base de données liées aux artistes.
/// Utilise <see cref="IArtisteRepository"/> en injection de dépendances.
/// </summary>
public class LocalArtisteRepository : IArtisteRepository
{
private readonly ILogger<LocalArtisteRepository> logger;
//private readonly List<Artiste> artistes;
private readonly InMemoryDataStore dataStore;
/// <summary>
/// Initializes a new instance of the <see cref="LocalArtisteRepository"/> class.
/// Est liéee à une liste d'artistes en local et utilise un logger pour enregistrer les opérations effectuées sur les artistes.
/// </summary>
/// <param name="artistes">La liste des artistes à initialiser. Ne peut pas être null.</param>
/// <param name="logger">Le logger à utiliser pour enregistrer les messages de journalisation. Ne peut pas être null.</param>
public LocalArtisteRepository(InMemoryDataStore dataStore, ILogger<LocalArtisteRepository> logger)
{
this.logger = logger;
//this.artistes = artistes;
this.dataStore = dataStore;
}
/// <inheritdoc/>
public void Add(Artiste artiste)
{
throw new NotSupportedException("Mode Local");
}
/// <inheritdoc/>
public void Delete(Artiste artiste)
{
throw new NotSupportedException("Mode Local");
}
/// <inheritdoc/>
public Artiste Find(int id)
{
var artiste = this.dataStore.Artistes.First(a => a.IdArtiste == id);
if (artiste == null)
{
return new Artiste();
}
return artiste;
}
/// <inheritdoc/>
public Artiste FindByName(string nom)
{
return this.dataStore.Artistes.First(a => a.Nom == nom);
}
/// <inheritdoc/>
/// La liste retournée est une copie de la liste interne, donc elle ne peut être nulle.
public IEnumerable<Artiste> FindAll()
{
return this.dataStore.Artistes;
}
/// <inheritdoc/>
public void Update(Artiste artiste)
{
throw new NotSupportedException("Mode Local");
}
}
}

View File

@@ -0,0 +1,43 @@
// <copyright file="LocalCommentaireRepository.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
using Webzine.Entity;
using Webzine.Repository.Contracts;
namespace Webzine.Repository
{
public class LocalCommentaireRepository : ICommentaireRepository
{
private readonly InMemoryDataStore dataStore;
public LocalCommentaireRepository(InMemoryDataStore dataStore)
{
this.dataStore = dataStore;
}
/// <inheritdoc/>
public void Add(Commentaire commentaire)
{
throw new NotSupportedException();
}
/// <inheritdoc/>
public void Delete(Commentaire commentaire)
{
throw new NotSupportedException();
}
/// <inheritdoc/>
public Commentaire Find(int id)
{
return this.dataStore.Commentaires.Find(c => c.IdCommentaire == id);
}
/// <inheritdoc/>
public IEnumerable<Commentaire> FindAll()
{
return this.dataStore.Commentaires.ToList();
}
}
}

View File

@@ -1,71 +0,0 @@
using Microsoft.Extensions.Logging;
using Webzine.Entity;
using Webzine.Entity.Fixtures;
using Webzine.Repository.Contracts;
namespace Webzine.Repository;
/// <summary>
/// Classe qui permet d'initialiser un jeu de données
/// pour tester l'application
/// </summary>
public class LocalEntityRepository : ITitreRepository
{
private readonly ILogger<LocalEntityRepository> _logger;
private readonly List<Titre> _titres;
/// <summary>
/// Initialise une nouvelle instance du <see cref="LocalEntityRepository"/> avec un service de journalisation injecte.
/// </summary>
/// <param name="logger">Service de journalisation injecte pour suivre les operations du repository.</param>
public LocalEntityRepository(ILogger<LocalEntityRepository> logger)
{
this._logger = logger;
this._logger.LogDebug(1, "NLog injected into LocalEntityRepository");
var factory = new DataFactory();
var artistes = factory.GenerateArtists(10);
var styles = factory.GenerateStyles(10);
_titres = factory.GenerateTitres(30, artistes, styles);
factory.GenerateCommentaires(50, _titres);
}
public IEnumerable<Titre> Search(string mot)
{
if (string.IsNullOrWhiteSpace(mot))
{
return Enumerable.Empty<Titre>();
}
return _titres
.Where(t => !string.IsNullOrWhiteSpace(t.Libelle)
&& t.Libelle.Contains(mot, StringComparison.OrdinalIgnoreCase))
.OrderBy(t => t.Libelle)
.ToList();
}
public Titre? Find(int idTitre)
{
return _titres.FirstOrDefault(t => t.IdTitre == idTitre);
}
public IEnumerable<Titre> FindAll()
{
return _titres;
}
public IEnumerable<Titre> SearchByStyle(string libelle)
{
if (string.IsNullOrWhiteSpace(libelle))
{
return Enumerable.Empty<Titre>();
}
return _titres
.Where(t => t.Styles.Any(s => !string.IsNullOrWhiteSpace(s.Libelle)
&& s.Libelle.Contains(libelle, StringComparison.OrdinalIgnoreCase)))
.OrderBy(t => t.Libelle)
.ToList();
}
}

View File

@@ -0,0 +1,57 @@
using Microsoft.Extensions.Logging;
using Webzine.Entity;
using Webzine.Repository.Contracts;
namespace Webzine.Repository;
/// <summary>
/// Classe qui implémente le repository pour les styles en utilisant une liste locale comme source de données.
/// </summary>
public class LocalStyleRepository : IStyleRepository
{
private readonly ILogger<LocalStyleRepository> logger;
//private readonly List<Style> styles;
private readonly InMemoryDataStore dataStore;
/// <summary>
/// Initializes a new instance of the <see cref="LocalStyleRepository"/> class.
/// </summary>
/// <param name="logger">Le service de journalisation injecté pour suivre les opérations du repository.</param>
/// <param name="styles">La liste de styles à utiliser comme source de données pour le repository.</param>
public LocalStyleRepository(ILogger<LocalStyleRepository> logger, InMemoryDataStore dataStore)
{
this.logger = logger;
this.dataStore = dataStore;
this.logger.LogDebug(1, "NLog injecté dans LocalStyleRepository");
}
/// <inheritdoc/>
public void Add(Style style)
{
throw new NotSupportedException("Mode local");
}
/// <inheritdoc/>
public void Delete(Style style)
{
throw new NotSupportedException("Mode local");
}
/// <inheritdoc/>
public Style Find(int id)
{
return this.dataStore.Styles.Find(s => s.IdStyle == id);
}
/// <inheritdoc/>
public IEnumerable<Style> FindAll()
{
return this.dataStore.Styles.ToList();
}
/// <inheritdoc/>
public void Update(Style style)
{
throw new NotSupportedException("Mode local");
}
}

View File

@@ -0,0 +1,99 @@
using Microsoft.Extensions.Logging;
using Webzine.Entity;
using Webzine.Repository.Contracts;
namespace Webzine.Repository;
/// <summary>
/// Classe qui implémente le repository pour les titres en utilisant une liste locale comme source de données.
/// </summary>
public class LocalTitreRepository : ITitreRepository
{
private readonly ILogger<LocalTitreRepository> logger;
private readonly InMemoryDataStore dataStore;
/// <summary>
/// Initializes a new instance of the <see cref="LocalTitreRepository"/> class.
/// </summary>
/// <param name="logger">Le service de journalisation injecté pour suivre les opérations du repository.</param>
/// <param name="dataStore">La liste de titres à utiliser comme source de données pour le repository.</param>
public LocalTitreRepository(ILogger<LocalTitreRepository> logger, InMemoryDataStore dataStore)
{
this.logger = logger;
this.dataStore = dataStore;
this.logger.LogDebug(1, "NLog injecté dans LocalTitreRepository");
}
/// <inheritdoc/>
public void Add(Titre titre)
{
throw new NotSupportedException("Mode local");
}
/// <inheritdoc/>
public int Count()
{
var count = this.dataStore.Titres.Count();
return count;
}
/// <inheritdoc/>
public void Delete(Titre titre)
{
throw new NotSupportedException("Mode Local");
}
/// <inheritdoc/>
public IEnumerable<Titre> FindTitres(int offset, int limit)
{
return this.dataStore.Titres
.OrderByDescending(t => t.DateCreation)
.Skip(offset)
.Take(limit);
}
/// <inheritdoc/>
public void IncrementNbLectures(Titre titre)
{
titre.NbLectures++;
}
/// <inheritdoc/>
public void IncrementNbLikes(Titre titre)
{
titre.NbLikes++;
}
/// <inheritdoc/>
public IEnumerable<Titre> Search(string mot)
{
return this.dataStore.Titres
.Where(t => t.Libelle != null && t.Libelle.Contains(mot));
}
/// <inheritdoc/>
public Titre Find(int idTitre)
{
return this.dataStore.Titres
.First(t => t.IdTitre == idTitre);
}
/// <inheritdoc/>
public IEnumerable<Titre> FindAll()
{
return this.dataStore.Titres;
}
/// <inheritdoc/>
public IEnumerable<Titre> SearchByStyle(string libelle)
{
return this.dataStore.Titres
.Where(t => t.Styles.Any(s => s.Libelle == libelle));
}
/// <inheritdoc/>
public void Update(Titre titre)
{
throw new NotSupportedException("Mode local");
}
}

View File

@@ -24,6 +24,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Webzine.EntitiesContext\Webzine.EntitiesContext.csproj" />
<ProjectReference Include="..\Webzine.Entity\Webzine.Entity.csproj" />
<ProjectReference Include="..\Webzine.Repository.Contracts\Webzine.Repository.Contracts.csproj" />
</ItemGroup>

View File

@@ -1,27 +1,33 @@
using Microsoft.AspNetCore.Mvc;
using Webzine.Entity;
using Webzine.Entity.Fixtures;
using Webzine.Repository.Contracts;
using Webzine.WebApplication.Areas.Administration.ViewModels.Artiste;
namespace Webzine.WebApplication.Areas.Administration.Controllers;
/// <summary>
/// Contrôleur pour la gestion des artistes dans l'administration du webzine.
/// </summary>
[Area("Administration")]
public class ArtisteController : Controller
{
// Injection du logger via le constructeur
private readonly ILogger<ArtisteController> _logger;
private readonly List<Artiste> _artistes;
private readonly ILogger<ArtisteController> logger;
private readonly IArtisteRepository artisteRepository;
public ArtisteController(ILogger<ArtisteController> logger)
/// <summary>
/// Initialise une nouvelle instance de la classe <see cref="ArtisteController"/>.
/// </summary>
/// <param name="logger">Logger.</param>
/// <param name="artisteRepository">Repository pour les artistes.</param>
public ArtisteController(
ILogger<ArtisteController> logger,
IArtisteRepository artisteRepository)
{
this._logger = logger;
this._logger.LogDebug(1, "initialisation du ArtisteController d'administration");
var factory = new DataFactory();
_artistes = factory.GenerateArtists(10);
this.logger = logger;
this.artisteRepository = artisteRepository;
}
/// <summary>
/// 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.
@@ -29,12 +35,11 @@ public class ArtisteController : Controller
/// <returns>Redirection.</returns>
public IActionResult Index()
{
IEnumerable<Artiste> artistes = this.artisteRepository.FindAll();
var _artistes_ordre = _artistes.OrderBy(t => t.Nom).ToList();
var artistes_ordre = artistes.OrderBy(t => t.Nom).ToList();
this._logger.LogInformation("Initialisation du contrôleur TitreController pour l'Administration.");
return View(_artistes_ordre);
return this.View(artistes_ordre);
}
/// <summary>
@@ -47,10 +52,10 @@ public class ArtisteController : Controller
{
Id = 0,
Nom = string.Empty,
Biographie = string.Empty
Biographie = string.Empty,
};
return View(model);
return this.View(model);
}
/// <summary>
@@ -60,32 +65,51 @@ public class ArtisteController : Controller
/// <returns>Redirection.</returns>
public IActionResult Edit(int id)
{
var artiste = _artistes.First(t => t.IdArtiste == id);
var artiste = this.artisteRepository.Find(id);
var model = new AdminArtisteForm
{
Id = artiste.IdArtiste,
Nom = artiste.Nom,
Biographie = artiste.Biographie
Biographie = artiste.Biographie,
};
return View(model);
return this.View(model);
}
/// <summary>
/// Renvoie à la page supprimer un artiste.
/// </summary>
/// <param name="id">L'identifiant de l'artiste à supprimer. </param>
/// <returns>Redirection.></returns>
/// <returns>Redirection.</returns>
public IActionResult Delete(int id)
{
var artiste = _artistes.First(t => t.IdArtiste == id);
var artiste = this.artisteRepository.Find(id);
var model = new AdminArtisteForm
{
Id = id,
Nom = artiste.Nom,
Biographie = artiste.Biographie
Biographie = artiste.Biographie,
};
return View(model);
return this.View(model);
}
/// <summary>
/// Méthode POST pour supprimer un artiste.
/// </summary>
/// <param name="model">L'artiste à supprimer.</param>
/// <returns>Redirige vers la page d'index d'admin artiste.</returns>
[HttpPost]
public IActionResult Delete(AdminArtisteForm model)
{
var artiste = this.artisteRepository.Find(model.Id);
if (artiste != null)
{
this.artisteRepository.Delete(artiste);
}
// 3. Redirect back to the list (or wherever you want them to go after)
return this.RedirectToAction("Index");
}
}

View File

@@ -8,19 +8,20 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers
[Area("Administration")]
public class CommentaireController : Controller
{
private readonly ILogger<CommentaireController> _logger;
private readonly ILogger<CommentaireController> logger;
private readonly List<Commentaire> _commentaires;
/// <summary>
/// Initialise une nouvelle instance de la classe <see cref="CommentaireController"/>.
/// Initialise une nouvelle instance du <see cref="CommentaireController"/>.
/// Les données sont générées dynamiquement via <see cref="DataFactory"/>.
/// </summary>
/// <param name="logger">Service de journalisation injecté.</param>
public CommentaireController(ILogger<CommentaireController> logger)
{
this._logger = logger;
this.logger = logger;
this._logger.LogInformation("Initialisation du contrôleur CommentaireController.");
this.logger.LogInformation("Initialisation du contrôleur CommentaireController.");
var factory = new DataFactory(); // TODO injecter le factory via DI pour éviter de le recréer à chaque fois
// faire une classe statique
@@ -31,7 +32,7 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers
_commentaires = factory.GenerateCommentaires(50, _titres);
this._logger.LogInformation("Données fictives générées avec succès.");
this.logger.LogInformation("Données fictives générées avec succès.");
}
/// <summary>
@@ -64,7 +65,7 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers
if (commentaire == null)
{
this._logger.LogWarning("Commentaire avec ID {Id} introuvable pour suppression.", id);
this.logger.LogWarning("Commentaire avec ID {Id} introuvable pour suppression.", id);
return RedirectToAction("Index");
}

View File

@@ -1,6 +1,8 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Webzine.Entity;
using Webzine.Entity.Fixtures;
using Webzine.Repository.Contracts;
using Webzine.WebApplication.Areas.Administration.ViewModels;
namespace Webzine.WebApplication.Areas.Administration.Controllers;
@@ -8,31 +10,24 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers;
[Area("Administration")]
public class DashboardController : Controller
{
private readonly ILogger<DashboardController> _logger;
private readonly List<Titre> _titres;
private readonly List<Style> _styles;
private readonly List<Artiste> _artistes;
private readonly ILogger<DashboardController> logger;
private readonly IStyleRepository styleRepository;
private readonly IArtisteRepository artisteRepository;
private readonly ITitreRepository titreRepository;
/// <summary>
/// Initialise une nouvelle instance du <see cref="DashboardController"/>.
/// Les données sont générées dynamiquement via <see cref="DataFactory"/>.
/// Initialise une nouvelle instance de la classe <see cref="DashboardController"/>.
/// </summary>
/// <param name="logger">Service de journalisation injecté.</param>
public DashboardController(ILogger<DashboardController> logger)
/// <param name="styleRepository">Repository des styles injecté.</param>
public DashboardController(ILogger<DashboardController> logger, IStyleRepository styleRepository, IArtisteRepository artisteRepository, ITitreRepository titreRepository)
{
this._logger = logger;
this.logger = logger;
this.styleRepository = styleRepository;
this.artisteRepository = artisteRepository;
this.titreRepository = titreRepository;
this._logger.LogInformation("Initialisation du contrôleur TitreController.");
var factory = new DataFactory();
_artistes = factory.GenerateArtists(10);
_styles = factory.GenerateStyles(10);
_titres = factory.GenerateTitres(30, _artistes, _styles);
factory.GenerateCommentaires(50, _titres);
this._logger.LogInformation("Données fictives générées avec succès.");
this.logger.LogInformation("Initialisation du contrôleur TitreController.");
}
/// <summary>
@@ -41,42 +36,42 @@ public class DashboardController : Controller
/// <returns>La vue Index du tableau de bord.</returns>
public IActionResult Index()
{
var artisteLePlusChronique = _titres
var artisteLePlusChronique = this.titreRepository.FindAll()
.GroupBy(t => t.Artiste)
.OrderByDescending(g => g.Count())
.FirstOrDefault();
.First();
var albumLePlusChronique = _titres
var albumLePlusChronique = this.titreRepository.FindAll()
.GroupBy(t => t.Artiste)
.OrderByDescending(g => g.Select(t => t.Album).Distinct().Count())
.FirstOrDefault();
.First();
var musiqueLaPlusJouee = _titres
var musiqueLaPlusJouee = this.titreRepository.FindAll()
.OrderByDescending(t => t.NbLectures)
.FirstOrDefault();
.First();
var model = new DashboardViewModel
{
NombreArtistes = _artistes.Count,
NombreArtistes = this.artisteRepository.FindAll().Count(),
ArtisteLePlusChronique = artisteLePlusChronique?.Key.Nom,
ArtisteLePlusChronique = artisteLePlusChronique.Key.Nom,
AlbumLePlusChronique = albumLePlusChronique?.Key.Nom,
AlbumLePlusChronique = albumLePlusChronique.Key.Nom,
NombreBiographies = _artistes.Count(a => !string.IsNullOrEmpty(a.Biographie)),
NombreBiographies = this.artisteRepository.FindAll().Count(a => !string.IsNullOrEmpty(a.Biographie)),
IdMusiqueLaPlusJouee = musiqueLaPlusJouee?.IdTitre ?? 0,
MusiqueLaPlusJouee = musiqueLaPlusJouee?.Libelle,
IdMusiqueLaPlusJouee = musiqueLaPlusJouee.IdTitre,
MusiqueLaPlusJouee = musiqueLaPlusJouee.Libelle,
NombreTitres = _titres.Count,
NombreTitres = this.titreRepository.Count(),
NombreGenres = _styles.Count,
NombreGenres = this.styleRepository.FindAll().Count(),
NombreLectures = _titres.Sum(t => t.NbLectures),
NombreLectures = this.titreRepository.FindAll().Sum(t => t.NbLectures),
NombreLikes = _titres.Sum(t => t.NbLikes)
NombreLikes = this.titreRepository.FindAll().Sum(t => t.NbLikes),
};
return View(model);
return this.View(model);
}
}

View File

@@ -1,70 +1,62 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Webzine.Entity;
using Webzine.Entity.Fixtures;
using Webzine.Repository.Contracts;
using Webzine.WebApplication.Areas.Administration.ViewModels.Style;
namespace Webzine.WebApplication.Areas.Administration.Controllers
{
/// <summary>
/// Contrôleur pour la gestion des styles dans l'administration du webzine.
/// </summary>
[Area("Administration")]
public class StyleController : Controller
{
private readonly ILogger<StyleController> _logger;
private readonly List<Style> _styles;
private readonly ILogger<StyleController> logger;
private readonly IStyleRepository styleRepository;
/// <summary>
/// Initialise une nouvelle instance du <see cref="StyleController"/>.
/// Les données sont générées dynamiquement via <see cref="DataFactory"/>.
/// Initialise une nouvelle instance de la classe <see cref="StyleController"/>.
/// </summary>
/// <param name="logger">Service de journalisation injecté.</param>
public StyleController(ILogger<StyleController> logger)
/// <param name="styles">Repository des styles injecté.</param>
public StyleController(
ILogger<StyleController> logger,
IStyleRepository styleRepository)
{
this._logger = logger;
this.logger = logger;
this._logger.LogInformation("Initialisation du contrôleur StyleController.");
this.logger.LogInformation("Initialisation du contrôleur StyleController.");
var factory = new DataFactory();
_styles = factory.GenerateStyles(10);
this._logger.LogInformation("Données fictives générées avec succès.");
this.styleRepository = styleRepository;
}
// GET: Administration/Styles
/// <summary>
/// Affiche la liste des styles dans la vue Index.
/// </summary>
/// <returns>La vue Index avec le ViewModel contenant la liste des styles.</returns>
public IActionResult Index()
{
// Création de données "bouchon" (mock) pour tester l'affichage
var listeStyles = this._styles;
var listeStyles = this.styleRepository.FindAll().Take(10);
// Initialisation du ViewModel
var viewModel = new StyleViewModel
{
Styles = listeStyles,
};
return View(viewModel);
return this.View(listeStyles);
}
// GET: Administration/Styles/Create
/// <summary>
/// Affiche la vue de création d'un nouveau style.
/// </summary>
/// <returns>La vue Create pour ajouter un nouveau style.</returns>
public IActionResult Create()
{
return View();
return this.View();
}
// GET: Administration/Styles/Delete/5
/// <summary>
/// Affiche la vue de confirmation de suppression d'un style, en récupérant les détails du style à supprimer à partir de l'identifiant fourni.
/// </summary>
/// <param name="id">L'identifiant du style à supprimer.</param>
/// <returns>La vue de confirmation de suppression avec le ViewModel contenant les détails du style à supprimer, ou une redirection vers l'index si le style n'existe pas.</returns>
public IActionResult Delete(int id)
{
var style = this._styles
.FirstOrDefault(c => c.IdStyle == id);
if (style == null)
{
this._logger.LogWarning("Style avec ID {Id} introuvable pour suppression.", id);
return RedirectToAction("Index");
}
var style = this.styleRepository.Find(id);
var vm = new StyleDeleteViewModel
{
@@ -72,30 +64,44 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers
Libelle = style.Libelle,
};
return View(vm);
return this.View(vm);
}
// GET: Administration/Styles/Edit/5
/// <summary>
/// Méthode POST pour supprimer un style.
/// </summary>
/// <param name="model">Le style à supprimer.</param>
/// <returns>Redirige vers la page d'index d'admin style.</returns>
[HttpPost]
public IActionResult Delete(StyleEditViewModel model)
{
var style = this.styleRepository.Find(model.IdStyle);
if (style != null)
{
this.styleRepository.Delete(style);
}
return this.RedirectToAction("Index");
}
/// <summary>
/// Affiche la vue d'édition d'un style existant, en récupérant les détails du style à éditer à partir de l'identifiant fourni.
/// </summary>
/// <param name="id">L'identifiant du style à éditer.</param>
/// <returns>La vue d'édition avec le ViewModel contenant les détails du style à éditer, ou une redirection vers l'index si le style n'existe pas.</returns>
[HttpGet]
public IActionResult Edit(int id)
{
// Recherche du style (simulation avec la liste _styles)
var style = _styles.FirstOrDefault(s => s.IdStyle == id);
var style = styleRepository.Find(id);
if (style == null)
{
this._logger.LogWarning("Style avec ID {Id} introuvable pour style.", id);
return RedirectToAction("Index");
}
// Mapping vers le ViewModel
var model = new StyleEditViewModel
{
IdStyle = style.IdStyle,
Libelle = style.Libelle
Libelle = style.Libelle,
};
return View(model);
return this.View(model);
}
}
}

View File

@@ -2,39 +2,36 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Webzine.Entity;
using Webzine.Entity.Fixtures;
using Webzine.Repository.Contracts;
using Webzine.WebApplication.Areas.Administration.ViewModels.Titre;
namespace Webzine.WebApplication.Areas.Administration.Controllers;
/// <summary>
/// Contrôleur pour la gestion des titres en administration. Ce contrôleur gère les opérations de création, modification, suppression et affichage des titres dans l'interface d'administration du webzine. Les données sont générées dynamiquement à l'aide de la classe <see cref="DataFactory"/> pour simuler un environnement de développement sans accès à une base de données réelle. 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.
/// </summary>
[Area("Administration")]
public class TitreController : Controller
{
private readonly ILogger<TitreController> _logger;
private readonly List<Titre> _titres;
private readonly List<Style> _styles;
private readonly List<Artiste> _artistes;
private readonly ILogger<TitreController> logger;
private readonly ITitreRepository titreRepository;
private readonly IArtisteRepository artisteRepository;
private readonly IStyleRepository styleRepository;
/// <summary>
/// Initialise une nouvelle instance du <see cref="TitreController"/>.
/// Les données sont générées dynamiquement via <see cref="DataFactory"/>.
/// Initialise une nouvelle instance de la classe <see cref="TitreController"/>.
/// </summary>
/// <param name="logger">Service de journalisation injecté.</param>
public TitreController(ILogger<TitreController> logger)
/// <param name="titreRepository">Repository des titres injecté pour accéder aux données des titres.</param>
/// <param name="artisteRepository">Repository des artistes injecté pour accéder aux données des artistes, nécessaires pour les associations avec les titres.</param>
/// <param name="styleRepository">Repository des styles injecté pour accéder aux données des styles, nécessaires pour les associations avec les titres.</param>
public TitreController(ILogger<TitreController> logger, ITitreRepository titreRepository, IArtisteRepository artisteRepository, IStyleRepository styleRepository)
{
this._logger = logger;
this._logger.LogInformation("Initialisation du contrôleur TitreController pour l'Administration.");
var factory = new DataFactory();
_artistes = factory.GenerateArtists(10);
_styles = factory.GenerateStyles(10);
_titres = factory.GenerateTitres(30, _artistes, _styles);
factory.GenerateCommentaires(50, _titres);
this._logger.LogInformation("Données fictives générées avec succès.");
this.logger = logger;
this.titreRepository = titreRepository;
this.artisteRepository = artisteRepository;
this.styleRepository = styleRepository;
}
/// <summary>
@@ -43,19 +40,19 @@ public class TitreController : Controller
/// <returns>La vue Index avec le ViewModel contenant la liste des titres.</returns>
public IActionResult Index()
{
var model = _titres.Select(t => new AdminTitreList
var model = this.titreRepository.FindAll().Take(10).Select(t => new AdminTitreList
{
Id = t.IdTitre,
Artiste = t.Artiste?.Nom,
Artiste = t.Artiste.Nom,
Titre = t.Libelle,
Duree = TimeSpan.FromSeconds(t.Duree).ToString(@"mm\:ss"),
DateSortie = t.DateSortie,
NbLectures = t.NbLectures,
NbLikes = t.NbLikes,
NbCommentaires = t.Commentaires?.Count ?? 0
NbCommentaires = t.Commentaires.Count,
}).ToList();
return View(model);
return this.View(model);
}
/// <summary>
@@ -66,20 +63,20 @@ public class TitreController : Controller
{
var model = new AdminTitreForm
{
Artistes = _artistes.Select(a => new SelectListItem
Artistes = this.artisteRepository.FindAll().Select(a => new SelectListItem
{
Value = a.IdArtiste.ToString(),
Text = a.Nom
Text = a.Nom,
}).ToList(),
AllStyles = _styles.Select(s => new SelectListItem
AllStyles = this.styleRepository.FindAll().Select(s => new SelectListItem
{
Value = s.IdStyle.ToString(),
Text = s.Libelle
}).ToList()
Text = s.Libelle,
}).ToList(),
};
return View(model);
return this.View(model);
}
/// <summary>
@@ -89,7 +86,7 @@ public class TitreController : Controller
/// <returns>La vue Edit avec le ViewModel contenant les données du titre à modifier, ainsi que les listes déroulantes pour les artistes et les styles. </returns>
public IActionResult Edit(int id)
{
var titre = _titres.First(t => t.IdTitre == id);
var titre = this.titreRepository.Find(id);
var model = new AdminTitreForm
{
@@ -106,20 +103,20 @@ public class TitreController : Controller
NbLikes = titre.NbLikes,
Styles = titre.Styles.Select(s => s.IdStyle).ToList(),
Artistes = _artistes.Select(a => new SelectListItem
Artistes = this.artisteRepository.FindAll().Select(a => new SelectListItem
{
Value = a.IdArtiste.ToString(),
Text = a.Nom
Text = a.Nom,
}).ToList(),
AllStyles = _styles.Select(s => new SelectListItem
AllStyles = this.styleRepository.FindAll().Select(s => new SelectListItem
{
Value = s.IdStyle.ToString(),
Text = s.Libelle
}).ToList()
Text = s.Libelle,
}).ToList(),
};
return View(model);
return this.View(model);
}
/// <summary>
@@ -129,15 +126,32 @@ public class TitreController : Controller
/// <returns>La vue de confirmation de suppression avec le ViewModel contenant les détails du titre à supprimer.</returns>
public IActionResult Delete(int id)
{
var titre = _titres.First(t => t.IdTitre == id);
var titre = this.titreRepository.Find(id);
var model = new AdminTitreDelete
{
Id = titre.IdTitre,
Titre = titre.Libelle,
Artiste = titre.Artiste?.Nom
Artiste = titre.Artiste.Nom,
};
return View(model);
return this.View(model);
}
/// <summary>
/// Méthode POST pour supprimer un titre.
/// </summary>
/// <param name="model">Le titre à supprimer.</param>
/// <returns>Redirige vers la page d'index d'admin titre.</returns>
[HttpPost]
public IActionResult Delete(AdminTitreDelete model)
{
var titre = this.titreRepository.Find(model.Id);
if (titre != null)
{
this.titreRepository.Delete(titre);
}
return this.RedirectToAction("Index");
}
}

View File

@@ -1,17 +0,0 @@
// <copyright file="StyleViewModel.cs" company="Webzine">
// Copyright (c) Webzine. Tout droit réservé.
// </copyright>
namespace Webzine.WebApplication.Areas.Administration.ViewModels.Style
{
/// <summary>
/// ViewModel pour afficher la liste des commentaires en administration.
/// </summary>
public class StyleViewModel
{
/// <summary>
/// Obtient ou définit la liste des commentaires.
/// </summary>
public IEnumerable<Entity.Style> Styles { get; set; } = new List<Entity.Style>();
}
}

View File

@@ -16,7 +16,7 @@
<thead class="table-active">
<tr>
<th scope="col" class="p-2">Nom</th>
<th scope="col" class="text-center p-2" style="width: 100px;">Actions</th>
<th scope="col" class="text-center p-2">Actions</th>
</tr>
</thead>
<tbody>
@@ -33,7 +33,9 @@
<i class="fa fa-edit"></i>
</a>
<a asp-action="Delete" asp-route-id="@artiste.IdArtiste">
<a asp-action="Delete"
asp-controller="Artiste"
asp-route-id="@artiste.IdArtiste">
<i class="fa fa-trash"></i>
</a>

View File

@@ -2,7 +2,7 @@
<div class="container">
<!-- ARTISTE -->
<div class="row mb-3 align-items-center">
<div class="row mb-3">
<label class="col-md-3 col-form-label">Nom de l'artiste<span class="text-danger">*</span></label>
<div class="col-md-9">
<input asp-for="Nom" class="form-control" />
@@ -10,14 +10,15 @@
</div>
<!-- BIOGRAPHIE -->
<div class="row mb-3 align-items-center">
<label class="col-md-3 col-form-label">Biographie<span class="text-danger">*</span></label>
<div class="row mb-3">
<label class="col-md-3 col-form-label">Biographie</label>
<div class="col-md-9">
<input asp-for="Biographie" class="form-control"/>
<textarea asp-for="Biographie" class="form-control" rows="5"></textarea>
</div>
</div>
<!-- BOUTONS -->
<div class="row mt-4">
<div class="col-md-9 offset-md-3">

View File

@@ -14,10 +14,10 @@
<thead class="table-active">
<tr>
<th scope="col">Titre</th>
<th scope="col">Auteur</th>
<th scope="col">Nom</th>
<th scope="col">Commentaire</th>
<th scope="col">Date de création</th>
<th scope="col" class="text-center p-2" style="width: 100px" ;>Actions</th>
<th scope="col" class="text-center p-2">Actions</th>
</tr>
</thead>
<tbody>
@@ -25,7 +25,9 @@
{
<tr class="align-middle">
<td>
@commentaire.Titre.Libelle
<a asp-action="Details" asp-controller="Titre" asp-route-id="@commentaire.Titre.IdTitre">
@commentaire.Titre.Libelle
</a>
</td>
<td>
@commentaire.Auteur

View File

@@ -12,19 +12,19 @@
<div class="col-md-4">
<a asp-area="Administration"
asp-controller="Artiste">
<div class="ratio ratio-4x3">
<div class="card shadow-sm p-4 bg-light h-100 dashboard-card">
<i class="fa fa-users fa-3x text-primary mb-3"></i>
<div class="py-5 bg-light rounded-3 d-flex flex-column justify-content-center align-items-center text-primary">
<i class="fa fa-users fa-5x text-primary mb-3"></i>
<h3>
@Model.NombreArtistes
</h3>
<p>
artistes
</p>
<h2>
@Model.NombreArtistes
</h2>
<p>
artistes
</p>
</div>
</div>
</a>
</div>
@@ -33,17 +33,17 @@
<a asp-area=""
asp-controller="Artiste"
asp-route-nom="@Model.ArtisteLePlusChronique">
<div class="ratio ratio-4x3">
<div class="py-5 bg-light rounded-3 d-flex flex-column justify-content-center align-items-center text-primary">
<i class="fa fa-user fa-5x text-primary mb-3"></i>
<div class="card shadow-sm p-4 bg-light h-100 dashboard-card">
<i class="fa fa-user fa-3x text-primary mb-3"></i>
<h2>
@Model.ArtisteLePlusChronique
</h2>
<h3>
@Model.ArtisteLePlusChronique
</h3>
<p>artiste le plus chroniqué</p>
<p>artiste le plus chroniqué</p>
</div>
</div>
</a>
</div>
@@ -52,19 +52,20 @@
<a asp-area=""
asp-controller="Artiste"
asp-route-nom="@Model.AlbumLePlusChronique">
<div class="ratio ratio-4x3">
<div class="card shadow-sm p-4 bg-light h-100 dashboard-card">
<i class="fa fa-trophy fa-3x text-primary mb-3"></i>
<div class="py-5 bg-light rounded-3 d-flex flex-column justify-content-center align-items-center text-primary">
<i class="fa fa-trophy fa-5x text-primary mb-3"></i>
<h3>
<h2>
@Model.AlbumLePlusChronique
</h3>
</h2>
<p>
artiste avec le plus d'albums distincts
</p>
</div>
</div>
</a>
</div>
@@ -72,19 +73,20 @@
<div class="col-md-4">
<a asp-area="Administration"
asp-controller="Titre">
<div class="ratio ratio-4x3">
<div class="card shadow-sm p-4 bg-light h-100 dashboard-card">
<i class="fa fa-book fa-3x text-primary mb-3"></i>
<div class="py-5 bg-light rounded-3 d-flex flex-column justify-content-center align-items-center text-primary">
<i class="fa fa-book fa-5x text-primary mb-3"></i>
<h3>
<h2>
@Model.NombreBiographies
</h3>
</h2>
<p>
biographies d'artistes
</p>
</div>
</div>
</a>
</div>
@@ -94,19 +96,20 @@
asp-controller="Titre"
asp-action="Details"
asp-route-id="@Model.IdMusiqueLaPlusJouee">
<div class="ratio ratio-4x3">
<div class="card shadow-sm p-4 bg-light h-100 dashboard-card">
<i class="fa fa-compact-disc fa-3x text-primary mb-3"></i>
<div class="py-5 bg-light rounded-3 d-flex flex-column justify-content-center align-items-center text-primary">
<i class="fa fa-compact-disc fa-5x text-primary mb-3"></i>
<h4>
<h2>
@Model.MusiqueLaPlusJouee
</h4>
</h2>
<p>
titre le plus lu
</p>
</div>
</div>
</a>
</div>
@@ -114,19 +117,20 @@
<div class="col-md-4">
<a asp-area="Administration"
asp-controller="Titre">
<div class="ratio ratio-4x3">
<div class="card shadow-sm p-4 bg-light h-100 dashboard-card">
<i class="fa fa-music fa-3x text-primary mb-3"></i>
<div class="py-5 bg-light rounded-3 d-flex flex-column justify-content-center align-items-center text-primary">
<i class="fa fa-music fa-5x text-primary mb-3"></i>
<h3>
<h2>
@Model.NombreTitres
</h3>
</h2>
<p>
titres
</p>
</div>
</div>
</a>
</div>
@@ -134,50 +138,57 @@
<div class="col-md-4">
<a asp-area="Administration"
asp-controller="Styles">
<div class="ratio ratio-4x3">
<div class="card shadow-sm p-4 bg-light h-100 dashboard-card">
<i class="fa fa-tags fa-3x text-primary mb-3"></i>
<div class="py-5 bg-light rounded-3 d-flex flex-column justify-content-center align-items-center text-primary">
<i class="fa fa-tags fa-5x text-primary mb-3"></i>
<h3>
<h2>
@Model.NombreGenres
</h3>
</h2>
<p>
styles de musique
</p>
</div>
</div>
</a>
</div>
<!-- NOMBRE DE LECTURES -->
<div class="col-md-4">
<div class="card shadow-sm p-4 bg-light h-100">
<i class="fa fa-eye fa-3x text-dark mb-3"></i>
<div class="ratio ratio-4x3">
<h3>
@Model.NombreLectures
</h3>
<div class="py-5 bg-light rounded-3 d-flex flex-column justify-content-center align-items-center">
<i class="fa fa-eye fa-5x text-dark mb-3"></i>
<p>
lectures
</p>
<h2>
@Model.NombreLectures
</h2>
<p>
lectures
</p>
</div>
</div>
</div>
<!-- TOTAL LIKES -->
<div class="col-md-4">
<div class="card shadow-sm p-4 bg-light h-100">
<i class="fa fa-thumbs-up fa-3x text-dark mb-3"></i>
<div class="ratio ratio-4x3">
<h3>
<div class="py-5 bg-light rounded-3 d-flex flex-column justify-content-center align-items-center">
<i class="fa fa-thumbs-up fa-5x text-dark mb-3"></i>
<h2>
@Model.NombreLikes
</h3>
</h2>
<p>
likes
</p>
</div>
</div>
</div>
</div>

View File

@@ -22,7 +22,7 @@
@* Input *@
<div class="me-3">
<input asp-for="Libelle" class="form-control" style="width: 250px;" />
<input asp-for="Libelle" class="form-control" />
</div>
@* Bouton *@

View File

@@ -25,7 +25,7 @@
@* Input *@
<div class="me-3">
<input asp-for="Libelle" class="form-control" style="width: 250px;" />
<input asp-for="Libelle" class="form-control" />
</div>
@* Bouton *@

View File

@@ -1,4 +1,4 @@
@model Webzine.WebApplication.Areas.Administration.ViewModels.Style.StyleViewModel
@model IEnumerable<Webzine.Entity.Style>
@{
ViewData["Title"] = "Styles";
@@ -20,23 +20,23 @@
<thead class="table-active">
<tr>
<th scope="col" class="p-2">Libellé</th>
<th scope="col" class="text-center p-2" style="width: 100px;">Actions</th>
<th scope="col" class="text-center p-2">Actions</th>
</tr>
</thead>
<tbody>
@if (Model.Styles != null && Model.Styles.Any())
@if ( Model.Any())
{
@foreach (Webzine.Entity.Style style in Model.Styles)
@foreach (Webzine.Entity.Style style in Model)
{
<tr class="align-middle">
<td class="p-2">
<tr >
<td class="p-2 w-75">
@style.Libelle
</td>
<td class="text-center p-2">
<td class="text-center w-auto p-2">
<a asp-action="Edit" asp-route-id="@style.IdStyle" class="text-primary me-2" title="Éditer">
<i class="fas fa-edit"></i>
</a>
<a asp-action="Delete" asp-route-id="@style.IdStyle" title="Supprimer">
<a asp-action="Delete" asp-route-id="@style.IdStyle" title="Supprimer">
<i class="fas fa-trash"></i>
</a>
</td>

View File

@@ -62,7 +62,7 @@
<!-- JAQUETTE -->
<div class="row mb-3 align-items-center">
<label class="col-md-3 col-form-label">Jaquette<span class="text-danger">*</span></label>
<label class="col-md-3 col-form-label">Jaquette de l'album<span class="text-danger">*</span></label>
<div class="col-md-9">
<input asp-for="UrlJaquette"
class="form-control"/>
@@ -102,13 +102,13 @@
</div>
<!-- LECTURES / LIKES (AFFICHAGE UNIQUEMENT) -->
<div class="row mb-4 align-items-center">
<div class="row align-items-center">
<label class="col-md-3 col-form-label">Nb de lectures<span class="text-danger">*</span></label>
<div class="col-md-3">
@Model.NbLectures
</div>
</div>
<div class="row mb-4 align-items-center">
<div class="row align-items-center">
<label class="col-md-3 col-form-label">Nb de likes<span class="text-danger">*</span></label>
<div class="col-md-3">
@Model.NbLikes

View File

@@ -5,7 +5,7 @@
namespace Webzine.WebApplication.Controllers
{
using Microsoft.AspNetCore.Mvc;
using Webzine.Repository.Fake;
using Webzine.Repository.Contracts;
using Webzine.WebApplication.ViewModels.Accueil;
/// <summary>
@@ -16,17 +16,23 @@ namespace Webzine.WebApplication.Controllers
// Injection du logger via le constructeur
private readonly ILogger<AccueilController> logger;
private readonly IConfiguration configuration;
private readonly ITitreRepository titreRepository;
/// <summary>
/// Initialise une nouvelle instance de la classe <see cref="AccueilController"/>.
/// </summary>
/// <param name="logger">Service de journalisation injecté pour enregistrer les événements et les erreurs.</param>
/// <param name="configuration">Service d'injection de configuration pour accéder aux paramètres de l'application.</param>
public AccueilController(ILogger<AccueilController> logger, IConfiguration configuration)
public AccueilController(
ILogger<AccueilController> logger,
IConfiguration configuration,
ITitreRepository titreRepository)
{
this.logger = logger;
this.configuration = configuration;
this.titreRepository = titreRepository;
this.logger.LogDebug(1, "initialisation du AccueilController");
this.titreRepository = titreRepository;
}
/// <summary>
@@ -38,19 +44,19 @@ namespace Webzine.WebApplication.Controllers
this.logger.LogInformation("Arrivée sur la page d'accueil");
var derniereChronique = this.configuration.GetValue<int>("Webzine:NombreDerniereChronique");
var topTitres = this.configuration.GetValue<int>("Webzine:NombreDeTopTitres");
var titres = FakeDataFactory.GetTitres();
var nbTopTitres = this.configuration.GetValue<int>("Webzine:NombreDeTopTitres");
// var titres = FakeDataFactory.GetTitres();
// var titres = this.titreRepository.FindTitres(derniereChronique, nbTopTitres);
var titres = this.titreRepository.FindAll();
var vm = new AccueilIndexViewModel
{
DerniersTitres = titres
.OrderByDescending(t => t.DateCreation)
.Take(derniereChronique)
.ToList(),
DerniersTitres = titres.Take(derniereChronique).ToList(),
TopTitres = titres
.OrderByDescending(t => t.NbLikes)
.Take(topTitres)
.Take(nbTopTitres)
.ToList(),
};

View File

@@ -4,26 +4,16 @@ namespace Webzine.WebApplication.Controllers;
public class ApiController : ControllerBase
{
private readonly ILogger<ApiController> _logger;
private readonly ILogger<ApiController> logger;
/// <summary>
/// Initialise une nouvelle instance du <see cref="ApiController"/> avec un service de journalisation injecté.
/// Initialise une nouvelle instance de la classe <see cref="ApiController"/>.
/// </summary>
/// <param name="logger">Service de journalisation injecté pour enregistrer les événements et les erreurs.</param>
public ApiController(ILogger<ApiController> logger)
{
this._logger = logger;
this._logger.LogDebug(1, "initialisation du ApiController");
}
/// <summary>
/// Endpoint de test pour vérifier que l'API fonctionne correctement. Retourne une chaîne de caractères "Hello World !".
/// </summary>
/// <returns>Une chaîne de caractères "Hello World !".</returns>
[HttpGet]
public string HelloWorld()
{
return "Hello World !";
this.logger = logger;
this.logger.LogDebug(1, "initialisation du ApiController");
}
/// <summary>
@@ -33,7 +23,7 @@ public class ApiController : ControllerBase
[HttpGet]
public IActionResult Version()
{
this._logger.LogInformation("Get Version was called");
this.logger.LogInformation("Get Version was called");
return Ok(new
{

View File

@@ -1,21 +1,26 @@
using Microsoft.AspNetCore.Mvc;
using Webzine.Entity.Fixtures;
namespace Webzine.WebApplication.Controllers
namespace Webzine.WebApplication.Controllers
{
using Microsoft.AspNetCore.Mvc;
using Webzine.Entity.Fixtures;
using Webzine.Repository.Contracts;
using Webzine.WebApplication.ViewModels.Artiste;
public class ArtisteController : Controller
{
// Injection du logger via le constructeur
private readonly ILogger<ArtisteController> _logger;
private readonly ILogger<ArtisteController> logger;
private readonly IArtisteRepository _artisteRepository;
/// <summary>
/// Initialise une nouvelle instance du <see cref="ArtisteController"/> avec un service de journalisation injecté.
/// Initialise une nouvelle instance du <see cref="ArtisteController"/>. avec un service de journalisation injecté.
/// </summary>
/// <param name="logger">Service de journalisation injecté pour enregistrer les événements et les erreurs.</param>
public ArtisteController(ILogger<ArtisteController> logger)
public ArtisteController(ILogger<ArtisteController> logger,
IArtisteRepository artisteRepository)
{
this._logger = logger;
this._logger.LogDebug(1, "initialisation du ArtisteController");
this.logger = logger;
this.logger.LogDebug("Initialisation du ArtisteController");
this._artisteRepository = artisteRepository;
}
/// <summary>
@@ -26,15 +31,12 @@ namespace Webzine.WebApplication.Controllers
[HttpGet("/artiste/{nom}")]
public IActionResult Index(string nom)
{
this._logger.LogInformation("Tentative d'accès à l'artiste avec le nom : {NomArtiste}", nom);
this.logger.LogInformation("Tentative d'accès à l'artiste avec le nom : {NomArtiste}", nom);
if (string.IsNullOrEmpty(nom))
{
this._logger.LogWarning("Nom de l'artiste manquant dans la requête.");
return RedirectToAction("Index", "Accueil");
this.logger.LogWarning("Nom de l'artiste manquant dans la requête.");
return RedirectToAction("Index");
}
// On transforme "fatal-bazooka" en "Fatal Bazooka" pour la factory
@@ -42,17 +44,28 @@ namespace Webzine.WebApplication.Controllers
.ToTitleCase(nom.Replace("-", " "));
// On appelle la factory pour obtenir l'artiste unique
var artiste = ArtisteFactory.SeedArtisteByName(nomPropre);
var artiste = this._artisteRepository.FindByName(nomPropre);
if (artiste == null)
{
this._logger.LogWarning("Artiste non trouvé pour le nom : {NomArtiste}", nomPropre);
this.logger.LogWarning("Artiste non trouvé pour le nom : {NomArtiste}", nomPropre);
return RedirectToAction("Index");
}
var viewModel = new ArtisteDetailsViewModel
{
IdArtiste = artiste.IdArtiste,
Nom = artiste.Nom,
Biographie = artiste.Biographie,
// On effectue le groupement ici une bonne fois pour toutes
AlbumsGroupes = artiste.Titres
.OrderBy(t => t.Libelle)
.GroupBy(t => t.Album)
.OrderBy(g => g.Key),
};
this._logger.LogInformation("Artiste trouvé : {NomArtiste}", nom);
this.logger.LogInformation("Artiste trouvé : {NomArtiste}", nom);
return View(artiste);
return View(viewModel);
}
}
}

View File

@@ -8,16 +8,16 @@ namespace Webzine.WebApplication.Controllers
public class ContactController : Controller
{
// Injection du logger via le constructeur
private readonly ILogger<ContactController> _logger;
private readonly ILogger<ContactController> logger;
/// <summary>
/// Initialise une nouvelle instance du <see cref="ContactController"/> avec un service de journalisation injecté.
/// Initialise une nouvelle instance de la classe <see cref="ContactController"/>.
/// </summary>
/// <param name="logger">Service de journalisation injecté pour enregistrer les événements et les erreurs.</param>
public ContactController(ILogger<ContactController> logger)
{
this._logger = logger;
this._logger.LogDebug(1, "initialisation du ContactController");
this.logger = logger;
this.logger.LogDebug(1, "initialisation du ContactController");
}
/// <summary>
@@ -26,7 +26,7 @@ namespace Webzine.WebApplication.Controllers
/// <returns>La vue Index de la page de contact.</returns>
public IActionResult Index()
{
return View();
return this.View();
}
}
}

View File

@@ -1,63 +1,68 @@
using Microsoft.AspNetCore.Mvc;
using Webzine.Repository.Contracts;
using Webzine.WebApplication.ViewModels.Recherche;
using Webzine.WebApplication.ViewModels.Titre;
// <copyright file="RechercheController.cs" company=" Equipe 1 - ">
// Copyright (c) Equipe 1 - . All rights reserved.
// </copyright>
namespace Webzine.WebApplication.Controllers;
[Route("recherche")]
public class RechercheController : Controller
namespace Webzine.WebApplication.Controllers
{
private readonly ILogger<RechercheController> _logger;
private readonly ITitreRepository _titreRepository;
using Microsoft.AspNetCore.Mvc;
using Webzine.Repository.Contracts;
using Webzine.WebApplication.ViewModels.Recherche;
using Webzine.WebApplication.ViewModels.Titre;
public RechercheController(ILogger<RechercheController> logger, ITitreRepository titreRepository)
[Route("recherche")]
public class RechercheController : Controller
{
this._logger = logger;
this._titreRepository = titreRepository;
}
private readonly ILogger<RechercheController> logger;
private readonly ITitreRepository titreRepository;
[HttpPost("")]
public IActionResult Index(string mot)
{
this._logger.LogInformation("Recherche artistes/titres pour le mot : {Mot}.", mot);
var titres = _titreRepository.Search(mot)
.Concat(_titreRepository.SearchByStyle(mot))
.DistinctBy(t => t.IdTitre)
.OrderBy(t => t.Libelle)
.Select(t => new TitreStyleItem
{
IdTitre = t.IdTitre,
Libelle = t.Libelle,
ArtisteNom = t.Artiste?.Nom,
UrlJaquette = t.UrlJaquette,
Duree = t.Duree
})
.ToList();
var artistes = _titreRepository.FindAll()
.Select(t => t.Artiste)
.Where(a => a != null
&& !string.IsNullOrWhiteSpace(a.Nom)
&& !string.IsNullOrWhiteSpace(mot)
&& a.Nom.Contains(mot, StringComparison.OrdinalIgnoreCase))
.DistinctBy(a => a!.IdArtiste)
.OrderBy(a => a!.Nom)
.Select(a => new RechercheArtisteItem
{
Nom = a!.Nom,
NombreDeTitres = a.Titres?.Count ?? 0
})
.ToList();
var vm = new RechercheIndexViewModel
public RechercheController(ILogger<RechercheController> logger, ITitreRepository titreRepository)
{
Mot = mot,
Artistes = artistes,
Titres = titres
};
this.logger = logger;
this.titreRepository = titreRepository;
}
return View(vm);
[HttpPost("")]
public IActionResult Index(string mot)
{
this.logger.LogInformation("Recherche artistes/titres pour le mot : {Mot}.", mot);
var titres = this.titreRepository.Search(mot)
.Concat(this.titreRepository.SearchByStyle(mot))
.DistinctBy(t => t.IdTitre)
.OrderBy(t => t.Libelle)
.Select(t => new TitreStyleItem
{
IdTitre = t.IdTitre,
Libelle = t.Libelle,
ArtisteNom = t.Artiste?.Nom,
UrlJaquette = t.UrlJaquette,
Duree = t.Duree,
})
.ToList();
var artistes = this.titreRepository.FindAll()
.Select(t => t.Artiste)
.Where(a => a != null
&& !string.IsNullOrWhiteSpace(a.Nom)
&& !string.IsNullOrWhiteSpace(mot)
&& a.Nom.Contains(mot, StringComparison.OrdinalIgnoreCase))
.DistinctBy(a => a!.IdArtiste)
.OrderBy(a => a!.Nom)
.Select(a => new RechercheArtisteItem
{
Nom = a!.Nom,
NombreDeTitres = a.Titres?.Count ?? 0,
})
.ToList();
var vm = new RechercheIndexViewModel
{
Mot = mot,
Artistes = artistes,
Titres = titres,
};
return this.View(vm);
}
}
}

View File

@@ -1,166 +1,171 @@
using Microsoft.AspNetCore.Mvc;
using Webzine.Entity;
using Webzine.Repository.Contracts;
using Webzine.WebApplication.ViewModels.Titre;
// <copyright file="TitreController.cs" company=" Equipe 1 - ">
// Copyright (c) Equipe 1 - . All rights reserved.
// </copyright>
namespace Webzine.WebApplication.Controllers;
/// <summary>
/// Controleur responsable de la gestion des titres musicaux :
/// affichage des details, filtrage par style,
/// ajout de likes, commentaires et recherche.
/// </summary>
[Route("titre")]
public class TitreController : Controller
namespace Webzine.WebApplication.Controllers
{
private readonly ILogger<TitreController> _logger;
private readonly ITitreRepository _titreRepository;
using Microsoft.AspNetCore.Mvc;
using Webzine.Entity;
using Webzine.Repository.Contracts;
using Webzine.WebApplication.ViewModels.Titre;
/// <summary>
/// Initialise une nouvelle instance du <see cref="TitreController"/>.
/// Controleur responsable de la gestion des titres musicaux :
/// affichage des details, filtrage par style,
/// ajout de likes, commentaires et recherche.
/// </summary>
/// <param name="logger">Service de journalisation injecte.</param>
/// <param name="titreRepository">Repository des titres injecte.</param>
public TitreController(ILogger<TitreController> logger, ITitreRepository titreRepository)
[Route("titre")]
public class TitreController : Controller
{
this._logger = logger;
this._titreRepository = titreRepository;
private readonly ILogger<TitreController> logger;
private readonly ITitreRepository titreRepository;
this._logger.LogInformation("Initialisation du controleur TitreController.");
}
/// <summary>
/// Affiche le detail d'un titre specifique.
/// </summary>
/// <param name="id">Identifiant du titre.</param>
/// <returns>Vue des details ou 404 si introuvable.</returns>
[HttpGet("{id}")]
public IActionResult Details(int id)
{
this._logger.LogInformation("Demande d'affichage du detail pour le titre ID {Id}.", id);
var titre = this._titreRepository.Find(id);
if (titre == null)
/// <summary>
/// Initialise une nouvelle instance de la classe <see cref="TitreController"/>.
/// </summary>
/// <param name="logger">Service de journalisation injecte.</param>
/// <param name="titreRepository">Repository des titres injecte.</param>
public TitreController(ILogger<TitreController> logger, ITitreRepository titreRepository)
{
this._logger.LogWarning("Titre avec ID {Id} introuvable.", id);
return RedirectToAction("Index");
this.logger = logger;
this.titreRepository = titreRepository;
this.logger.LogInformation("Initialisation du controleur TitreController.");
}
var vm = new TitreDetail
/// <summary>
/// Affiche le detail d'un titre specifique.
/// </summary>
/// <param name="id">Identifiant du titre.</param>
/// <returns>Vue des details ou 404 si introuvable.</returns>
[HttpGet("{id}")]
public IActionResult Details(int id)
{
Details = new TitreContent
this.logger.LogInformation("Demande d'affichage du detail pour le titre ID {Id}.", id);
var titre = this.titreRepository.Find(id);
if (titre == null)
{
this.logger.LogWarning("Titre avec ID {Id} introuvable.", id);
return this.RedirectToAction("Index");
}
var vm = new TitreDetail
{
Details = new TitreContent
{
IdTitre = titre.IdTitre,
Libelle = titre.Libelle,
Chronique = titre.Chronique,
DateSortie = titre.DateSortie,
NbLikes = titre.NbLikes,
UrlJaquette = titre.UrlJaquette,
UrlEcoute = titre.UrlEcoute,
ArtisteNom = titre.Artiste.Nom,
Styles = titre.Styles,
Commentaires = titre.Commentaires,
},
CommentForm = new TitreComment
{
IdTitre = titre.IdTitre,
},
};
return this.View(vm);
}
/// <summary>
/// Affiche les titres correspondant a un style musical donne.
/// </summary>
/// <param name="style">Nom du style musical.</param>
/// <returns>Vue contenant la liste filtree.</returns>
[HttpGet("style/{style}")]
public IActionResult Style(string style)
{
this.logger.LogInformation("Recherche des titres pour le style : {Style}.", style);
var titresFiltres = this.titreRepository.SearchByStyle(style).ToList();
var vm = new TitreStyle
{
StyleName = style,
Titres = titresFiltres.Select(MapTitreItem).ToList(),
};
return this.View(vm);
}
/// <summary>
/// Ajoute un like a un titre.
/// </summary>
/// <param name="model">Modele contenant l'identifiant du titre.</param>
/// <returns>Redirection vers la page detail.</returns>
[HttpPost("like")]
public IActionResult Like(TitreLike model)
{
this.logger.LogInformation("Ajout d'un like pour le titre ID {Id}.", model.IdTitre);
var titre = this.titreRepository.Find(model.IdTitre);
if (titre == null)
{
this.logger.LogWarning("Impossible d'ajouter un like. Titre ID {Id} introuvable.", model.IdTitre);
return this.RedirectToAction("Index");
}
titre.NbLikes++;
return this.RedirectToAction("Details", new { id = model.IdTitre });
}
/// <summary>
/// Ajoute un commentaire a un titre.
/// </summary>
/// <param name="model">Donnees du commentaire.</param>
/// <returns>Redirection vers la page detail.</returns>
[HttpPost("comment")]
public IActionResult Comment(TitreComment model)
{
if (!this.ModelState.IsValid)
{
this.logger.LogWarning("Echec de validation du modele de commentaire pour le titre ID {Id}.", model.IdTitre);
return this.RedirectToAction("Details", new { id = model.IdTitre });
}
var titre = this.titreRepository.Find(model.IdTitre);
if (titre == null)
{
this.logger.LogWarning("Impossible d'ajouter le commentaire. Titre ID {Id} introuvable.", model.IdTitre);
return this.RedirectToAction("Index");
}
var commentaire = new Commentaire
{
Auteur = model.Auteur,
Contenu = model.Contenu,
DateCreation = DateTime.Now,
IdTitre = model.IdTitre,
};
titre.Commentaires.Add(commentaire);
this.logger.LogInformation("Commentaire ajoute avec succes au titre ID {Id}.", model.IdTitre);
return this.RedirectToAction("Details", new { id = model.IdTitre });
}
private static TitreStyleItem MapTitreItem(Titre titre)
{
return new TitreStyleItem
{
IdTitre = titre.IdTitre,
Libelle = titre.Libelle,
Chronique = titre.Chronique,
DateSortie = titre.DateSortie,
NbLikes = titre.NbLikes,
UrlJaquette = titre.UrlJaquette,
UrlEcoute = titre.UrlEcoute,
ArtisteNom = titre.Artiste?.Nom,
Styles = titre.Styles,
Commentaires = titre.Commentaires
},
CommentForm = new TitreComment
{
IdTitre = titre.IdTitre
}
};
return View(vm);
}
/// <summary>
/// Affiche les titres correspondant a un style musical donne.
/// </summary>
/// <param name="style">Nom du style musical.</param>
/// <returns>Vue contenant la liste filtree.</returns>
[HttpGet("style/{style}")]
public IActionResult Style(string style)
{
this._logger.LogInformation("Recherche des titres pour le style : {Style}.", style);
var titresFiltres = _titreRepository.SearchByStyle(style).ToList();
var vm = new TitreStyle
{
StyleName = style,
Titres = titresFiltres.Select(MapTitreItem).ToList()
};
return View(vm);
}
/// <summary>
/// Ajoute un like a un titre.
/// </summary>
/// <param name="model">Modele contenant l'identifiant du titre.</param>
/// <returns>Redirection vers la page detail.</returns>
[HttpPost("like")]
public IActionResult Like(TitreLike model)
{
this._logger.LogInformation("Ajout d'un like pour le titre ID {Id}.", model.IdTitre);
var titre = this._titreRepository.Find(model.IdTitre);
if (titre == null)
{
this._logger.LogWarning("Impossible d'ajouter un like. Titre ID {Id} introuvable.", model.IdTitre);
return RedirectToAction("Index");
UrlJaquette = titre.UrlJaquette,
Duree = titre.Duree,
};
}
titre.NbLikes++;
return RedirectToAction("Details", new { id = model.IdTitre });
}
/// <summary>
/// Ajoute un commentaire a un titre.
/// </summary>
/// <param name="model">Donnees du commentaire.</param>
/// <returns>Redirection vers la page detail.</returns>
[HttpPost("comment")]
public IActionResult Comment(TitreComment model)
{
if (!ModelState.IsValid)
{
this._logger.LogWarning("Echec de validation du modele de commentaire pour le titre ID {Id}.", model.IdTitre);
return RedirectToAction("Details", new { id = model.IdTitre });
}
var titre = this._titreRepository.Find(model.IdTitre);
if (titre == null)
{
this._logger.LogWarning("Impossible d'ajouter le commentaire. Titre ID {Id} introuvable.", model.IdTitre);
return RedirectToAction("Index");
}
var commentaire = new Commentaire
{
Auteur = model.Auteur,
Contenu = model.Contenu,
DateCreation = DateTime.Now,
IdTitre = model.IdTitre
};
titre.Commentaires.Add(commentaire);
this._logger.LogInformation("Commentaire ajoute avec succes au titre ID {Id}.", model.IdTitre);
return RedirectToAction("Details", new { id = model.IdTitre });
}
private static TitreStyleItem MapTitreItem(Titre titre)
{
return new TitreStyleItem
{
IdTitre = titre.IdTitre,
Libelle = titre.Libelle,
ArtisteNom = titre.Artiste?.Nom,
UrlJaquette = titre.UrlJaquette,
Duree = titre.Duree
};
}
}

View File

@@ -1,7 +1,9 @@
using Microsoft.EntityFrameworkCore;
using NLog;
using NLog.Web;
using Microsoft.EntityFrameworkCore;
using Webzine.EntitiesContext;
using Webzine.Entity;
using Webzine.Entity.Fixtures;
using Webzine.Repository;
using Webzine.Repository.Contracts;
@@ -21,8 +23,6 @@ try
// Necessite le package Nuget Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.
.AddRazorRuntimeCompilation();
builder.Services.AddSingleton<ITitreRepository, LocalEntityRepository>();
builder.Services.AddDbContext<WebzineDbContext>(options =>
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));
@@ -30,17 +30,79 @@ try
builder.Logging.ClearProviders();
builder.Host.UseNLog();
// En fonction de la configuration, utilise soit les repositories basés sur une base de données, soit les repositories basés sur des listes locales.
bool useDatabase = builder.Configuration.GetValue<bool>("UseDatabase");
if (useDatabase)
{
builder.Services.AddScoped<ITitreRepository, DbTitreRepository>();
builder.Services.AddScoped<IStyleRepository, DbStyleRepository>();
builder.Services.AddScoped<IArtisteRepository, DbArtisteRepository>();
//builder.Services.AddScoped<ICommentaireRepository, DbCommentaireRepository>();
builder.Services.AddScoped<DbEntityRepository>();
}
else
{
builder.Services.AddScoped<ITitreRepository, LocalTitreRepository>();
builder.Services.AddScoped<IStyleRepository, LocalStyleRepository>();
builder.Services.AddScoped<IArtisteRepository, LocalArtisteRepository>();
builder.Services.AddScoped<ICommentaireRepository, LocalCommentaireRepository>();
builder.Services.AddSingleton<InMemoryDataStore>();
}
// https://learn.microsoft.com/fr-fr/aspnet/core/performance/response-compression?view=aspnetcore-10.0#configuration
// Ajoute le service de compression des réponses HTTP pour réduire la taille des données envoyées au client et améliorer les performances de l'application.
builder.Services.AddResponseCompression();
var app = builder.Build();
if (useDatabase)
{
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<WebzineDbContext>();
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
var repo = scope.ServiceProvider.GetRequiredService<DbEntityRepository>();
repo.SeedBaseDeDonnees();
}
}
else
{
using (var scope = app.Services.CreateScope())
{
var store = scope.ServiceProvider.GetRequiredService<InMemoryDataStore>();
var artistes = SeedDataLocal.GenererListeArtiste(100);
var styles = SeedDataLocal.GenererListeStyle(15, 20);
var albums = SeedDataLocal.GenererListeAlbums(50);
var titres = SeedDataLocal.GenererListeTitre(500, artistes, styles, albums);
var commentaires = new List<Commentaire>();
foreach (var titre in titres)
{
commentaires.AddRange(SeedDataLocal.GenererListeCommentaire(titre, 0, 5));
}
store.Artistes.AddRange(artistes);
store.Styles = styles;
store.Titres = titres;
store.Commentaires.AddRange(commentaires);
}
}
app.UseResponseCompression();
// Active la possibilite de servir des fichiers statiques presents dans
// le dossier wwwroot.
app.UseStaticFiles();
using (var scope = app.Services.CreateScope())
app.UseStaticFiles(new StaticFileOptions
{
var db = scope.ServiceProvider.GetRequiredService<WebzineDbContext>();
db.Database.EnsureCreated();
}
OnPrepareResponse = ctx =>
{
// https://learn.microsoft.com/fr-fr/aspnet/core/fundamentals/static-files?view=aspnetcore-10.0#set-http-response-headers
ctx.Context.Response.Headers.Append("Cache-Control", "public, max-age=31536000");
},
});
// Active le middleware permettant le routage des requetes entrantes.
app.UseRouting();
@@ -60,12 +122,12 @@ try
}
catch (Exception exception)
{
// NLog: attrape les exceptions non gerees et les logge.
// NLog: attrape les exceptions non gerees et les logger.
logger.Error(exception, "Stopped program because of exception");
throw;
}
finally
{
// Assure que NLog flush tous les messages de log avant de fermer l'application.
NLog.LogManager.Shutdown();
LogManager.Shutdown();
}

View File

@@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Mvc;
using Webzine.Repository.Contracts;
namespace Webzine.WebApplication.ViewComponents
{
/// <summary>
/// View component pour la sidebar, récupère les styles depuis le repository.
/// </summary>
public class SidebarViewComponent : ViewComponent
{
private readonly IStyleRepository styleRepository;
/// <summary>
/// Initializes a new instance of the <see cref="SidebarViewComponent"/> class.
/// </summary>
/// <param name="styleRepository">Repository des styles injecté.</param>
public SidebarViewComponent(IStyleRepository styleRepository)
{
this.styleRepository = styleRepository;
}
/// <summary>
/// Récupère tous les styles triés par libellé et les passe à la vue.
/// </summary>
/// <returns>Une vue contenant la liste des styles.</returns>
public IViewComponentResult Invoke()
{
var styles = this.styleRepository.FindAll()
.OrderBy(s => s.Libelle)
.ToList();
return this.View(styles);
}
}
}

View File

@@ -1,18 +1,30 @@
namespace Webzine.WebApplication.ViewModels.Accueil
{
using Webzine.Entity;
/// <summary>
/// ViewModel pour la page d'accueil du webzine, affichant les derniers titres et les titres les plus populaires.
/// </summary>
public class AccueilIndexViewModel
{
/// <summary>
/// Définit la liste des derniers titres ajoutés au webzine.
/// Obtient ou définit la liste des derniers titres ajoutés au webzine.
/// </summary>
public List<Entity.Titre> DerniersTitres { get; set; } = [];
public List<Titre> DerniersTitres { get; set; } = new List<Titre>();
/// <summary>
/// Définit la liste des titres les plus populaires du webzine.
/// Obtient ou définit la liste des titres les plus populaires du webzine.
/// </summary>
public List<Entity.Titre> TopTitres { get; set; } = [];
public List<Titre> TopTitres { get; set; } = new List<Titre>();
/// <summary>
/// Obtient ou définit le nombre de titre disponible.
/// </summary>
// public int NombreDeTitre { get; set; } = 0;
/// <summary>
/// Obtient ou définit le nombre de titre paginé.
/// </summary>
// public int Pagination { get; set; } = 0;
}
}

View File

@@ -0,0 +1,30 @@
namespace Webzine.WebApplication.ViewModels.Artiste
{
using Webzine.Entity;
/// <summary>
/// ViewModel pour afficher les informations d'un artiste et ses titres groupés par album.
/// </summary>
public class ArtisteDetailsViewModel
{
/// <summary>
/// Obtient ou définit l'identifiant de l'artiste.
/// </summary>
public int IdArtiste { get; set; }
/// <summary>
/// Obtient ou définit le nom de l'artiste.
/// </summary>
public string Nom { get; set; } = string.Empty;
/// <summary>
/// Obtient ou définit la biographie de l'artiste.
/// </summary>
public string Biographie { get; set; } = string.Empty;
/// <summary>
/// Obtient ou définit défini la liste des titres de l'artiste groupés par nom d'Album.
/// </summary>
public IEnumerable<IGrouping<string?, Titre>> AlbumsGroupes { get; set; }
= Enumerable.Empty<IGrouping<string?, Titre>>();
}
}

View File

@@ -14,7 +14,8 @@
<div class="col-12 col-md-3 text-center mb-3 mb-md-0">
<img class="img-fluid img-thumbnail"
src="@titre.UrlJaquette"
alt="@titre.Libelle" />
alt="@titre.Libelle"
loading="lazy" />
</div>
<!-- Contenu -->
@@ -37,7 +38,7 @@
<!-- Chronique -->
<p class="mt-2 mb-3 text-muted">
@titre.Chronique
@(titre.Chronique.Length > 200 ? titre.Chronique.Substring(0, 200) + "..." : titre.Chronique)
</p>
<!-- Footer -->
@@ -82,11 +83,11 @@
{
<div class="col-12 col-md-6 col-lg-4">
<div class="card h-100">
<img class="card-img-top" src="@titre.UrlJaquette" alt="@titre.Album" />
<img class="card-img-top" src="@titre.UrlJaquette" alt="@titre.Album" loading="lazy" />
<div class="card-body">
<a asp-controller="Titre" asp-action="Details" asp-route-id="@titre.IdTitre" class="card-link">
@titre.Album
@titre.Libelle
</a>
<br />
par

View File

@@ -1,4 +1,4 @@
@model Webzine.Entity.Artiste
@model Webzine.WebApplication.ViewModels.Artiste.ArtisteDetailsViewModel;
@{
ViewData["Title"] = "Artiste";
@@ -8,22 +8,20 @@
<div class="container">
<h1>@Model.Nom</h1>
<hr class="mb-5" />
<hr/>
<p class="lead">@Model.Biographie</p>
<h2 class="mt-5 mb-4">Albums</h2>
<hr class="mb-5" />
<hr/>
@* On groupe les titres par nom d'album *@
@{
var albumsGroupes = Model.Titres
.OrderBy(t => t.Libelle) // Trie les titres par ordre alphabétique au sein de chaque groupe futur
.GroupBy(t => t.Album) // Groupe par nom d'album
.OrderBy(g => g.Key); // Trie les albums par ordre alphabétique (la clé du groupe)
}
@foreach (var groupe in albumsGroupes)
@if (!Model.AlbumsGroupes.Any())
{
<p>Cet artiste n'a pas encore de titres répertoriés.</p>
}
else
{
@foreach (var groupe in Model.AlbumsGroupes)
{
// On récupère le premier titre du groupe pour afficher l'image de l'album
var premierTitre = groupe.First();
@@ -32,7 +30,8 @@
<div class="col-md-3 mb-3">
<img src="@premierTitre.UrlJaquette"
class="img-fluid shadow-sm rounded border"
alt="Pochette de @groupe.Key" />
alt="Pochette de @groupe.Key"
loading="lazy" />
</div>
<div class="col-md-9">
@@ -59,7 +58,7 @@
<a asp-controller="Titre"
asp-action="Details"
asp-route-id="@titre.IdTitre"
class="text-primary fw-bold">
class="text-primary">
@titre.Libelle
</a>
</td>
@@ -70,4 +69,5 @@
</div>
</div>
}
}
</div>

View File

@@ -4,14 +4,15 @@
<div class="container">
<h1>Contact</h1>
<div>
<div class="my-2">
C.U.C.D.B - DIIAGE <br />
69 Avenue Aristide Briand<br />
21000 Dijon
</div>
<div>
<div class ="my-2">
<i class="fa-solid fa-phone"></i> Phone : 03 80 40 50 60<br />
<i class="fa-solid fa-envelope"></i> secretariat@cucdb.fr
<i class="fa-solid fa-envelope"></i> <span class="text-primary">secretariat@cucdb.fr</span>
</div>
</div>
@@ -21,43 +22,43 @@
<h2>Suivez-nous</h2>
<div class="row g-4 text-center">
<div class="col-md-4">
<a href="#" class="card h-100 p-4 shadow-sm border-0 bg-light-subtle">
<i class="fa-solid fa-link fa-3x text-primary mb-3"></i>
<a href="#" class="card h-100 p-4 border-0 bg-light">
<i class="fa-solid fa-link fa-3x text-primary mb-3 align-self-center"></i>
<div class="fw-bold text-primary">Site officiel du DIIAGE</div>
</a>
</div>
<div class="col-md-4">
<a href="#" class="card h-100 p-4 shadow-sm border-0 bg-light-subtle">
<i class="fa-brands fa-facebook fa-3x text-primary mb-3"></i>
<a href="#" class="card h-100 p-4 border-0 bg-light">
<i class="fa-brands fa-facebook fa-3x text-primary mb-3 align-self-center"></i>
<div class="fw-bold text-primary">Facebook</div>
</a>
</div>
<div class="col-md-4">
<a href="#" class="card h-100 p-4 shadow-sm border-0 bg-light-subtle">
<i class="fa-brands fa-instagram fa-3x text-primary mb-3"></i>
<a href="#" class="card h-100 p-4 border-0 bg-light">
<i class="fa-brands fa-instagram fa-3x text-primary mb-3 align-self-center"></i>
<div class="fw-bold text-primary">Instagram</div>
</a>
</div>
<div class="col-md-4">
<a href="#" class="card h-100 p-4 shadow-sm border-0 bg-light-subtle">
<i class="fa-brands fa-linkedin fa-3x text-primary mb-3"></i>
<a href="#" class="card h-100 p-4 border-0 bg-light">
<i class="fa-brands fa-linkedin fa-3x text-primary mb-3 align-self-center"></i>
<div class="fw-bold text-primary">LinkedIn</div>
</a>
</div>
<div class="col-md-4">
<a href="#" class="card h-100 p-4 shadow-sm border-0 bg-light-subtle">
<i class="fa-solid fa-map fa-3x text-primary mb-3"></i>
<a href="#" class="card h-100 p-4 border-0 bg-light">
<i class="fa-solid fa-map fa-3x text-primary mb-3 align-self-center"></i>
<div class="fw-bold text-primary">Google Maps</div>
</a>
</div>
<div class="col-md-4">
<a href="#" class="card h-100 p-4 shadow-sm border-0 bg-light-subtle">
<i class="fa-brands fa-twitter fa-3x text-primary mb-3"></i>
<a href="#" class="card h-100 p-4 border-0 bg-light">
<i class="fa-brands fa-twitter fa-3x text-primary mb-3 align-self-center"></i>
<div class="fw-bold text-primary">Twitter</div>
</a>
</div>

View File

@@ -53,7 +53,7 @@
asp-action="Details"
asp-route-id="@titre.IdTitre"
class="me-3 text-black">
<img src="@titre.UrlJaquette" alt="@titre.Libelle" width="70" height="70" class="object-fit-cover" />
<img src="@titre.UrlJaquette" alt="@titre.Libelle" width="70" height="70" class="object-fit-cover" loading="lazy"/>
</a>
<div class="justify-content-center d-flex flex-column">

View File

@@ -0,0 +1,23 @@
@model IEnumerable<Webzine.Entity.Style>
<aside class="col-lg-3 d-none d-lg-block">
<div>
<h2>À propos</h2>
<p>Retrouvez les dernières pépites sur notre webzine.</p>
</div>
<div>
<h2>Styles</h2>
<ul>
@foreach (var style in Model)
{
<li>
<a asp-controller="Titre"
asp-action="Style"
asp-route-style="@style.Libelle">
@style.Libelle
</a>
</li>
}
</ul>
</div>
</aside>

View File

@@ -9,22 +9,22 @@
<link rel="stylesheet" href="~/css/bootstrap.min.css">
<link rel="stylesheet" href="~/css/all.min.css">
<link rel="stylesheet" href="~/css/app.css">
<script src="~/js/bootstrap.bundle.js" defer></script>
</head>
<body>
<partial name="_Header"/>
<div class="container-fluid flex-grow-1 py-4">
<div class="row">
<main class="col mx-3">
@RenderBody()
</main>
@if(ViewContext.RouteData.Values["area"]?.ToString() != "Administration")
{
<partial name="_Sidebar" />
}
</div>
<partial name="_Header"/>
<div class="container-fluid flex-grow-1 py-4">
<div class="row">
<main class="col mx-3">
@RenderBody()
</main>
@if(ViewContext.RouteData.Values["area"]?.ToString() != "Administration")
{
@await Component.InvokeAsync("Sidebar")
}
</div>
<partial name="_Footer" />
</div>
<partial name="_Footer" />
</body>
</html>
<script src="~/js/bootstrap.bundle.min.js" defer></script>

View File

@@ -1,51 +0,0 @@
@*
For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*@
@{
}
<aside class="col-lg-3 d-none d-lg-block">
<div>
<h2>À propos</h2>
<p>Retrouvez les dernières pépites sur notre webzine.</p>
</div>
<div>
<h2>Styles</h2>
<ul>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Acid house">Acid house</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Ambient">Ambient</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Deep house">Deep house</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Disco">Disco</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Downtempo">Downtempo</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Drum n bass">Drum n bass</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Dub Techno">Dub Techno</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Electro">Electro</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Electronic">Electronic</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Experimental">Experimental</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Funk">Funk</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Garage">Garage</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Hardcore">Hardcore</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Hardstyle">Hardstyle</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Hip hop">Hip hop</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="House">House</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Indie">Indie</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Industrial">Industrial</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Jazz">Jazz</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Latin">Latin</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Metal">Metal</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Minimal">Minimal</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Pop">Pop</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Progressive">Progressive</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Punk">Punk</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="R&B">R&B</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Rap">Rap</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Reggae">Reggae</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Rock">Rock</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Soul">Soul</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Techno">Techno</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Trance">Trance</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="Trip hop">Trip hop</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="UK garage">UK garage</a></li>
<li><a asp-controller="Titre" asp-action="Style" asp-route-id="World">World</a></li>
</ul>
</div>
</aside>

View File

@@ -86,7 +86,9 @@
<div class="col-md-4 text-center">
<img src="@Model.Details.UrlJaquette"
class="img-fluid rounded shadow"
alt="Jaquette" />
alt="Jaquette"
loading="lazy"
fetchpriority="high" />
</div>
</div>
@@ -154,7 +156,7 @@
<h4 class="mb-4">Commentaires</h4>
@if (Model.Details.Commentaires.Any())
@if (Model.Details.Commentaires != null && Model.Details.Commentaires.Any())
{
foreach (var comment in Model.Details.Commentaires.OrderByDescending(c => c.DateCreation))
{

View File

@@ -29,7 +29,7 @@
<a asp-action="Details"
asp-route-id="@titre.IdTitre"
class="me-3 text-black">
<img src="@titre.UrlJaquette" alt="@titre.Libelle" width="70px" height="70px" class="object-fit-cover"/>
<img src="@titre.UrlJaquette" alt="@titre.Libelle" width="70px" height="70px" class="object-fit-cover" loading="lazy"/>
</a>
<!-- Infos -->

View File

@@ -18,8 +18,6 @@
<ItemGroup>
<Folder Include="Data\" />
<Folder Include="wwwroot\data\" />
<Folder Include="wwwroot\lib\" />
</ItemGroup>
<ItemGroup>

View File

@@ -6,9 +6,11 @@
}
},
"Webzine": {
// TODO : préciser les modes environnement et repo
"NombreDerniereChronique": 3,
"NombreDeTopTitres": 3
},
"UseDatabase": false,
"ConnectionStrings": {
"DefaultConnection": "Data Source=Data/webzine.sqlite"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long