diff --git a/.gitea/workflows/deploy-prod.yaml b/.gitea/workflows/deploy-prod.yaml
new file mode 100644
index 0000000..0bb9c60
--- /dev/null
+++ b/.gitea/workflows/deploy-prod.yaml
@@ -0,0 +1,26 @@
+name: Deploiement API Prod Docker
+
+on:
+ push:
+ branches:
+ - dev
+
+jobs:
+ deploy:
+ name: Build et Déploiement
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: 📥 Récupération du code source
+ uses: actions/checkout@v4
+
+ - name: 🔐 Injection des variables d'environnement
+ run: |
+ echo "PGSQL_CONNECTION=${{ secrets.PGSQL_CONNECTION }}" > .env
+
+ - name: 🐳 Redémarrage Docker
+ run: |
+ echo "🚀 Démarrage du déploiement Docker sur api-prod..."
+ docker compose down
+ docker compose up -d --build
+ echo "✅ Déploiement terminé !"
\ No newline at end of file
diff --git a/Webzine.WebApplication/Areas/Administration/ViewModels/DashboardViewModel.cs b/Webzine.Business.Contracts/Dto/DashboardDTO.cs
similarity index 90%
rename from Webzine.WebApplication/Areas/Administration/ViewModels/DashboardViewModel.cs
rename to Webzine.Business.Contracts/Dto/DashboardDTO.cs
index 106779d..9509c6c 100644
--- a/Webzine.WebApplication/Areas/Administration/ViewModels/DashboardViewModel.cs
+++ b/Webzine.Business.Contracts/Dto/DashboardDTO.cs
@@ -1,9 +1,9 @@
-namespace Webzine.WebApplication.Areas.Administration.ViewModels;
+namespace Webzine.Business.Contracts.Dto;
///
-/// ViewModel pour le tableau de bord de l'administration du webzine.
+/// DTO pour le tableau de bord de l'administration du webzine.
///
-public class DashboardViewModel
+public class DashboardDTO
{
///
/// Définit le nombre total d'artistes chroniqués dans le webzine.
diff --git a/Webzine.Business.Contracts/IDashboardService.cs b/Webzine.Business.Contracts/IDashboardService.cs
new file mode 100644
index 0000000..8bde456
--- /dev/null
+++ b/Webzine.Business.Contracts/IDashboardService.cs
@@ -0,0 +1,16 @@
+using Webzine.Business.Contracts.Dto;
+
+namespace Webzine.Business.Contracts;
+
+///
+/// Service responsable du calcul des statistiques affichées sur le tableau de bord d'administration.
+/// Agrège les données provenant de plusieurs repositories pour produire un résumé cohérent.
+///
+public interface IDashboardService
+{
+ ///
+ /// Calcule et retourne toutes les statistiques du tableau de bord en une seule passe.
+ ///
+ /// Un contenant les agrégats calculés.
+ DashboardDTO GetDashboardData();
+}
\ No newline at end of file
diff --git a/Webzine.Business/DashboardService.cs b/Webzine.Business/DashboardService.cs
new file mode 100644
index 0000000..d9e9812
--- /dev/null
+++ b/Webzine.Business/DashboardService.cs
@@ -0,0 +1,71 @@
+using Webzine.Business.Contracts;
+using Webzine.Entity;
+using Webzine.Repository.Contracts;
+
+namespace Webzine.Business;
+
+using Contracts.Dto;
+
+///
+/// Implémentation de .
+/// Orchestre plusieurs appels aux repositories pour produire les statistiques du tableau de bord.
+///
+public class DashboardService : IDashboardService
+{
+ private readonly IArtisteRepository artisteRepository;
+ private readonly ITitreRepository titreRepository;
+ private readonly IStyleRepository styleRepository;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Repository des artistes.
+ /// Repository des titres.
+ /// Repository des styles.
+ public DashboardService(
+ IArtisteRepository artisteRepository,
+ ITitreRepository titreRepository,
+ IStyleRepository styleRepository)
+ {
+ this.artisteRepository = artisteRepository;
+ this.titreRepository = titreRepository;
+ this.styleRepository = styleRepository;
+ }
+
+ ///
+ public DashboardDTO GetDashboardData()
+ {
+ IEnumerable titres = this.titreRepository.FindAll();
+
+ Artiste? artisteLePlusChronique = titres
+ .GroupBy(t => t.Artiste)
+ .OrderByDescending(g => g.Count())
+ .FirstOrDefault()
+ ?.Key;
+
+ Artiste? albumLePlusChronique = titres
+ .GroupBy(t => (t.Artiste, t.Album))
+ .GroupBy(g => g.Key.Artiste)
+ .OrderByDescending(g => g.Count())
+ .FirstOrDefault()
+ ?.Key;
+
+ Titre? musiqueLaPlusJouee = titres
+ .OrderByDescending(t => t.NbLectures)
+ .FirstOrDefault();
+
+ return new DashboardDTO
+ {
+ NombreArtistes = this.artisteRepository.Count(),
+ ArtisteLePlusChronique = artisteLePlusChronique.Nom,
+ AlbumLePlusChronique = albumLePlusChronique.Nom,
+ NombreBiographies = this.artisteRepository.Count(a => !string.IsNullOrEmpty(a.Biographie)),
+ IdMusiqueLaPlusJouee = musiqueLaPlusJouee.IdTitre,
+ MusiqueLaPlusJouee = musiqueLaPlusJouee.Libelle,
+ NombreTitres = this.titreRepository.Count(),
+ NombreGenres = this.styleRepository.Count(),
+ NombreLectures = titres.Sum(t => t.NbLectures),
+ NombreLikes = titres.Sum(t => t.NbLikes),
+ };
+ }
+}
\ No newline at end of file
diff --git a/Webzine.Repository.Contracts/IArtisteRepository.cs b/Webzine.Repository.Contracts/IArtisteRepository.cs
index bc13f76..ee170b5 100644
--- a/Webzine.Repository.Contracts/IArtisteRepository.cs
+++ b/Webzine.Repository.Contracts/IArtisteRepository.cs
@@ -51,5 +51,18 @@ namespace Webzine.Repository.Contracts
/// Nom de l'artiste.
/// IEnumarble. qui contient la chaine de caractere.
IEnumerable Search(string nom);
+
+ ///
+ /// Récupère le nombre total d'artistes dans la collection.
+ ///
+ /// Le nombre total d'artistes.
+ int Count();
+
+ ///
+ /// Récupère le nombre d'artistes correspondant au prédicat fourni.
+ ///
+ /// Le prédicat de filtrage.
+ /// Le nombre d'artistes correspondants.
+ int Count(Func predicate);
}
}
\ No newline at end of file
diff --git a/Webzine.Repository.Contracts/IStyleRepository.cs b/Webzine.Repository.Contracts/IStyleRepository.cs
index d260039..0ea9ba3 100644
--- a/Webzine.Repository.Contracts/IStyleRepository.cs
+++ b/Webzine.Repository.Contracts/IStyleRepository.cs
@@ -37,5 +37,11 @@ namespace Webzine.Repository.Contracts
///
/// L'objet style à mettre à jour.
+ /// Récupère le nombre total de styles dans la liste des styles.
+ ///
+ /// Le nombre total de styles présents dans la liste.
+ int Count();
}
}
\ No newline at end of file
diff --git a/Webzine.Repository/DbArtisteRepository.cs b/Webzine.Repository/DbArtisteRepository.cs
index fa6ff18..f6f868b 100644
--- a/Webzine.Repository/DbArtisteRepository.cs
+++ b/Webzine.Repository/DbArtisteRepository.cs
@@ -83,8 +83,8 @@ namespace Webzine.Repository
try
{
Artiste artiste = this.context.Artistes
- .Include(a => a.Titres)
- .First(a => a.IdArtiste == id);
+ .Include(a => a.Titres)
+ .FirstOrDefault(a => a.IdArtiste == id);
return artiste;
}
catch (Exception ex)
@@ -176,5 +176,37 @@ namespace Webzine.Repository
throw new Exception("Erreur lors de la recherche d'artiste {error}", ex);
}
}
+
+ ///
+ public int Count()
+ {
+ try
+ {
+ int count = Enumerable.Count(this.context.Artistes);
+ this.logger.LogDebug("Nombre total d'artistes dans la base: {Count}", count);
+ return count;
+ }
+ catch (Exception ex)
+ {
+ this.logger.LogError(ex, "Erreur lors du comptage des artistes.");
+ throw;
+ }
+ }
+
+ ///
+ public int Count(Func predicate)
+ {
+ try
+ {
+ int count = this.context.Artistes.Count(predicate);
+ this.logger.LogDebug("Nombre d'artistes (avec prédicat): {Count}", count);
+ return count;
+ }
+ catch (Exception ex)
+ {
+ this.logger.LogError(ex, "Erreur lors du comptage des artistes avec prédicat.");
+ throw;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Webzine.Repository/DbStyleRepository.cs b/Webzine.Repository/DbStyleRepository.cs
index c350a4f..a189ffd 100644
--- a/Webzine.Repository/DbStyleRepository.cs
+++ b/Webzine.Repository/DbStyleRepository.cs
@@ -178,4 +178,20 @@ public class DbStyleRepository : IStyleRepository
throw;
}
}
+
+ ///
+ public int Count()
+ {
+ try
+ {
+ int count = Enumerable.Count(this.context.Styles);
+ this.logger.LogDebug("Nombre total de styles: {Count}", count);
+ return count;
+ }
+ catch (Exception ex)
+ {
+ this.logger.LogError(ex, "Erreur lors du comptage des styles");
+ throw;
+ }
+ }
}
\ No newline at end of file
diff --git a/Webzine.Repository/DbTitreRepository.cs b/Webzine.Repository/DbTitreRepository.cs
index dad798d..1c199a1 100644
--- a/Webzine.Repository/DbTitreRepository.cs
+++ b/Webzine.Repository/DbTitreRepository.cs
@@ -245,15 +245,13 @@ public class DbTitreRepository : ITitreRepository
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);
+ .FirstOrDefault(t => t.IdTitre == idTitre);
- this.logger.LogDebug("Titre trouvé: {Libelle}", titre.Libelle);
return titre;
}
catch (InvalidOperationException ex)
diff --git a/Webzine.Repository/LocalArtisteRepository.cs b/Webzine.Repository/LocalArtisteRepository.cs
index 69021c5..0388da3 100644
--- a/Webzine.Repository/LocalArtisteRepository.cs
+++ b/Webzine.Repository/LocalArtisteRepository.cs
@@ -93,5 +93,17 @@ namespace Webzine.Repository
.Where(a => a.Nom.ToLower().Contains(mot.ToLower()))
.ToList();
}
+
+ ///
+ public int Count()
+ {
+ return this.dataStore.Artistes.Count;
+ }
+
+ ///
+ public int Count(Func predicate)
+ {
+ return this.dataStore.Artistes.Count(predicate);
+ }
}
}
\ No newline at end of file
diff --git a/Webzine.Repository/LocalStyleRepository.cs b/Webzine.Repository/LocalStyleRepository.cs
index 0b4d16c..e750ff2 100644
--- a/Webzine.Repository/LocalStyleRepository.cs
+++ b/Webzine.Repository/LocalStyleRepository.cs
@@ -56,4 +56,10 @@ public class LocalStyleRepository : IStyleRepository
{
throw new NotSupportedException("Mode local");
}
+
+ ///
+ public int Count()
+ {
+ return this.dataStore.Styles.Count;
+ }
}
\ No newline at end of file
diff --git a/Webzine.WebApplication/Areas/Administration/Controllers/ArtisteController.cs b/Webzine.WebApplication/Areas/Administration/Controllers/ArtisteController.cs
index fcaf920..e6ad7e5 100644
--- a/Webzine.WebApplication/Areas/Administration/Controllers/ArtisteController.cs
+++ b/Webzine.WebApplication/Areas/Administration/Controllers/ArtisteController.cs
@@ -50,14 +50,37 @@ public class ArtisteController : Controller
/// Redirection.
public IActionResult Create()
{
- var model = new AdminArtisteForm
+ return this.View();
+ }
+
+ ///
+ /// Formulaire de création d'un artiste.
+ ///
+ /// Paramètre nécessaire pour la création d'un artiste.
+ /// Redirection sur la page Index.
+ [HttpPost]
+ public IActionResult Create(ArtisteCreateViewModel model)
+ {
+ // vérifier si les données sont corrects.
+ if (!this.ModelState.IsValid)
{
- Id = 0,
- Nom = string.Empty,
- Biographie = string.Empty,
+ // Passer model en paramètre afin que
+ // l'utilisateur conserve sa saissie.
+ return this.View(model);
+ }
+
+ // Créer un objet Artiste avecc les paramètres.
+ var artiste = new Artiste
+ {
+ Nom = model.Nom,
+ Biographie = model.Biographie,
};
- return this.View(model);
+ // Persister les données.
+ this.artisteRepository.Add(artiste);
+
+ // Renvoyer sur la page Index.
+ return this.RedirectToAction("Index");
}
///
@@ -69,14 +92,37 @@ public class ArtisteController : Controller
{
var artiste = this.artisteRepository.Find(id);
- var model = new AdminArtisteForm
+ if (artiste == null)
{
- Id = artiste.IdArtiste,
- Nom = artiste.Nom,
- Biographie = artiste.Biographie,
+ return this.RedirectToAction("Index");
+ }
+
+ return this.View(artiste);
+ }
+
+ ///
+ /// Traitement du formulaire de modification d'un artiste.
+ ///
+ /// Paramètre d'un artiste.
+ /// Redirection sur Index.
+ [HttpPost]
+ public IActionResult Edit(ArtisteEditViewModel model)
+ {
+ var artiste = new Artiste
+ {
+ IdArtiste = model.Id,
+ Nom = model.Nom,
+ Biographie = model.Biographie,
};
- return this.View(model);
+ if (!this.ModelState.IsValid)
+ {
+ return this.View(artiste);
+ }
+
+ this.artisteRepository.Update(artiste);
+
+ return this.RedirectToAction("Index");
}
///
@@ -87,6 +133,12 @@ public class ArtisteController : Controller
public IActionResult Delete(int id)
{
var artiste = this.artisteRepository.Find(id);
+
+ if (artiste == null)
+ {
+ return this.RedirectToAction("Index");
+ }
+
var model = new AdminArtisteForm
{
Id = id,
diff --git a/Webzine.WebApplication/Areas/Administration/Controllers/CommentaireController.cs b/Webzine.WebApplication/Areas/Administration/Controllers/CommentaireController.cs
index c60e9bd..b954e14 100644
--- a/Webzine.WebApplication/Areas/Administration/Controllers/CommentaireController.cs
+++ b/Webzine.WebApplication/Areas/Administration/Controllers/CommentaireController.cs
@@ -53,6 +53,11 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers
{
var commentaire = this.commentaireRepository.Find(id);
+ if (commentaire == null)
+ {
+ return this.RedirectToAction("Index");
+ }
+
var model = new CommentaireDeleteViewModel
{
IdCommentaire = commentaire.IdCommentaire,
diff --git a/Webzine.WebApplication/Areas/Administration/Controllers/DashboardController.cs b/Webzine.WebApplication/Areas/Administration/Controllers/DashboardController.cs
index 2f314ad..97f1721 100644
--- a/Webzine.WebApplication/Areas/Administration/Controllers/DashboardController.cs
+++ b/Webzine.WebApplication/Areas/Administration/Controllers/DashboardController.cs
@@ -1,30 +1,26 @@
namespace Webzine.WebApplication.Areas.Administration.Controllers;
+using Webzine.Business.Contracts;
+using Webzine.Business.Contracts.Dto;
using Microsoft.AspNetCore.Mvc;
-
using Webzine.Repository.Contracts;
-using Webzine.WebApplication.Areas.Administration.ViewModels;
[Area("Administration")]
public class DashboardController : Controller
{
private readonly ILogger logger;
- private readonly IStyleRepository styleRepository;
- private readonly IArtisteRepository artisteRepository;
- private readonly ITitreRepository titreRepository;
+ private readonly IDashboardService dashboardService;
///
/// Initializes a new instance of the class.
/// Initialise une nouvelle instance de la classe .
///
/// Service de journalisation injecté.
- /// Repository des styles injecté.
- public DashboardController(ILogger logger, IStyleRepository styleRepository, IArtisteRepository artisteRepository, ITitreRepository titreRepository)
+ /// Service de calcul des statistiques du tableau de bord.
+ public DashboardController(ILogger logger, IDashboardService dashboardService)
{
this.logger = logger;
- this.styleRepository = styleRepository;
- this.artisteRepository = artisteRepository;
- this.titreRepository = titreRepository;
+ this.dashboardService = dashboardService;
this.logger.LogInformation("Initialisation du contrôleur TitreController.");
}
@@ -35,42 +31,8 @@ public class DashboardController : Controller
/// La vue Index du tableau de bord.
public IActionResult Index()
{
- var artisteLePlusChronique = this.titreRepository.FindAll()
- .GroupBy(t => t.Artiste)
- .OrderByDescending(g => g.Count())
- .First();
+ DashboardDTO data = dashboardService.GetDashboardData();
- var albumLePlusChronique = this.titreRepository.FindAll()
- .GroupBy(t => t.Artiste)
- .OrderByDescending(g => g.Select(t => t.Album).Distinct().Count())
- .First();
-
- var musiqueLaPlusJouee = this.titreRepository.FindAll()
- .OrderByDescending(t => t.NbLectures)
- .First();
-
- var model = new DashboardViewModel
- {
- NombreArtistes = this.artisteRepository.FindAll().Count(),
-
- ArtisteLePlusChronique = artisteLePlusChronique.Key.Nom,
-
- AlbumLePlusChronique = albumLePlusChronique.Key.Nom,
-
- NombreBiographies = this.artisteRepository.FindAll().Count(a => !string.IsNullOrEmpty(a.Biographie)),
-
- IdMusiqueLaPlusJouee = musiqueLaPlusJouee.IdTitre,
- MusiqueLaPlusJouee = musiqueLaPlusJouee.Libelle,
-
- NombreTitres = this.titreRepository.Count(),
-
- NombreGenres = this.styleRepository.FindAll().Count(),
-
- NombreLectures = this.titreRepository.FindAll().Sum(t => t.NbLectures),
-
- NombreLikes = this.titreRepository.FindAll().Sum(t => t.NbLikes),
- };
-
- return this.View(model);
+ return this.View(data);
}
}
\ No newline at end of file
diff --git a/Webzine.WebApplication/Areas/Administration/Controllers/StyleController.cs b/Webzine.WebApplication/Areas/Administration/Controllers/StyleController.cs
index e6741f9..1283fd0 100644
--- a/Webzine.WebApplication/Areas/Administration/Controllers/StyleController.cs
+++ b/Webzine.WebApplication/Areas/Administration/Controllers/StyleController.cs
@@ -1,109 +1,170 @@
-namespace Webzine.WebApplication.Areas.Administration.Controllers
-{
- using Microsoft.AspNetCore.Mvc;
+namespace Webzine.WebApplication.Areas.Administration.Controllers;
- using Webzine.Repository.Contracts;
- using Webzine.WebApplication.Areas.Administration.ViewModels.Style;
+using Microsoft.AspNetCore.Mvc;
+
+using Webzine.Entity;
+using Webzine.Repository.Contracts;
+using Webzine.WebApplication.Areas.Administration.ViewModels.Style;
+
+///
+/// Controleur pour la gestion des styles dans l'administration du webzine.
+///
+[Area("Administration")]
+public class StyleController : Controller
+{
+ private readonly ILogger logger;
+ private readonly IStyleRepository styleRepository;
///
- /// Contrôleur pour la gestion des styles dans l'administration du webzine.
+ /// Initializes a new instance of the class.
///
- [Area("Administration")]
- public class StyleController : Controller
+ /// Service de journalisation injecte.
+ /// Repository des styles injecte.
+ public StyleController(
+ ILogger logger,
+ IStyleRepository styleRepository)
{
- private readonly ILogger logger;
- private readonly IStyleRepository styleRepository;
+ this.logger = logger;
+ this.styleRepository = styleRepository;
- ///
- /// Initializes a new instance of the class.
- /// Initialise une nouvelle instance de la classe .
- ///
- /// Service de journalisation injecté.
- /// Repository des styles injecté.
- public StyleController(
- ILogger logger,
- IStyleRepository styleRepository)
+ this.logger.LogInformation("Initialisation du controleur StyleController.");
+ }
+
+ ///
+ /// Affiche la liste des styles dans la vue Index.
+ ///
+ /// La vue Index avec la liste des styles.
+ public IActionResult Index()
+ {
+ IEnumerable