From 143ba29d6047a8bcacc81fd809194d732ebc9a26 Mon Sep 17 00:00:00 2001 From: tleodev Date: Wed, 21 May 2025 19:58:16 +0200 Subject: [PATCH 01/11] =?UTF-8?q?=E2=9E=95=20Add=20CRUD=20operations=20on?= =?UTF-8?q?=20exercices=20template=20(catalog=20svc)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CatalogService/Controllers/ExercicesController.cs | 8 +++++++- src/CatalogService/DTOs/UpdateExerciceTemplateDto.cs | 3 --- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/CatalogService/Controllers/ExercicesController.cs b/src/CatalogService/Controllers/ExercicesController.cs index deb6456..5370499 100644 --- a/src/CatalogService/Controllers/ExercicesController.cs +++ b/src/CatalogService/Controllers/ExercicesController.cs @@ -2,6 +2,7 @@ using AutoMapper; using CatalogService.Data; using CatalogService.DTOs; using CatalogService.Entities; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Shared.DTOs; @@ -9,7 +10,8 @@ using Shared.DTOs; namespace CatalogService.Controllers; [ApiController] -[Route("api/[controller]")] +[Authorize] +[Route("api/catalog/[controller]")] public class ExercicesController : ControllerBase { private readonly CatalogDbContext _context; @@ -22,6 +24,7 @@ public class ExercicesController : ControllerBase } [HttpPost] + [AllowAnonymous] public async Task Create([FromBody] CreateExerciceTemplateDto dto) { var exercice = _mapper.Map(dto); @@ -31,6 +34,7 @@ public class ExercicesController : ControllerBase } [HttpPut("{id}")] + [AllowAnonymous] public async Task Update(string id, [FromBody] UpdateExerciceTemplateDto dto) { var exercice = await _context.Exercices.FindAsync(id); @@ -43,6 +47,7 @@ public class ExercicesController : ControllerBase } [HttpDelete("{id}")] + [AllowAnonymous] public async Task Delete(string id) { var exercice = await _context.Exercices.FindAsync(id); @@ -54,6 +59,7 @@ public class ExercicesController : ControllerBase } [HttpGet("{id}")] + [AllowAnonymous] public async Task> GetById(string id) { var exercice = await _context.Exercices.FindAsync(id); diff --git a/src/CatalogService/DTOs/UpdateExerciceTemplateDto.cs b/src/CatalogService/DTOs/UpdateExerciceTemplateDto.cs index e19a600..0031442 100644 --- a/src/CatalogService/DTOs/UpdateExerciceTemplateDto.cs +++ b/src/CatalogService/DTOs/UpdateExerciceTemplateDto.cs @@ -5,9 +5,6 @@ namespace CatalogService.DTOs; public class UpdateExerciceTemplateDto { - [Required] - public required string Id { get; set; } - public string? Name { get; set; } public string? Description { get; set; } From 2c791baa0671f83cf9eca417b3bf7d4911094492 Mon Sep 17 00:00:00 2001 From: tleodev Date: Thu, 22 May 2025 09:26:06 +0200 Subject: [PATCH 02/11] =?UTF-8?q?=E2=9C=A8=20Introduice=20TrainingSVC=20+?= =?UTF-8?q?=20DataEntities=20relations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OptifitWebServices.sln | 7 ++++ src/Shared/Enum/EDifficulty.cs | 10 ++++++ src/Shared/Enum/EGoal.cs | 10 ++++++ src/TrainingSvc/Data/TrainingDbContext.cs | 11 +++++++ src/TrainingSvc/Entities/ExerciceInstance.cs | 28 ++++++++++++++++ src/TrainingSvc/Entities/Session.cs | 24 ++++++++++++++ src/TrainingSvc/Entities/TrainingProgram.cs | 26 +++++++++++++++ src/TrainingSvc/Program.cs | 33 +++++++++++++++++++ .../Properties/launchSettings.json | 15 +++++++++ src/TrainingSvc/TrainingSvc.csproj | 22 +++++++++++++ src/TrainingSvc/TrainingSvc.http | 6 ++++ 11 files changed, 192 insertions(+) create mode 100644 src/Shared/Enum/EDifficulty.cs create mode 100644 src/Shared/Enum/EGoal.cs create mode 100644 src/TrainingSvc/Data/TrainingDbContext.cs create mode 100644 src/TrainingSvc/Entities/ExerciceInstance.cs create mode 100644 src/TrainingSvc/Entities/Session.cs create mode 100644 src/TrainingSvc/Entities/TrainingProgram.cs create mode 100644 src/TrainingSvc/Program.cs create mode 100644 src/TrainingSvc/Properties/launchSettings.json create mode 100644 src/TrainingSvc/TrainingSvc.csproj create mode 100644 src/TrainingSvc/TrainingSvc.http diff --git a/OptifitWebServices.sln b/OptifitWebServices.sln index e0fc7d6..9888887 100644 --- a/OptifitWebServices.sln +++ b/OptifitWebServices.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalogService", "src\Catal EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared", "src\Shared\Shared.csproj", "{BF49B348-4188-4AC7-9ED4-5837F4B3BCD2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrainingSvc", "src\TrainingSvc\TrainingSvc.csproj", "{F16873D3-6511-48AA-B74E-8B13D5F20F20}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -26,9 +28,14 @@ Global {BF49B348-4188-4AC7-9ED4-5837F4B3BCD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {BF49B348-4188-4AC7-9ED4-5837F4B3BCD2}.Release|Any CPU.ActiveCfg = Release|Any CPU {BF49B348-4188-4AC7-9ED4-5837F4B3BCD2}.Release|Any CPU.Build.0 = Release|Any CPU + {F16873D3-6511-48AA-B74E-8B13D5F20F20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F16873D3-6511-48AA-B74E-8B13D5F20F20}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F16873D3-6511-48AA-B74E-8B13D5F20F20}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F16873D3-6511-48AA-B74E-8B13D5F20F20}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {54BE8DE8-08BD-429F-BCCA-3363A879D922} = {2A7200CA-F40B-4715-8726-4ED30C785FA4} {BF49B348-4188-4AC7-9ED4-5837F4B3BCD2} = {2A7200CA-F40B-4715-8726-4ED30C785FA4} + {F16873D3-6511-48AA-B74E-8B13D5F20F20} = {2A7200CA-F40B-4715-8726-4ED30C785FA4} EndGlobalSection EndGlobal diff --git a/src/Shared/Enum/EDifficulty.cs b/src/Shared/Enum/EDifficulty.cs new file mode 100644 index 0000000..c6b3ace --- /dev/null +++ b/src/Shared/Enum/EDifficulty.cs @@ -0,0 +1,10 @@ +namespace Shared.Enum; + +public enum EDifficulty +{ + None, + Beginner, + Intermediate, + Advanced, + Expert +} \ No newline at end of file diff --git a/src/Shared/Enum/EGoal.cs b/src/Shared/Enum/EGoal.cs new file mode 100644 index 0000000..64b558f --- /dev/null +++ b/src/Shared/Enum/EGoal.cs @@ -0,0 +1,10 @@ +namespace Shared.Enum; + +public enum EGoal +{ + None, + MuscleGain, + WeightLoss, + Endurance, + Strength, +} \ No newline at end of file diff --git a/src/TrainingSvc/Data/TrainingDbContext.cs b/src/TrainingSvc/Data/TrainingDbContext.cs new file mode 100644 index 0000000..82d9f9f --- /dev/null +++ b/src/TrainingSvc/Data/TrainingDbContext.cs @@ -0,0 +1,11 @@ +using Microsoft.EntityFrameworkCore; + +namespace TrainingSvc.Data; + +public class TrainingDbContext : DbContext +{ + public TrainingDbContext(DbContextOptions options) : base(options) + { + } + +} \ No newline at end of file diff --git a/src/TrainingSvc/Entities/ExerciceInstance.cs b/src/TrainingSvc/Entities/ExerciceInstance.cs new file mode 100644 index 0000000..e966b07 --- /dev/null +++ b/src/TrainingSvc/Entities/ExerciceInstance.cs @@ -0,0 +1,28 @@ +using System.ComponentModel.DataAnnotations; +using Shared.Entities; + +namespace TrainingSvc.Entities; + +public class ExerciceInstance : EntityBase +{ + public required string Name { get; set; } + + public string? ExerciceId { get; set; } + + public float Duration { get; set; } + + public int NbSets { get; set; } + + public int NbReps { get; set; } + + public float RestingTime { get; set; } + + public float? Weight { get; set; } + + public bool IsDone { get; set; } + + [Required] + public string SessionId { get; set; } + + public Session Session { get; set; } +} \ No newline at end of file diff --git a/src/TrainingSvc/Entities/Session.cs b/src/TrainingSvc/Entities/Session.cs new file mode 100644 index 0000000..5c6806a --- /dev/null +++ b/src/TrainingSvc/Entities/Session.cs @@ -0,0 +1,24 @@ +using System.Collections.ObjectModel; +using System.ComponentModel.DataAnnotations; +using Shared.Entities; +using Shared.Enum; + +namespace TrainingSvc.Entities; + +public class Session : EntityBase +{ + public required string Name { get; set; } + + public string? Description { get; set; } + + [Range(1, 7)] + public int Day { get; set; } + + public ETarget? Target { get; set; } + + [Required] + public string TrainingProgramId { get; set; } + public TrainingProgram TrainingProgram { get; set; } + + public virtual ICollection Exercices { get; set; } = new Collection(); +} \ No newline at end of file diff --git a/src/TrainingSvc/Entities/TrainingProgram.cs b/src/TrainingSvc/Entities/TrainingProgram.cs new file mode 100644 index 0000000..c48c1ed --- /dev/null +++ b/src/TrainingSvc/Entities/TrainingProgram.cs @@ -0,0 +1,26 @@ +using System.Collections.ObjectModel; +using Shared.Entities; +using Shared.Enum; + +namespace TrainingSvc.Entities; + +public class TrainingProgram : EntityBase +{ + public string Lang { get; set; } = "en"; + + public required string Name { get; set; } + + public string? Description { get; set; } + + public int WeekDuration { get; set; } = 4; + + public int NbDays { get; set; } = 3; + + public string OwnerId { get; set; } = ""; + + public EGoal Goal { get; set; } = EGoal.MuscleGain; + + public EDifficulty Difficulty { get; set; } = EDifficulty.Beginner; + + public virtual ICollection Sessions { get; set; } = new Collection(); +} \ No newline at end of file diff --git a/src/TrainingSvc/Program.cs b/src/TrainingSvc/Program.cs new file mode 100644 index 0000000..4cb66c9 --- /dev/null +++ b/src/TrainingSvc/Program.cs @@ -0,0 +1,33 @@ +using TrainingSvc.Data; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddControllers(); +builder.Services.AddDbContext(opt => +{ + opt.UseNpgsql(builder.Configuration.GetConnectionString("TrainingDb")); + +}); +builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); + + +var app = builder.Build(); + +app.UseAuthorization(); + +app.MapControllers(); + +try +{ + //NOT IMPLEMENTED + app.MapGet("/", () => "Hello World!"); + //DbInitializer.InitDb(app); +} +catch (Exception e) +{ + Console.WriteLine(e); +} + +app.Run(); \ No newline at end of file diff --git a/src/TrainingSvc/Properties/launchSettings.json b/src/TrainingSvc/Properties/launchSettings.json new file mode 100644 index 0000000..a913d38 --- /dev/null +++ b/src/TrainingSvc/Properties/launchSettings.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:7002", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} + diff --git a/src/TrainingSvc/TrainingSvc.csproj b/src/TrainingSvc/TrainingSvc.csproj new file mode 100644 index 0000000..9e84a87 --- /dev/null +++ b/src/TrainingSvc/TrainingSvc.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/src/TrainingSvc/TrainingSvc.http b/src/TrainingSvc/TrainingSvc.http new file mode 100644 index 0000000..033abd4 --- /dev/null +++ b/src/TrainingSvc/TrainingSvc.http @@ -0,0 +1,6 @@ +@TrainingSvc_HostAddress = http://localhost:5269 + +GET {{TrainingSvc_HostAddress}}/weatherforecast/ +Accept: application/json + +### From f45ba725691925a841522629899c50796c219569 Mon Sep 17 00:00:00 2001 From: tleodev Date: Sat, 7 Jun 2025 15:51:38 +0200 Subject: [PATCH 03/11] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F=20Initialize=20Db?= =?UTF-8?q?=20migration=20+=20DbInitializer=20(TrainingDb)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CatalogService/CatalogService.csproj | 4 + .../Controllers/ExercicesController.cs | 2 +- src/CatalogService/Data/CatalogDbContext.cs | 2 +- src/CatalogService/Data/DbInitializer.cs | 12 +- .../RequestHelpers/MappingProfiles.cs | 8 +- .../Entities/ExerciceTemplate.cs} | 2 +- .../20250607133126_InitialCreate.Designer.cs | 204 ++++++++++++++++++ .../20250607133126_InitialCreate.cs | 126 +++++++++++ .../TrainingDbContextModelSnapshot.cs | 201 +++++++++++++++++ src/TrainingSvc/Data/TrainingDbContext.cs | 10 + src/TrainingSvc/Data/TrainingDbInitializer.cs | 76 +++++++ src/TrainingSvc/Entities/ExerciceInstance.cs | 2 +- src/TrainingSvc/Program.cs | 8 +- src/TrainingSvc/TrainingSvc.csproj | 4 + 14 files changed, 643 insertions(+), 18 deletions(-) rename src/{CatalogService/Entities/Exercice.cs => Shared/Entities/ExerciceTemplate.cs} (92%) create mode 100644 src/TrainingSvc/Data/Migrations/20250607133126_InitialCreate.Designer.cs create mode 100644 src/TrainingSvc/Data/Migrations/20250607133126_InitialCreate.cs create mode 100644 src/TrainingSvc/Data/Migrations/TrainingDbContextModelSnapshot.cs create mode 100644 src/TrainingSvc/Data/TrainingDbInitializer.cs diff --git a/src/CatalogService/CatalogService.csproj b/src/CatalogService/CatalogService.csproj index c36fcf9..3c55125 100644 --- a/src/CatalogService/CatalogService.csproj +++ b/src/CatalogService/CatalogService.csproj @@ -20,4 +20,8 @@ + + + + \ No newline at end of file diff --git a/src/CatalogService/Controllers/ExercicesController.cs b/src/CatalogService/Controllers/ExercicesController.cs index 5370499..8fb59c5 100644 --- a/src/CatalogService/Controllers/ExercicesController.cs +++ b/src/CatalogService/Controllers/ExercicesController.cs @@ -27,7 +27,7 @@ public class ExercicesController : ControllerBase [AllowAnonymous] public async Task Create([FromBody] CreateExerciceTemplateDto dto) { - var exercice = _mapper.Map(dto); + var exercice = _mapper.Map(dto); _context.Exercices.Add(exercice); await _context.SaveChangesAsync(); return CreatedAtAction(nameof(GetById), new { id = exercice.Id }, _mapper.Map(exercice)); diff --git a/src/CatalogService/Data/CatalogDbContext.cs b/src/CatalogService/Data/CatalogDbContext.cs index f870489..ba8bfc1 100644 --- a/src/CatalogService/Data/CatalogDbContext.cs +++ b/src/CatalogService/Data/CatalogDbContext.cs @@ -9,5 +9,5 @@ public class CatalogDbContext : DbContext { } - public DbSet Exercices { get; set; } + public DbSet Exercices { get; set; } } \ No newline at end of file diff --git a/src/CatalogService/Data/DbInitializer.cs b/src/CatalogService/Data/DbInitializer.cs index 65cea6b..7fd27b5 100644 --- a/src/CatalogService/Data/DbInitializer.cs +++ b/src/CatalogService/Data/DbInitializer.cs @@ -22,9 +22,9 @@ public class DbInitializer return; } - var exercices = new List() + var exercices = new List() { - new Exercice + new ExerciceTemplate { Id = Guid.NewGuid().ToString(), Name = "Squat", @@ -33,7 +33,7 @@ public class DbInitializer ImageUrl = "images/squat.jpg", VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", }, - new Exercice + new ExerciceTemplate { Id = Guid.NewGuid().ToString(), Name = "Bench Press", @@ -42,7 +42,7 @@ public class DbInitializer ImageUrl = "images/bench_press.jpg", VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", }, - new Exercice + new ExerciceTemplate { Id = Guid.NewGuid().ToString(), Name = "Deadlift", @@ -51,7 +51,7 @@ public class DbInitializer ImageUrl = "images/deadlift.jpg", VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", }, - new Exercice + new ExerciceTemplate { Id = Guid.NewGuid().ToString(), Name = "Shoulder Press", @@ -60,7 +60,7 @@ public class DbInitializer ImageUrl = "images/shoulder_press.jpg", VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", }, - new Exercice + new ExerciceTemplate { Id = Guid.NewGuid().ToString(), Name = "Running on Treadmill", diff --git a/src/CatalogService/RequestHelpers/MappingProfiles.cs b/src/CatalogService/RequestHelpers/MappingProfiles.cs index a1f98bb..c41fe6f 100644 --- a/src/CatalogService/RequestHelpers/MappingProfiles.cs +++ b/src/CatalogService/RequestHelpers/MappingProfiles.cs @@ -8,9 +8,9 @@ public class MappingProfiles : Profile { public MappingProfiles() { - CreateMap(); - CreateMap(); - CreateMap(); - CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); } } \ No newline at end of file diff --git a/src/CatalogService/Entities/Exercice.cs b/src/Shared/Entities/ExerciceTemplate.cs similarity index 92% rename from src/CatalogService/Entities/Exercice.cs rename to src/Shared/Entities/ExerciceTemplate.cs index 99ed2fc..97a52db 100644 --- a/src/CatalogService/Entities/Exercice.cs +++ b/src/Shared/Entities/ExerciceTemplate.cs @@ -3,7 +3,7 @@ using Shared.Enum; namespace CatalogService.Entities; -public class Exercice : EntityBase +public class ExerciceTemplate : EntityBase { public string Name { get; set; } diff --git a/src/TrainingSvc/Data/Migrations/20250607133126_InitialCreate.Designer.cs b/src/TrainingSvc/Data/Migrations/20250607133126_InitialCreate.Designer.cs new file mode 100644 index 0000000..15d7ee4 --- /dev/null +++ b/src/TrainingSvc/Data/Migrations/20250607133126_InitialCreate.Designer.cs @@ -0,0 +1,204 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using TrainingSvc.Data; + +#nullable disable + +namespace TrainingSvc.Data.Migrations +{ + [DbContext(typeof(TrainingDbContext))] + [Migration("20250607133126_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.15") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("CatalogService.Entities.ExerciceTemplate", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ImageUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Target") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VideoUrl") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("ExerciceTemplates"); + }); + + modelBuilder.Entity("TrainingSvc.Entities.ExerciceInstance", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Duration") + .HasColumnType("real"); + + b.Property("ExerciceTemplateId") + .HasColumnType("text"); + + b.Property("IsDone") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NbReps") + .HasColumnType("integer"); + + b.Property("NbSets") + .HasColumnType("integer"); + + b.Property("RestingTime") + .HasColumnType("real"); + + b.Property("SessionId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Weight") + .HasColumnType("real"); + + b.HasKey("Id"); + + b.HasIndex("SessionId"); + + b.ToTable("ExerciceInstances"); + }); + + modelBuilder.Entity("TrainingSvc.Entities.Session", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Day") + .HasColumnType("integer"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Target") + .HasColumnType("integer"); + + b.Property("TrainingProgramId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TrainingProgramId"); + + b.ToTable("Sessions"); + }); + + modelBuilder.Entity("TrainingSvc.Entities.TrainingProgram", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Difficulty") + .HasColumnType("integer"); + + b.Property("Goal") + .HasColumnType("integer"); + + b.Property("Lang") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NbDays") + .HasColumnType("integer"); + + b.Property("OwnerId") + .IsRequired() + .HasColumnType("text"); + + b.Property("WeekDuration") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("TrainingPrograms"); + }); + + modelBuilder.Entity("TrainingSvc.Entities.ExerciceInstance", b => + { + b.HasOne("TrainingSvc.Entities.Session", "Session") + .WithMany("Exercices") + .HasForeignKey("SessionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Session"); + }); + + modelBuilder.Entity("TrainingSvc.Entities.Session", b => + { + b.HasOne("TrainingSvc.Entities.TrainingProgram", "TrainingProgram") + .WithMany("Sessions") + .HasForeignKey("TrainingProgramId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TrainingProgram"); + }); + + modelBuilder.Entity("TrainingSvc.Entities.Session", b => + { + b.Navigation("Exercices"); + }); + + modelBuilder.Entity("TrainingSvc.Entities.TrainingProgram", b => + { + b.Navigation("Sessions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/TrainingSvc/Data/Migrations/20250607133126_InitialCreate.cs b/src/TrainingSvc/Data/Migrations/20250607133126_InitialCreate.cs new file mode 100644 index 0000000..b09c24c --- /dev/null +++ b/src/TrainingSvc/Data/Migrations/20250607133126_InitialCreate.cs @@ -0,0 +1,126 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TrainingSvc.Data.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ExerciceTemplates", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + Description = table.Column(type: "text", nullable: false), + Target = table.Column(type: "integer", nullable: false), + ImageUrl = table.Column(type: "text", nullable: false), + VideoUrl = table.Column(type: "text", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ExerciceTemplates", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "TrainingPrograms", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Lang = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + Description = table.Column(type: "text", nullable: true), + WeekDuration = table.Column(type: "integer", nullable: false), + NbDays = table.Column(type: "integer", nullable: false), + OwnerId = table.Column(type: "text", nullable: false), + Goal = table.Column(type: "integer", nullable: false), + Difficulty = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TrainingPrograms", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Sessions", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + Description = table.Column(type: "text", nullable: true), + Day = table.Column(type: "integer", nullable: false), + Target = table.Column(type: "integer", nullable: true), + TrainingProgramId = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Sessions", x => x.Id); + table.ForeignKey( + name: "FK_Sessions_TrainingPrograms_TrainingProgramId", + column: x => x.TrainingProgramId, + principalTable: "TrainingPrograms", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ExerciceInstances", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + ExerciceTemplateId = table.Column(type: "text", nullable: true), + Duration = table.Column(type: "real", nullable: false), + NbSets = table.Column(type: "integer", nullable: false), + NbReps = table.Column(type: "integer", nullable: false), + RestingTime = table.Column(type: "real", nullable: false), + Weight = table.Column(type: "real", nullable: true), + IsDone = table.Column(type: "boolean", nullable: false), + SessionId = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ExerciceInstances", x => x.Id); + table.ForeignKey( + name: "FK_ExerciceInstances_Sessions_SessionId", + column: x => x.SessionId, + principalTable: "Sessions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ExerciceInstances_SessionId", + table: "ExerciceInstances", + column: "SessionId"); + + migrationBuilder.CreateIndex( + name: "IX_Sessions_TrainingProgramId", + table: "Sessions", + column: "TrainingProgramId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ExerciceInstances"); + + migrationBuilder.DropTable( + name: "ExerciceTemplates"); + + migrationBuilder.DropTable( + name: "Sessions"); + + migrationBuilder.DropTable( + name: "TrainingPrograms"); + } + } +} diff --git a/src/TrainingSvc/Data/Migrations/TrainingDbContextModelSnapshot.cs b/src/TrainingSvc/Data/Migrations/TrainingDbContextModelSnapshot.cs new file mode 100644 index 0000000..352c7ce --- /dev/null +++ b/src/TrainingSvc/Data/Migrations/TrainingDbContextModelSnapshot.cs @@ -0,0 +1,201 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using TrainingSvc.Data; + +#nullable disable + +namespace TrainingSvc.Data.Migrations +{ + [DbContext(typeof(TrainingDbContext))] + partial class TrainingDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.15") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("CatalogService.Entities.ExerciceTemplate", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ImageUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Target") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VideoUrl") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("ExerciceTemplates"); + }); + + modelBuilder.Entity("TrainingSvc.Entities.ExerciceInstance", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Duration") + .HasColumnType("real"); + + b.Property("ExerciceTemplateId") + .HasColumnType("text"); + + b.Property("IsDone") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NbReps") + .HasColumnType("integer"); + + b.Property("NbSets") + .HasColumnType("integer"); + + b.Property("RestingTime") + .HasColumnType("real"); + + b.Property("SessionId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Weight") + .HasColumnType("real"); + + b.HasKey("Id"); + + b.HasIndex("SessionId"); + + b.ToTable("ExerciceInstances"); + }); + + modelBuilder.Entity("TrainingSvc.Entities.Session", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Day") + .HasColumnType("integer"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Target") + .HasColumnType("integer"); + + b.Property("TrainingProgramId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TrainingProgramId"); + + b.ToTable("Sessions"); + }); + + modelBuilder.Entity("TrainingSvc.Entities.TrainingProgram", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Difficulty") + .HasColumnType("integer"); + + b.Property("Goal") + .HasColumnType("integer"); + + b.Property("Lang") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NbDays") + .HasColumnType("integer"); + + b.Property("OwnerId") + .IsRequired() + .HasColumnType("text"); + + b.Property("WeekDuration") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("TrainingPrograms"); + }); + + modelBuilder.Entity("TrainingSvc.Entities.ExerciceInstance", b => + { + b.HasOne("TrainingSvc.Entities.Session", "Session") + .WithMany("Exercices") + .HasForeignKey("SessionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Session"); + }); + + modelBuilder.Entity("TrainingSvc.Entities.Session", b => + { + b.HasOne("TrainingSvc.Entities.TrainingProgram", "TrainingProgram") + .WithMany("Sessions") + .HasForeignKey("TrainingProgramId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TrainingProgram"); + }); + + modelBuilder.Entity("TrainingSvc.Entities.Session", b => + { + b.Navigation("Exercices"); + }); + + modelBuilder.Entity("TrainingSvc.Entities.TrainingProgram", b => + { + b.Navigation("Sessions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/TrainingSvc/Data/TrainingDbContext.cs b/src/TrainingSvc/Data/TrainingDbContext.cs index 82d9f9f..5d047bf 100644 --- a/src/TrainingSvc/Data/TrainingDbContext.cs +++ b/src/TrainingSvc/Data/TrainingDbContext.cs @@ -1,4 +1,6 @@ +using CatalogService.Entities; using Microsoft.EntityFrameworkCore; +using TrainingSvc.Entities; namespace TrainingSvc.Data; @@ -8,4 +10,12 @@ public class TrainingDbContext : DbContext { } + public DbSet ExerciceTemplates { get; set; } + + public DbSet ExerciceInstances { get; set; } + + public DbSet Sessions { get; set; } + + public DbSet TrainingPrograms { get; set; } + } \ No newline at end of file diff --git a/src/TrainingSvc/Data/TrainingDbInitializer.cs b/src/TrainingSvc/Data/TrainingDbInitializer.cs new file mode 100644 index 0000000..1a958bc --- /dev/null +++ b/src/TrainingSvc/Data/TrainingDbInitializer.cs @@ -0,0 +1,76 @@ +namespace TrainingSvc.Data; +using CatalogService.Entities; +using Microsoft.EntityFrameworkCore; +using Shared.Enum; + +public class TrainingDbInitializer +{ + public static void InitDb(WebApplication app) + { + using var scope = app.Services.CreateScope(); + SeedData(scope.ServiceProvider.GetService()); + } + + private static void SeedData(TrainingDbContext context) + { + context.Database.Migrate(); + + if (context.ExerciceTemplates.Any()) + { + Console.WriteLine("Already have data in the database"); + return; + } + + var exercices = new List() + { + new ExerciceTemplate + { + Id = Guid.NewGuid().ToString(), + Name = "Squat", + Description = "Squat is a compound exercise that targets the lower body, primarily the quadriceps, hamstrings, and glutes.", + Target = ETarget.Legs, + ImageUrl = "images/squat.jpg", + VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", + }, + new ExerciceTemplate + { + Id = Guid.NewGuid().ToString(), + Name = "Bench Press", + Description = "Bench Press is a compound exercise that primarily targets the chest, shoulders, and triceps.", + Target = ETarget.Chest, + ImageUrl = "images/bench_press.jpg", + VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", + }, + new ExerciceTemplate + { + Id = Guid.NewGuid().ToString(), + Name = "Deadlift", + Description = "Deadlift is a compound exercise that primarily targets the back, glutes, and hamstrings.", + Target = ETarget.Back, + ImageUrl = "images/deadlift.jpg", + VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", + }, + new ExerciceTemplate + { + Id = Guid.NewGuid().ToString(), + Name = "Shoulder Press", + Description = "Shoulder Press is a compound exercise that primarily targets the shoulders and triceps.", + Target = ETarget.Arms, + ImageUrl = "images/shoulder_press.jpg", + VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", + }, + new ExerciceTemplate + { + Id = Guid.NewGuid().ToString(), + Name = "Running on Treadmill", + Description = "Running on Treadmill is a cardiovascular exercise that primarily targets the legs and improves overall fitness.", + Target = ETarget.Cardio, + ImageUrl = "images/running_treadmill.jpg", + VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", + }, + }; + context.AddRange(exercices); + + context.SaveChanges(); + } +} \ No newline at end of file diff --git a/src/TrainingSvc/Entities/ExerciceInstance.cs b/src/TrainingSvc/Entities/ExerciceInstance.cs index e966b07..cb1760a 100644 --- a/src/TrainingSvc/Entities/ExerciceInstance.cs +++ b/src/TrainingSvc/Entities/ExerciceInstance.cs @@ -7,7 +7,7 @@ public class ExerciceInstance : EntityBase { public required string Name { get; set; } - public string? ExerciceId { get; set; } + public string? ExerciceTemplateId { get; set; } public float Duration { get; set; } diff --git a/src/TrainingSvc/Program.cs b/src/TrainingSvc/Program.cs index 4cb66c9..1a102aa 100644 --- a/src/TrainingSvc/Program.cs +++ b/src/TrainingSvc/Program.cs @@ -21,13 +21,13 @@ app.MapControllers(); try { - //NOT IMPLEMENTED - app.MapGet("/", () => "Hello World!"); - //DbInitializer.InitDb(app); + TrainingDbInitializer.InitDb(app); } catch (Exception e) { Console.WriteLine(e); } -app.Run(); \ No newline at end of file +app.Run(); + + diff --git a/src/TrainingSvc/TrainingSvc.csproj b/src/TrainingSvc/TrainingSvc.csproj index 9e84a87..094268b 100644 --- a/src/TrainingSvc/TrainingSvc.csproj +++ b/src/TrainingSvc/TrainingSvc.csproj @@ -11,6 +11,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + From 25a830f51661ba9a8b43bf397c53d4279b8c156f Mon Sep 17 00:00:00 2001 From: tleodev Date: Sat, 7 Jun 2025 17:35:01 +0200 Subject: [PATCH 04/11] Exercice Instance Endpoints + Mappers + Dto --- ...ller.cs => ExercicesTemplateController.cs} | 4 +- .../Controllers/ExerciceController.cs | 73 ++++++++ src/TrainingSvc/DTOs/CreateExerciceDto.cs | 13 ++ src/TrainingSvc/DTOs/ExerciceDto.cs | 22 +++ src/TrainingSvc/DTOs/UpdateExerciceDto.cs | 12 ++ src/TrainingSvc/Data/TrainingDbInitializer.cs | 162 ++++++++++++------ src/TrainingSvc/Entities/ExerciceInstance.cs | 3 + .../RequestHelpers/ExerciceInstanceProfile.cs | 22 +++ src/TrainingSvc/TrainingSvc.csproj | 2 - 9 files changed, 259 insertions(+), 54 deletions(-) rename src/CatalogService/Controllers/{ExercicesController.cs => ExercicesTemplateController.cs} (93%) create mode 100644 src/TrainingSvc/Controllers/ExerciceController.cs create mode 100644 src/TrainingSvc/DTOs/CreateExerciceDto.cs create mode 100644 src/TrainingSvc/DTOs/ExerciceDto.cs create mode 100644 src/TrainingSvc/DTOs/UpdateExerciceDto.cs create mode 100644 src/TrainingSvc/RequestHelpers/ExerciceInstanceProfile.cs diff --git a/src/CatalogService/Controllers/ExercicesController.cs b/src/CatalogService/Controllers/ExercicesTemplateController.cs similarity index 93% rename from src/CatalogService/Controllers/ExercicesController.cs rename to src/CatalogService/Controllers/ExercicesTemplateController.cs index 8fb59c5..3392be8 100644 --- a/src/CatalogService/Controllers/ExercicesController.cs +++ b/src/CatalogService/Controllers/ExercicesTemplateController.cs @@ -12,12 +12,12 @@ namespace CatalogService.Controllers; [ApiController] [Authorize] [Route("api/catalog/[controller]")] -public class ExercicesController : ControllerBase +public class ExercicesTemplateController : ControllerBase { private readonly CatalogDbContext _context; private readonly IMapper _mapper; - public ExercicesController(CatalogDbContext context, IMapper mapper) + public ExercicesTemplateController(CatalogDbContext context, IMapper mapper) { _context = context; _mapper = mapper; diff --git a/src/TrainingSvc/Controllers/ExerciceController.cs b/src/TrainingSvc/Controllers/ExerciceController.cs new file mode 100644 index 0000000..4abca70 --- /dev/null +++ b/src/TrainingSvc/Controllers/ExerciceController.cs @@ -0,0 +1,73 @@ +using AutoMapper; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using TrainingSvc.Data; +using TrainingSvc.DTOs; +using TrainingSvc.Entities; + +namespace TrainingSvc.Controllers; + +[ApiController] +[Route("api/training/[controller]")] +public class ExerciceController : ControllerBase +{ + private readonly TrainingDbContext _context; + private readonly IMapper _mapper; + + public ExerciceController(TrainingDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + [HttpGet] + public async Task>> GetAll() + { + var list = await _context.ExerciceInstances + .Include(e => e.ExerciceTemplate) + .ToListAsync(); + return Ok(_mapper.Map>(list)); + } + + [HttpGet("{id}")] + public async Task> GetById(string id) + { + var entity = await _context.ExerciceInstances + .Include(e => e.ExerciceTemplate) + .FirstOrDefaultAsync(e => e.Id == id); + if (entity == null) return NotFound(); + return _mapper.Map(entity); + } + + [HttpPost] + public async Task> Create([FromBody] CreateExerciceInstanceDto dto) + { + var entity = _mapper.Map(dto); + _context.ExerciceInstances.Add(entity); + await _context.SaveChangesAsync(); + await _context.Entry(entity).Reference(e => e.ExerciceTemplate).LoadAsync(); + return CreatedAtAction(nameof(GetById), new { id = entity.Id }, _mapper.Map(entity)); + } + + [HttpPut("{id}")] + public async Task Update(string id, [FromBody] UpdateExerciceInstanceDto dto) + { + var entity = await _context.ExerciceInstances + .Include(e => e.ExerciceTemplate) + .FirstOrDefaultAsync(e => e.Id == id); + if (entity == null) return NotFound(); + _mapper.Map(dto, entity); + await _context.SaveChangesAsync(); + return NoContent(); + } + + [HttpDelete("{id}")] + public async Task Delete(string id) + { + var entity = await _context.ExerciceInstances.FindAsync(id); + if (entity == null) return NotFound(); + _context.ExerciceInstances.Remove(entity); + await _context.SaveChangesAsync(); + return NoContent(); + } +} \ No newline at end of file diff --git a/src/TrainingSvc/DTOs/CreateExerciceDto.cs b/src/TrainingSvc/DTOs/CreateExerciceDto.cs new file mode 100644 index 0000000..aacc2e6 --- /dev/null +++ b/src/TrainingSvc/DTOs/CreateExerciceDto.cs @@ -0,0 +1,13 @@ +namespace TrainingSvc.DTOs; + +public class CreateExerciceInstanceDto +{ + public string Name { get; set; } + public string? ExerciceTemplateId { get; set; } + public float Duration { get; set; } + public int NbSets { get; set; } + public int NbReps { get; set; } + public float RestingTime { get; set; } + public float? Weight { get; set; } + public string SessionId { get; set; } +} \ No newline at end of file diff --git a/src/TrainingSvc/DTOs/ExerciceDto.cs b/src/TrainingSvc/DTOs/ExerciceDto.cs new file mode 100644 index 0000000..da3e9fe --- /dev/null +++ b/src/TrainingSvc/DTOs/ExerciceDto.cs @@ -0,0 +1,22 @@ +namespace TrainingSvc.DTOs; + +public class ExerciceDto +{ + public string Id { get; set; } + public string Name { get; set; } + public string? ExerciceTemplateId { get; set; } + public float Duration { get; set; } + public int NbSets { get; set; } + public int NbReps { get; set; } + public float RestingTime { get; set; } + public float? Weight { get; set; } + public bool IsDone { get; set; } + public string SessionId { get; set; } + + // Champs du template (préfixés) + public string? Name_Template { get; set; } + public string? Description_Template { get; set; } + public string? ImageUrl_Template { get; set; } + public string? VideoUrl_Template { get; set; } + public string? Target_Template { get; set; } +} \ No newline at end of file diff --git a/src/TrainingSvc/DTOs/UpdateExerciceDto.cs b/src/TrainingSvc/DTOs/UpdateExerciceDto.cs new file mode 100644 index 0000000..641599f --- /dev/null +++ b/src/TrainingSvc/DTOs/UpdateExerciceDto.cs @@ -0,0 +1,12 @@ +namespace TrainingSvc.DTOs; + +public class UpdateExerciceInstanceDto +{ + public string Name { get; set; } + public float Duration { get; set; } + public int NbSets { get; set; } + public int NbReps { get; set; } + public float RestingTime { get; set; } + public float? Weight { get; set; } + public bool IsDone { get; set; } +} \ No newline at end of file diff --git a/src/TrainingSvc/Data/TrainingDbInitializer.cs b/src/TrainingSvc/Data/TrainingDbInitializer.cs index 1a958bc..dd4bd16 100644 --- a/src/TrainingSvc/Data/TrainingDbInitializer.cs +++ b/src/TrainingSvc/Data/TrainingDbInitializer.cs @@ -1,7 +1,8 @@ -namespace TrainingSvc.Data; using CatalogService.Entities; using Microsoft.EntityFrameworkCore; -using Shared.Enum; +using TrainingSvc.Entities; + +namespace TrainingSvc.Data; public class TrainingDbInitializer { @@ -15,62 +16,123 @@ public class TrainingDbInitializer { context.Database.Migrate(); - if (context.ExerciceTemplates.Any()) + // 1. Seed ExerciceTemplates si vide + if (!context.ExerciceTemplates.Any()) { - Console.WriteLine("Already have data in the database"); - return; + var exercices = new List() + { + new ExerciceTemplate + { + Id = Guid.NewGuid().ToString(), + Name = "Squat", + Description = "Squat is a compound exercise that targets the lower body, primarily the quadriceps, hamstrings, and glutes.", + Target = Shared.Enum.ETarget.Legs, + ImageUrl = "images/squat.jpg", + VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", + }, + new ExerciceTemplate + { + Id = Guid.NewGuid().ToString(), + Name = "Bench Press", + Description = "Bench Press is a compound exercise that primarily targets the chest, shoulders, and triceps.", + Target = Shared.Enum.ETarget.Chest, + ImageUrl = "images/bench_press.jpg", + VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", + }, + new ExerciceTemplate + { + Id = Guid.NewGuid().ToString(), + Name = "Deadlift", + Description = "Deadlift is a compound exercise that primarily targets the back, glutes, and hamstrings.", + Target = Shared.Enum.ETarget.Back, + ImageUrl = "images/deadlift.jpg", + VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", + }, + new ExerciceTemplate + { + Id = Guid.NewGuid().ToString(), + Name = "Shoulder Press", + Description = "Shoulder Press is a compound exercise that primarily targets the shoulders and triceps.", + Target = Shared.Enum.ETarget.Arms, + ImageUrl = "images/shoulder_press.jpg", + VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", + }, + new ExerciceTemplate + { + Id = Guid.NewGuid().ToString(), + Name = "Running on Treadmill", + Description = "Running on Treadmill is a cardiovascular exercise that primarily targets the legs and improves overall fitness.", + Target = Shared.Enum.ETarget.Cardio, + ImageUrl = "images/running_treadmill.jpg", + VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", + }, + }; + context.AddRange(exercices); + context.SaveChanges(); } - var exercices = new List() + // 2. Créer un TrainingProgram si aucun n'existe + var program = context.TrainingPrograms.FirstOrDefault(); + if (program == null) { - new ExerciceTemplate + program = new TrainingProgram { Id = Guid.NewGuid().ToString(), - Name = "Squat", - Description = "Squat is a compound exercise that targets the lower body, primarily the quadriceps, hamstrings, and glutes.", - Target = ETarget.Legs, - ImageUrl = "images/squat.jpg", - VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", - }, - new ExerciceTemplate - { - Id = Guid.NewGuid().ToString(), - Name = "Bench Press", - Description = "Bench Press is a compound exercise that primarily targets the chest, shoulders, and triceps.", - Target = ETarget.Chest, - ImageUrl = "images/bench_press.jpg", - VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", - }, - new ExerciceTemplate - { - Id = Guid.NewGuid().ToString(), - Name = "Deadlift", - Description = "Deadlift is a compound exercise that primarily targets the back, glutes, and hamstrings.", - Target = ETarget.Back, - ImageUrl = "images/deadlift.jpg", - VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", - }, - new ExerciceTemplate + Name = "Programme de test" + }; + context.TrainingPrograms.Add(program); + context.SaveChanges(); + } + + // 3. Créer une session de test liée au programme si aucune n'existe + var session = context.Sessions.FirstOrDefault(); + if (session == null) + { + session = new Session { Id = Guid.NewGuid().ToString(), - Name = "Shoulder Press", - Description = "Shoulder Press is a compound exercise that primarily targets the shoulders and triceps.", - Target = ETarget.Arms, - ImageUrl = "images/shoulder_press.jpg", - VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", - }, - new ExerciceTemplate + Name = "Session de test", + TrainingProgramId = program.Id + }; + context.Sessions.Add(session); + context.SaveChanges(); + } + + // 4. Seed ExerciceInstances si vide + if (!context.ExerciceInstances.Any() && context.ExerciceTemplates.Any()) + { + var templates = context.ExerciceTemplates.ToList(); + + var instances = new List { - Id = Guid.NewGuid().ToString(), - Name = "Running on Treadmill", - Description = "Running on Treadmill is a cardiovascular exercise that primarily targets the legs and improves overall fitness.", - Target = ETarget.Cardio, - ImageUrl = "images/running_treadmill.jpg", - VideoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley", - }, - }; - context.AddRange(exercices); - - context.SaveChanges(); + new ExerciceInstance + { + Name = "Squat Série 1", + ExerciceTemplateId = templates.First().Id, + Duration = 60, + NbSets = 4, + NbReps = 10, + RestingTime = 90, + Weight = 80, + IsDone = false, + SessionId = session.Id + }, + new ExerciceInstance + { + Name = "Bench Press Série 1", + ExerciceTemplateId = templates.Skip(1).First().Id, + Duration = 45, + NbSets = 3, + NbReps = 12, + RestingTime = 60, + Weight = 70, + IsDone = false, + SessionId = session.Id + } + }; + + context.ExerciceInstances.AddRange(instances); + context.SaveChanges(); + } } } \ No newline at end of file diff --git a/src/TrainingSvc/Entities/ExerciceInstance.cs b/src/TrainingSvc/Entities/ExerciceInstance.cs index cb1760a..529098e 100644 --- a/src/TrainingSvc/Entities/ExerciceInstance.cs +++ b/src/TrainingSvc/Entities/ExerciceInstance.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using CatalogService.Entities; using Shared.Entities; namespace TrainingSvc.Entities; @@ -9,6 +10,8 @@ public class ExerciceInstance : EntityBase public string? ExerciceTemplateId { get; set; } + public ExerciceTemplate? ExerciceTemplate { get; set; } + public float Duration { get; set; } public int NbSets { get; set; } diff --git a/src/TrainingSvc/RequestHelpers/ExerciceInstanceProfile.cs b/src/TrainingSvc/RequestHelpers/ExerciceInstanceProfile.cs new file mode 100644 index 0000000..cc14d57 --- /dev/null +++ b/src/TrainingSvc/RequestHelpers/ExerciceInstanceProfile.cs @@ -0,0 +1,22 @@ +using AutoMapper; +using TrainingSvc.DTOs; +using TrainingSvc.Entities; +using CatalogService.Entities; + +namespace TrainingSvc.RequestHelpers; + +public class ExerciceInstanceProfile : Profile +{ + public ExerciceInstanceProfile() + { + CreateMap() + .ForMember(dest => dest.Name_Template, opt => opt.MapFrom(src => src.ExerciceTemplate != null ? src.ExerciceTemplate.Name : null)) + .ForMember(dest => dest.Description_Template, opt => opt.MapFrom(src => src.ExerciceTemplate != null ? src.ExerciceTemplate.Description : null)) + .ForMember(dest => dest.ImageUrl_Template, opt => opt.MapFrom(src => src.ExerciceTemplate != null ? src.ExerciceTemplate.ImageUrl : null)) + .ForMember(dest => dest.VideoUrl_Template, opt => opt.MapFrom(src => src.ExerciceTemplate != null ? src.ExerciceTemplate.VideoUrl : null)) + .ForMember(dest => dest.Target_Template, opt => opt.MapFrom(src => src.ExerciceTemplate != null ? src.ExerciceTemplate.Target.ToString() : null)); + + CreateMap(); + CreateMap(); + } +} \ No newline at end of file diff --git a/src/TrainingSvc/TrainingSvc.csproj b/src/TrainingSvc/TrainingSvc.csproj index 094268b..63bd5fc 100644 --- a/src/TrainingSvc/TrainingSvc.csproj +++ b/src/TrainingSvc/TrainingSvc.csproj @@ -9,14 +9,12 @@ - all runtime; build; native; contentfiles; analyzers; buildtransitive - From 969a9c8cd67795350c19c9534643e3dc83c2c15e Mon Sep 17 00:00:00 2001 From: tleodev Date: Sat, 14 Jun 2025 21:37:19 +0200 Subject: [PATCH 05/11] Temporarly Save Training Svc progression --- ...ceController.cs => ExercicesController.cs} | 29 +++-- .../Controllers/SessionsController.cs | 110 ++++++++++++++++++ src/TrainingSvc/DTOs/CreateExerciceDto.cs | 2 +- src/TrainingSvc/DTOs/CreateSessionDto.cs | 13 +++ src/TrainingSvc/DTOs/SessionDto.cs | 14 +++ src/TrainingSvc/DTOs/UpdateSessionDto.cs | 13 +++ src/TrainingSvc/Entities/Session.cs | 1 + src/TrainingSvc/Program.cs | 7 +- .../RequestHelpers/SessionProfile.cs | 17 +++ 9 files changed, 196 insertions(+), 10 deletions(-) rename src/TrainingSvc/Controllers/{ExerciceController.cs => ExercicesController.cs} (73%) create mode 100644 src/TrainingSvc/Controllers/SessionsController.cs create mode 100644 src/TrainingSvc/DTOs/CreateSessionDto.cs create mode 100644 src/TrainingSvc/DTOs/SessionDto.cs create mode 100644 src/TrainingSvc/DTOs/UpdateSessionDto.cs create mode 100644 src/TrainingSvc/RequestHelpers/SessionProfile.cs diff --git a/src/TrainingSvc/Controllers/ExerciceController.cs b/src/TrainingSvc/Controllers/ExercicesController.cs similarity index 73% rename from src/TrainingSvc/Controllers/ExerciceController.cs rename to src/TrainingSvc/Controllers/ExercicesController.cs index 4abca70..7f8dada 100644 --- a/src/TrainingSvc/Controllers/ExerciceController.cs +++ b/src/TrainingSvc/Controllers/ExercicesController.cs @@ -9,12 +9,12 @@ namespace TrainingSvc.Controllers; [ApiController] [Route("api/training/[controller]")] -public class ExerciceController : ControllerBase +public class ExercicesController : ControllerBase { private readonly TrainingDbContext _context; private readonly IMapper _mapper; - public ExerciceController(TrainingDbContext context, IMapper mapper) + public ExercicesController(TrainingDbContext context, IMapper mapper) { _context = context; _mapper = mapper; @@ -50,13 +50,26 @@ public class ExerciceController : ControllerBase } [HttpPut("{id}")] - public async Task Update(string id, [FromBody] UpdateExerciceInstanceDto dto) + public async Task Update(string id, [FromBody] UpdateSessionDto dto) { - var entity = await _context.ExerciceInstances - .Include(e => e.ExerciceTemplate) - .FirstOrDefaultAsync(e => e.Id == id); - if (entity == null) return NotFound(); - _mapper.Map(dto, entity); + var session = await _context.Sessions + .Include(s => s.Exercices) + .FirstOrDefaultAsync(s => s.Id == id); + if (session == null) return NotFound(); + + _mapper.Map(dto, session); + + // Supprime tous les anciens exercices + _context.ExerciceInstances.RemoveRange(session.Exercices); + + // Ajoute les nouveaux exercices + foreach (var exoDto in dto.Exercices) + { + var exo = _mapper.Map(exoDto); + exo.SessionId = session.Id; + _context.ExerciceInstances.Add(exo); + } + await _context.SaveChangesAsync(); return NoContent(); } diff --git a/src/TrainingSvc/Controllers/SessionsController.cs b/src/TrainingSvc/Controllers/SessionsController.cs new file mode 100644 index 0000000..f91c238 --- /dev/null +++ b/src/TrainingSvc/Controllers/SessionsController.cs @@ -0,0 +1,110 @@ +using AutoMapper; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Shared.DTOs; +using TrainingSvc.Data; +using TrainingSvc.DTOs; +using TrainingSvc.Entities; + +namespace TrainingSvc.Controllers; + +[ApiController] +[Route("api/training/[controller]")] +public class SessionsController : ControllerBase +{ + private readonly TrainingDbContext _context; + private readonly IMapper _mapper; + + public SessionsController(TrainingDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + [HttpGet] + public async Task>> GetAll() + { + var list = await _context.Sessions + .Include(s => s.Exercices) + .ThenInclude(e => e.ExerciceTemplate) + .ToListAsync(); + return Ok(_mapper.Map>(list)); + } + + [HttpGet("{id}")] + public async Task> GetById(string id) + { + var entity = await _context.Sessions + .Include(s => s.Exercices) + .ThenInclude(e => e.ExerciceTemplate) + .FirstOrDefaultAsync(s => s.Id == id); + if (entity == null) return NotFound(); + return _mapper.Map(entity); + } + + [HttpPost] + public async Task> Create([FromBody] CreateSessionDto dto) + { + var session = _mapper.Map(dto); + _context.Sessions.Add(session); + await _context.SaveChangesAsync(); + + // Crée les ExerciceInstance associées + foreach (var exoDto in dto.Exercices) + { + var exo = _mapper.Map(exoDto); + exo.SessionId = session.Id; + _context.ExerciceInstances.Add(exo); + } + await _context.SaveChangesAsync(); + + // Recharge la session avec les exercices et templates + var entity = await _context.Sessions + .Include(s => s.Exercices) + .ThenInclude(e => e.ExerciceTemplate) + .FirstOrDefaultAsync(s => s.Id == session.Id); + + return CreatedAtAction(nameof(GetById), new { id = session.Id }, _mapper.Map(entity)); + } + + [HttpPut("{id}")] + public async Task Update(string id, [FromBody] UpdateSessionDto dto) + { + var session = await _context.Sessions + .Include(s => s.Exercices) + .FirstOrDefaultAsync(s => s.Id == id); + if (session == null) return NotFound(); + + _mapper.Map(dto, session); + + // Supprime les anciens exercices + _context.ExerciceInstances.RemoveRange(session.Exercices); + + // Ajoute les nouveaux exercices + foreach (var exoDto in dto.Exercices) + { + var exo = _mapper.Map(exoDto); + exo.SessionId = session.Id; + _context.ExerciceInstances.Add(exo); + } + + await _context.SaveChangesAsync(); + return NoContent(); + } + + [HttpDelete("{id}")] + public async Task Delete(string id) + { + var session = await _context.Sessions + .Include(s => s.Exercices) + .FirstOrDefaultAsync(s => s.Id == id); + + if (session == null) return NotFound(); + + _context.ExerciceInstances.RemoveRange(session.Exercices); + _context.Sessions.Remove(session); + + await _context.SaveChangesAsync(); + return NoContent(); + } +} \ No newline at end of file diff --git a/src/TrainingSvc/DTOs/CreateExerciceDto.cs b/src/TrainingSvc/DTOs/CreateExerciceDto.cs index aacc2e6..d61c534 100644 --- a/src/TrainingSvc/DTOs/CreateExerciceDto.cs +++ b/src/TrainingSvc/DTOs/CreateExerciceDto.cs @@ -9,5 +9,5 @@ public class CreateExerciceInstanceDto public int NbReps { get; set; } public float RestingTime { get; set; } public float? Weight { get; set; } - public string SessionId { get; set; } + public string? SessionId { get; set; } } \ No newline at end of file diff --git a/src/TrainingSvc/DTOs/CreateSessionDto.cs b/src/TrainingSvc/DTOs/CreateSessionDto.cs new file mode 100644 index 0000000..30c155d --- /dev/null +++ b/src/TrainingSvc/DTOs/CreateSessionDto.cs @@ -0,0 +1,13 @@ +using Shared.Enum; + +namespace TrainingSvc.DTOs; + +public class CreateSessionDto +{ + public string Name { get; set; } + public string? Description { get; set; } + public int Day { get; set; } + public ETarget? Target { get; set; } + public string TrainingProgramId { get; set; } + public List Exercices { get; set; } = new(); +} \ No newline at end of file diff --git a/src/TrainingSvc/DTOs/SessionDto.cs b/src/TrainingSvc/DTOs/SessionDto.cs new file mode 100644 index 0000000..10100b0 --- /dev/null +++ b/src/TrainingSvc/DTOs/SessionDto.cs @@ -0,0 +1,14 @@ +using Shared.Enum; + +namespace TrainingSvc.DTOs; + +public class SessionDto +{ + public string Id { get; set; } + public string Name { get; set; } + public string? Description { get; set; } + public int Day { get; set; } + public ETarget? Target { get; set; } + public string TrainingProgramId { get; set; } + public List Exercices { get; set; } = new(); +} \ No newline at end of file diff --git a/src/TrainingSvc/DTOs/UpdateSessionDto.cs b/src/TrainingSvc/DTOs/UpdateSessionDto.cs new file mode 100644 index 0000000..eecb871 --- /dev/null +++ b/src/TrainingSvc/DTOs/UpdateSessionDto.cs @@ -0,0 +1,13 @@ +using Shared.Enum; + +namespace TrainingSvc.DTOs; + +public class UpdateSessionDto +{ + public string Name { get; set; } + public string? Description { get; set; } + public int Day { get; set; } + public ETarget? Target { get; set; } + public string TrainingProgramId { get; set; } + public List Exercices { get; set; } = new(); +} \ No newline at end of file diff --git a/src/TrainingSvc/Entities/Session.cs b/src/TrainingSvc/Entities/Session.cs index 5c6806a..00b2513 100644 --- a/src/TrainingSvc/Entities/Session.cs +++ b/src/TrainingSvc/Entities/Session.cs @@ -7,6 +7,7 @@ namespace TrainingSvc.Entities; public class Session : EntityBase { + [Required] public required string Name { get; set; } public string? Description { get; set; } diff --git a/src/TrainingSvc/Program.cs b/src/TrainingSvc/Program.cs index 1a102aa..cbabc15 100644 --- a/src/TrainingSvc/Program.cs +++ b/src/TrainingSvc/Program.cs @@ -4,7 +4,12 @@ using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); // Add services to the container. -builder.Services.AddControllers(); +builder.Services.AddControllers() + .AddJsonOptions(options => + { + options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter(System.Text.Json.JsonNamingPolicy.CamelCase, allowIntegerValues: false)); + }); + builder.Services.AddDbContext(opt => { opt.UseNpgsql(builder.Configuration.GetConnectionString("TrainingDb")); diff --git a/src/TrainingSvc/RequestHelpers/SessionProfile.cs b/src/TrainingSvc/RequestHelpers/SessionProfile.cs new file mode 100644 index 0000000..752e8dc --- /dev/null +++ b/src/TrainingSvc/RequestHelpers/SessionProfile.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using TrainingSvc.DTOs; +using TrainingSvc.Entities; + +namespace TrainingSvc.RequestHelpers; + +public class SessionProfile : Profile +{ + public SessionProfile() + { + CreateMap() + .ForMember(dest => dest.Exercices, opt + => opt.MapFrom(src => src.Exercices)); + CreateMap(); + CreateMap(); + } +} \ No newline at end of file From 9153be1e7742aeaf26ad65b1fc8bd5aba581f820 Mon Sep 17 00:00:00 2001 From: tleodev Date: Sun, 15 Jun 2025 14:27:42 +0200 Subject: [PATCH 06/11] Save --- src/CatalogService/CatalogService.csproj | 4 --- .../Controllers/ExercicesController.cs | 25 ++++++------------- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/src/CatalogService/CatalogService.csproj b/src/CatalogService/CatalogService.csproj index 3c55125..c36fcf9 100644 --- a/src/CatalogService/CatalogService.csproj +++ b/src/CatalogService/CatalogService.csproj @@ -20,8 +20,4 @@ - - - - \ No newline at end of file diff --git a/src/TrainingSvc/Controllers/ExercicesController.cs b/src/TrainingSvc/Controllers/ExercicesController.cs index 7f8dada..b2d3ba2 100644 --- a/src/TrainingSvc/Controllers/ExercicesController.cs +++ b/src/TrainingSvc/Controllers/ExercicesController.cs @@ -50,25 +50,13 @@ public class ExercicesController : ControllerBase } [HttpPut("{id}")] - public async Task Update(string id, [FromBody] UpdateSessionDto dto) + public async Task Update(string id, [FromBody] UpdateExerciceInstanceDto dto) { - var session = await _context.Sessions - .Include(s => s.Exercices) - .FirstOrDefaultAsync(s => s.Id == id); - if (session == null) return NotFound(); - - _mapper.Map(dto, session); - - // Supprime tous les anciens exercices - _context.ExerciceInstances.RemoveRange(session.Exercices); - - // Ajoute les nouveaux exercices - foreach (var exoDto in dto.Exercices) - { - var exo = _mapper.Map(exoDto); - exo.SessionId = session.Id; - _context.ExerciceInstances.Add(exo); - } + var entity = await _context.ExerciceInstances + .Include(e => e.ExerciceTemplate) + .FirstOrDefaultAsync(e => e.Id == id); + if (entity == null) return NotFound(); + _mapper.Map(dto, entity); await _context.SaveChangesAsync(); return NoContent(); @@ -80,6 +68,7 @@ public class ExercicesController : ControllerBase var entity = await _context.ExerciceInstances.FindAsync(id); if (entity == null) return NotFound(); _context.ExerciceInstances.Remove(entity); + _context.ExerciceInstances.Remove(entity); await _context.SaveChangesAsync(); return NoContent(); } From 1fec305555815a5ce969bf55abc2f0ff8a423db0 Mon Sep 17 00:00:00 2001 From: tleodev Date: Sun, 15 Jun 2025 22:13:48 +0200 Subject: [PATCH 07/11] Session + Exercice Instance / Template logical is finnaly fonctionnal git add .! --- .../Controllers/SessionsController.cs | 14 ++++-- src/TrainingSvc/DTOs/UpdateSessionDto.cs | 2 +- src/TrainingSvc/Services/SessionService.cs | 43 +++++++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 src/TrainingSvc/Services/SessionService.cs diff --git a/src/TrainingSvc/Controllers/SessionsController.cs b/src/TrainingSvc/Controllers/SessionsController.cs index f91c238..8627473 100644 --- a/src/TrainingSvc/Controllers/SessionsController.cs +++ b/src/TrainingSvc/Controllers/SessionsController.cs @@ -75,10 +75,16 @@ public class SessionsController : ControllerBase .FirstOrDefaultAsync(s => s.Id == id); if (session == null) return NotFound(); - _mapper.Map(dto, session); - - // Supprime les anciens exercices + // Ne pas mapper TrainingProgramId pour éviter de casser la FK + session.Name = dto.Name; + session.Description = dto.Description; + session.Day = dto.Day; + session.Target = dto.Target; + // session.TrainingProgramId = dto.TrainingProgramId; // NE PAS TOUCHER + + // Supprime tous les exercices existants et sauvegarde _context.ExerciceInstances.RemoveRange(session.Exercices); + await _context.SaveChangesAsync(); // Ajoute les nouveaux exercices foreach (var exoDto in dto.Exercices) @@ -87,8 +93,8 @@ public class SessionsController : ControllerBase exo.SessionId = session.Id; _context.ExerciceInstances.Add(exo); } - await _context.SaveChangesAsync(); + return NoContent(); } diff --git a/src/TrainingSvc/DTOs/UpdateSessionDto.cs b/src/TrainingSvc/DTOs/UpdateSessionDto.cs index eecb871..f770e0e 100644 --- a/src/TrainingSvc/DTOs/UpdateSessionDto.cs +++ b/src/TrainingSvc/DTOs/UpdateSessionDto.cs @@ -9,5 +9,5 @@ public class UpdateSessionDto public int Day { get; set; } public ETarget? Target { get; set; } public string TrainingProgramId { get; set; } - public List Exercices { get; set; } = new(); + public List Exercices { get; set; } = new(); } \ No newline at end of file diff --git a/src/TrainingSvc/Services/SessionService.cs b/src/TrainingSvc/Services/SessionService.cs new file mode 100644 index 0000000..c6bd267 --- /dev/null +++ b/src/TrainingSvc/Services/SessionService.cs @@ -0,0 +1,43 @@ +using AutoMapper; +using Microsoft.EntityFrameworkCore; +using TrainingSvc.Data; +using TrainingSvc.DTOs; +using TrainingSvc.Entities; + +namespace TrainingSvc.Services; + +// src/TrainingSvc/Services/SessionService.cs +public class SessionService +{ + private readonly TrainingDbContext _context; + private readonly IMapper _mapper; + + public SessionService(TrainingDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task UpdateSessionAsync(string id, UpdateSessionDto dto) + { + var session = await _context.Sessions + .Include(s => s.Exercices) + .FirstOrDefaultAsync(s => s.Id == id); + if (session == null) throw new Exception("Session not found"); + + _mapper.Map(dto, session); + + // Supprime les anciens exercices + _context.ExerciceInstances.RemoveRange(session.Exercices); + + // Ajoute les nouveaux exercices + foreach (var exoDto in dto.Exercices) + { + var exo = _mapper.Map(exoDto); + exo.SessionId = session.Id; + _context.ExerciceInstances.Add(exo); + } + + await _context.SaveChangesAsync(); + } +} \ No newline at end of file From fb6e2f2aaa8bcffe91908336f5b26d98492342cc Mon Sep 17 00:00:00 2001 From: tleodev Date: Mon, 16 Jun 2025 10:22:10 +0200 Subject: [PATCH 08/11] Add Repository & UnitOfWork pattern --- src/Shared/Infrastructure/IRepository.cs | 25 ++++ src/TrainingSvc/Program.cs | 7 ++ .../ExerciceInstanceRepository.cs | 11 ++ .../ExerciceTemplateRepository.cs | 11 ++ .../Repositories/GenericRepository.cs | 114 ++++++++++++++++++ .../IExerciceInstanceRepository.cs | 9 ++ .../IExerciceTemplateRepository.cs | 8 ++ .../Repositories/ISessionRepository.cs | 8 ++ .../ITrainingProgramRepository.cs | 8 ++ .../Repositories/SessionRepository.cs | 11 ++ .../Repositories/TrainingProgramRepository.cs | 11 ++ src/TrainingSvc/TrainingSvc.csproj | 1 + 12 files changed, 224 insertions(+) create mode 100644 src/Shared/Infrastructure/IRepository.cs create mode 100644 src/TrainingSvc/Repositories/ExerciceInstanceRepository.cs create mode 100644 src/TrainingSvc/Repositories/ExerciceTemplateRepository.cs create mode 100644 src/TrainingSvc/Repositories/GenericRepository.cs create mode 100644 src/TrainingSvc/Repositories/IExerciceInstanceRepository.cs create mode 100644 src/TrainingSvc/Repositories/IExerciceTemplateRepository.cs create mode 100644 src/TrainingSvc/Repositories/ISessionRepository.cs create mode 100644 src/TrainingSvc/Repositories/ITrainingProgramRepository.cs create mode 100644 src/TrainingSvc/Repositories/SessionRepository.cs create mode 100644 src/TrainingSvc/Repositories/TrainingProgramRepository.cs diff --git a/src/Shared/Infrastructure/IRepository.cs b/src/Shared/Infrastructure/IRepository.cs new file mode 100644 index 0000000..2935fda --- /dev/null +++ b/src/Shared/Infrastructure/IRepository.cs @@ -0,0 +1,25 @@ +using System.Linq.Expressions; +using Shared.Entities; + +namespace Shared.Infrastructure; + +public interface IRepository where T : EntityBase +{ + IEnumerable GetAll(params Expression>[] includes); + + Task> GetAllAsync(Expression>? expression = null, CancellationToken cancellationToken = default, params Expression>[] includes); + + Task GetByIdAsync(object id, params Expression>[] includes); + + Task InsertAsync(T obj); + + void Update(T obj); + + void Delete(object id); + + //Task> GetPaginatedListAsync(int pageNumber, int pageSize, string[]? orderBy = null, Expression>? expression = null, CancellationToken cancellationToken = default, params Expression>[] includes); + + Task CountAsync(Expression>? expression = null, CancellationToken cancellationToken = default); + + Task ExistsAsync(Expression> expression, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/TrainingSvc/Program.cs b/src/TrainingSvc/Program.cs index cbabc15..ebdf95b 100644 --- a/src/TrainingSvc/Program.cs +++ b/src/TrainingSvc/Program.cs @@ -1,8 +1,15 @@ using TrainingSvc.Data; using Microsoft.EntityFrameworkCore; +using TrainingSvc.Repositories; var builder = WebApplication.CreateBuilder(args); +// Pour chaque repository +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + // Add services to the container. builder.Services.AddControllers() .AddJsonOptions(options => diff --git a/src/TrainingSvc/Repositories/ExerciceInstanceRepository.cs b/src/TrainingSvc/Repositories/ExerciceInstanceRepository.cs new file mode 100644 index 0000000..e276b13 --- /dev/null +++ b/src/TrainingSvc/Repositories/ExerciceInstanceRepository.cs @@ -0,0 +1,11 @@ +using TrainingSvc.Data; +using TrainingSvc.Entities; + +namespace TrainingSvc.Repositories; + +public class ExerciceInstanceRepository : GenericRepository, IExerciceInstanceRepository +{ + public ExerciceInstanceRepository(TrainingDbContext context) : base(context) + { + } +} \ No newline at end of file diff --git a/src/TrainingSvc/Repositories/ExerciceTemplateRepository.cs b/src/TrainingSvc/Repositories/ExerciceTemplateRepository.cs new file mode 100644 index 0000000..186ae25 --- /dev/null +++ b/src/TrainingSvc/Repositories/ExerciceTemplateRepository.cs @@ -0,0 +1,11 @@ +using CatalogService.Entities; +using TrainingSvc.Data; + +namespace TrainingSvc.Repositories; + +public class ExerciceTemplateRepository : GenericRepository, IExerciceTemplateRepository +{ + public ExerciceTemplateRepository(TrainingDbContext context) : base(context) + { + } +} \ No newline at end of file diff --git a/src/TrainingSvc/Repositories/GenericRepository.cs b/src/TrainingSvc/Repositories/GenericRepository.cs new file mode 100644 index 0000000..4e410ea --- /dev/null +++ b/src/TrainingSvc/Repositories/GenericRepository.cs @@ -0,0 +1,114 @@ +using System.Linq.Dynamic.Core; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; +using Shared.Entities; +using Shared.Infrastructure; +using TrainingSvc.Data; + +namespace TrainingSvc.Repositories; + + +public class GenericRepository : IRepository where T : EntityBase + { + protected readonly TrainingDbContext context; + + public GenericRepository(TrainingDbContext context) + { + this.context = context; + } + + public IEnumerable GetAll(params Expression>[] includes) + { + IQueryable query = this.context.Set(); + + query = includes.Aggregate(query, (current, include) => current.Include(include)); + + return query.ToList(); + } + + public async Task> GetAllAsync(Expression>? expression = null, CancellationToken cancellationToken = default, params Expression>[] includes) + { + IQueryable query = this.context.Set(); + query = includes.Aggregate(query, (current, include) => current.Include(include)); + if (expression != null) query = query.Where(expression); + + return await query.ToDynamicListAsync(cancellationToken: cancellationToken); + } + + public virtual async Task GetByIdAsync(object id, params Expression>[] includes) + { + IQueryable query = this.context.Set(); + + query = query.Where(entity => entity.Id.Equals(id)); + + query = includes.Aggregate(query, (current, include) => current.Include(include)); + + return await query.FirstOrDefaultAsync(); + } + + public async Task InsertAsync(T obj) + { + _ = await this.context.Set() + .AddAsync(obj); + } + + public void Update(T obj) + { + _ = this.context.Set().Attach(obj); + this.context.Entry(obj).State = EntityState.Modified; + } + + public void Delete(object id) + { + var existing = this.context + .Set() + .Find(id); + + _ = this.context.Set().Remove(existing!); + } + + /*public async Task> GetPaginatedListAsync( + int pageNumber, + int pageSize, + string[]? orderBy = null, + Expression>? expression = null, + CancellationToken cancellationToken = default, + params Expression>[] includes) + { + IQueryable query = this.context.Set(); + + query = includes.Aggregate(query, (current, include) => current.Include(include)); + + if (expression != null) query = query.Where(expression); + + var ordering = orderBy?.Any() == true ? string.Join(",", orderBy) : null; + + query = !string.IsNullOrWhiteSpace(ordering) ? query.OrderBy(ordering) : query.OrderBy(a => a.Id); + + var count = await query + .AsNoTracking() + .CountAsync(cancellationToken: cancellationToken); + + var items = await query + .Skip(pageNumber * pageSize) + .Take(pageSize) + .ToDynamicListAsync(cancellationToken: cancellationToken); + + return new PaginatedResult(items, count, pageNumber, pageSize); + }*/ + + public async Task CountAsync(Expression>? expression = null, CancellationToken cancellationToken = default) + { + IQueryable query = this.context.Set(); + if (expression != null) query = query.Where(expression); + + return await query + .AsNoTracking() + .CountAsync(cancellationToken: cancellationToken); + } + + public async Task ExistsAsync(Expression> expression, CancellationToken cancellationToken = default) + { + return await this.context.Set().AnyAsync(expression, cancellationToken: cancellationToken); + } + } \ No newline at end of file diff --git a/src/TrainingSvc/Repositories/IExerciceInstanceRepository.cs b/src/TrainingSvc/Repositories/IExerciceInstanceRepository.cs new file mode 100644 index 0000000..24c2d8f --- /dev/null +++ b/src/TrainingSvc/Repositories/IExerciceInstanceRepository.cs @@ -0,0 +1,9 @@ +using CatalogService.Entities; +using Shared.Infrastructure; +using TrainingSvc.Entities; + +namespace TrainingSvc.Repositories; + +public interface IExerciceInstanceRepository : IRepository +{ +} \ No newline at end of file diff --git a/src/TrainingSvc/Repositories/IExerciceTemplateRepository.cs b/src/TrainingSvc/Repositories/IExerciceTemplateRepository.cs new file mode 100644 index 0000000..0038f7a --- /dev/null +++ b/src/TrainingSvc/Repositories/IExerciceTemplateRepository.cs @@ -0,0 +1,8 @@ +using CatalogService.Entities; +using Shared.Infrastructure; + +namespace TrainingSvc.Repositories; + +public interface IExerciceTemplateRepository : IRepository +{ +} \ No newline at end of file diff --git a/src/TrainingSvc/Repositories/ISessionRepository.cs b/src/TrainingSvc/Repositories/ISessionRepository.cs new file mode 100644 index 0000000..2fa6ed4 --- /dev/null +++ b/src/TrainingSvc/Repositories/ISessionRepository.cs @@ -0,0 +1,8 @@ +using Shared.Infrastructure; +using TrainingSvc.Entities; + +namespace TrainingSvc.Repositories; + +public interface ISessionRepository : IRepository +{ +} \ No newline at end of file diff --git a/src/TrainingSvc/Repositories/ITrainingProgramRepository.cs b/src/TrainingSvc/Repositories/ITrainingProgramRepository.cs new file mode 100644 index 0000000..d702daa --- /dev/null +++ b/src/TrainingSvc/Repositories/ITrainingProgramRepository.cs @@ -0,0 +1,8 @@ +using Shared.Infrastructure; +using TrainingSvc.Entities; + +namespace TrainingSvc.Repositories; + +public interface ITrainingProgramRepository : IRepository +{ +} \ No newline at end of file diff --git a/src/TrainingSvc/Repositories/SessionRepository.cs b/src/TrainingSvc/Repositories/SessionRepository.cs new file mode 100644 index 0000000..e941edb --- /dev/null +++ b/src/TrainingSvc/Repositories/SessionRepository.cs @@ -0,0 +1,11 @@ +using TrainingSvc.Data; +using TrainingSvc.Entities; + +namespace TrainingSvc.Repositories; + +public class SessionRepository : GenericRepository, ISessionRepository +{ + public SessionRepository(TrainingDbContext context) : base(context) + { + } +} \ No newline at end of file diff --git a/src/TrainingSvc/Repositories/TrainingProgramRepository.cs b/src/TrainingSvc/Repositories/TrainingProgramRepository.cs new file mode 100644 index 0000000..4b771d7 --- /dev/null +++ b/src/TrainingSvc/Repositories/TrainingProgramRepository.cs @@ -0,0 +1,11 @@ +using TrainingSvc.Data; +using TrainingSvc.Entities; + +namespace TrainingSvc.Repositories; + +public class TrainingProgramRepository : GenericRepository, ITrainingProgramRepository +{ + public TrainingProgramRepository(TrainingDbContext context) : base(context) + { + } +} \ No newline at end of file diff --git a/src/TrainingSvc/TrainingSvc.csproj b/src/TrainingSvc/TrainingSvc.csproj index 63bd5fc..777694e 100644 --- a/src/TrainingSvc/TrainingSvc.csproj +++ b/src/TrainingSvc/TrainingSvc.csproj @@ -15,6 +15,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + From 92e5c1a31c8c5b7601f29892c78c2dc6f5abc47f Mon Sep 17 00:00:00 2001 From: tleodev Date: Mon, 16 Jun 2025 12:29:33 +0200 Subject: [PATCH 09/11] ExerciceServices --- src/TrainingSvc/IServices/IExerciceService.cs | 6 +++ src/TrainingSvc/Services/ExerciceService.cs | 6 +++ src/TrainingSvc/Services/SessionService.cs | 43 ------------------- 3 files changed, 12 insertions(+), 43 deletions(-) create mode 100644 src/TrainingSvc/IServices/IExerciceService.cs create mode 100644 src/TrainingSvc/Services/ExerciceService.cs delete mode 100644 src/TrainingSvc/Services/SessionService.cs diff --git a/src/TrainingSvc/IServices/IExerciceService.cs b/src/TrainingSvc/IServices/IExerciceService.cs new file mode 100644 index 0000000..6348406 --- /dev/null +++ b/src/TrainingSvc/IServices/IExerciceService.cs @@ -0,0 +1,6 @@ +namespace TrainingSvc.IServices; + +public class IExerciceService +{ + +} \ No newline at end of file diff --git a/src/TrainingSvc/Services/ExerciceService.cs b/src/TrainingSvc/Services/ExerciceService.cs new file mode 100644 index 0000000..cf36467 --- /dev/null +++ b/src/TrainingSvc/Services/ExerciceService.cs @@ -0,0 +1,6 @@ +namespace TrainingSvc.Services; + +public class ExerciceService +{ + +} \ No newline at end of file diff --git a/src/TrainingSvc/Services/SessionService.cs b/src/TrainingSvc/Services/SessionService.cs deleted file mode 100644 index c6bd267..0000000 --- a/src/TrainingSvc/Services/SessionService.cs +++ /dev/null @@ -1,43 +0,0 @@ -using AutoMapper; -using Microsoft.EntityFrameworkCore; -using TrainingSvc.Data; -using TrainingSvc.DTOs; -using TrainingSvc.Entities; - -namespace TrainingSvc.Services; - -// src/TrainingSvc/Services/SessionService.cs -public class SessionService -{ - private readonly TrainingDbContext _context; - private readonly IMapper _mapper; - - public SessionService(TrainingDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task UpdateSessionAsync(string id, UpdateSessionDto dto) - { - var session = await _context.Sessions - .Include(s => s.Exercices) - .FirstOrDefaultAsync(s => s.Id == id); - if (session == null) throw new Exception("Session not found"); - - _mapper.Map(dto, session); - - // Supprime les anciens exercices - _context.ExerciceInstances.RemoveRange(session.Exercices); - - // Ajoute les nouveaux exercices - foreach (var exoDto in dto.Exercices) - { - var exo = _mapper.Map(exoDto); - exo.SessionId = session.Id; - _context.ExerciceInstances.Add(exo); - } - - await _context.SaveChangesAsync(); - } -} \ No newline at end of file From 1f0537d83ea4c9b795a7e9e1c168897f8367fe2d Mon Sep 17 00:00:00 2001 From: tleodev Date: Mon, 16 Jun 2025 12:30:00 +0200 Subject: [PATCH 10/11] ExerciceServices --- src/Shared/Infrastructure/IRepository.cs | 2 + .../Controllers/ExercicesController.cs | 50 ++++--------- src/TrainingSvc/IServices/IExerciceService.cs | 10 ++- src/TrainingSvc/Program.cs | 3 + .../Repositories/GenericRepository.cs | 5 ++ src/TrainingSvc/Services/ExerciceService.cs | 70 ++++++++++++++++++- 6 files changed, 101 insertions(+), 39 deletions(-) diff --git a/src/Shared/Infrastructure/IRepository.cs b/src/Shared/Infrastructure/IRepository.cs index 2935fda..b5bb837 100644 --- a/src/Shared/Infrastructure/IRepository.cs +++ b/src/Shared/Infrastructure/IRepository.cs @@ -22,4 +22,6 @@ public interface IRepository where T : EntityBase Task CountAsync(Expression>? expression = null, CancellationToken cancellationToken = default); Task ExistsAsync(Expression> expression, CancellationToken cancellationToken = default); + + Task SaveChangesAsync(); } \ No newline at end of file diff --git a/src/TrainingSvc/Controllers/ExercicesController.cs b/src/TrainingSvc/Controllers/ExercicesController.cs index b2d3ba2..237dd3f 100644 --- a/src/TrainingSvc/Controllers/ExercicesController.cs +++ b/src/TrainingSvc/Controllers/ExercicesController.cs @@ -1,9 +1,6 @@ -using AutoMapper; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using TrainingSvc.Data; using TrainingSvc.DTOs; -using TrainingSvc.Entities; +using TrainingSvc.IServices; namespace TrainingSvc.Controllers; @@ -11,65 +8,48 @@ namespace TrainingSvc.Controllers; [Route("api/training/[controller]")] public class ExercicesController : ControllerBase { - private readonly TrainingDbContext _context; - private readonly IMapper _mapper; + private readonly IExerciceService _exerciceService; - public ExercicesController(TrainingDbContext context, IMapper mapper) + public ExercicesController(IExerciceService exerciceService) { - _context = context; - _mapper = mapper; + _exerciceService = exerciceService; } [HttpGet] public async Task>> GetAll() { - var list = await _context.ExerciceInstances - .Include(e => e.ExerciceTemplate) - .ToListAsync(); - return Ok(_mapper.Map>(list)); + var list = await _exerciceService.GetAllAsync(); + return Ok(list); } [HttpGet("{id}")] public async Task> GetById(string id) { - var entity = await _context.ExerciceInstances - .Include(e => e.ExerciceTemplate) - .FirstOrDefaultAsync(e => e.Id == id); - if (entity == null) return NotFound(); - return _mapper.Map(entity); + var dto = await _exerciceService.GetByIdAsync(id); + if (dto == null) return NotFound(); + return Ok(dto); } [HttpPost] public async Task> Create([FromBody] CreateExerciceInstanceDto dto) { - var entity = _mapper.Map(dto); - _context.ExerciceInstances.Add(entity); - await _context.SaveChangesAsync(); - await _context.Entry(entity).Reference(e => e.ExerciceTemplate).LoadAsync(); - return CreatedAtAction(nameof(GetById), new { id = entity.Id }, _mapper.Map(entity)); + var created = await _exerciceService.CreateAsync(dto); + return CreatedAtAction(nameof(GetById), new { id = created.Id }, created); } [HttpPut("{id}")] public async Task Update(string id, [FromBody] UpdateExerciceInstanceDto dto) { - var entity = await _context.ExerciceInstances - .Include(e => e.ExerciceTemplate) - .FirstOrDefaultAsync(e => e.Id == id); - if (entity == null) return NotFound(); - _mapper.Map(dto, entity); - - await _context.SaveChangesAsync(); + var success = await _exerciceService.UpdateAsync(id, dto); + if (!success) return NotFound(); return NoContent(); } [HttpDelete("{id}")] public async Task Delete(string id) { - var entity = await _context.ExerciceInstances.FindAsync(id); - if (entity == null) return NotFound(); - _context.ExerciceInstances.Remove(entity); - _context.ExerciceInstances.Remove(entity); - await _context.SaveChangesAsync(); + var success = await _exerciceService.DeleteAsync(id); + if (!success) return NotFound(); return NoContent(); } } \ No newline at end of file diff --git a/src/TrainingSvc/IServices/IExerciceService.cs b/src/TrainingSvc/IServices/IExerciceService.cs index 6348406..b6e1f0b 100644 --- a/src/TrainingSvc/IServices/IExerciceService.cs +++ b/src/TrainingSvc/IServices/IExerciceService.cs @@ -1,6 +1,12 @@ +using TrainingSvc.DTOs; + namespace TrainingSvc.IServices; -public class IExerciceService +public interface IExerciceService { - + Task> GetAllAsync(); + Task GetByIdAsync(string id); + Task CreateAsync(CreateExerciceInstanceDto dto); + Task UpdateAsync(string id, UpdateExerciceInstanceDto dto); + Task DeleteAsync(string id); } \ No newline at end of file diff --git a/src/TrainingSvc/Program.cs b/src/TrainingSvc/Program.cs index ebdf95b..368aaae 100644 --- a/src/TrainingSvc/Program.cs +++ b/src/TrainingSvc/Program.cs @@ -1,12 +1,15 @@ using TrainingSvc.Data; using Microsoft.EntityFrameworkCore; +using TrainingSvc.IServices; using TrainingSvc.Repositories; +using TrainingSvc.Services; var builder = WebApplication.CreateBuilder(args); // Pour chaque repository builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/src/TrainingSvc/Repositories/GenericRepository.cs b/src/TrainingSvc/Repositories/GenericRepository.cs index 4e410ea..dddab58 100644 --- a/src/TrainingSvc/Repositories/GenericRepository.cs +++ b/src/TrainingSvc/Repositories/GenericRepository.cs @@ -111,4 +111,9 @@ public class GenericRepository : IRepository where T : EntityBase { return await this.context.Set().AnyAsync(expression, cancellationToken: cancellationToken); } + + public async Task SaveChangesAsync() + { + await context.SaveChangesAsync(); + } } \ No newline at end of file diff --git a/src/TrainingSvc/Services/ExerciceService.cs b/src/TrainingSvc/Services/ExerciceService.cs index cf36467..2cad578 100644 --- a/src/TrainingSvc/Services/ExerciceService.cs +++ b/src/TrainingSvc/Services/ExerciceService.cs @@ -1,6 +1,72 @@ +using AutoMapper; +using TrainingSvc.DTOs; +using TrainingSvc.Entities; +using TrainingSvc.Repositories; +using TrainingSvc.IServices; + namespace TrainingSvc.Services; -public class ExerciceService +public class ExerciceService : IExerciceService { - + private readonly IExerciceInstanceRepository _exerciceRepo; + private readonly IExerciceTemplateRepository _exerciceTemplateRepo; + private readonly IMapper _mapper; + + public ExerciceService( + IExerciceInstanceRepository exerciceRepo, + IExerciceTemplateRepository exerciceTemplateRepo, + IMapper mapper) + { + _exerciceRepo = exerciceRepo; + _exerciceTemplateRepo = exerciceTemplateRepo; + _mapper = mapper; + } + + public async Task> GetAllAsync() + { + var list = await _exerciceRepo.GetAllAsync(null, default, e => e.ExerciceTemplate); + return _mapper.Map>(list); + } + + public async Task GetByIdAsync(string id) + { + var entity = await _exerciceRepo.GetByIdAsync(id, e => e.ExerciceTemplate); + return entity == null ? null : _mapper.Map(entity); + } + + public async Task CreateAsync(CreateExerciceInstanceDto dto) + { + if (!string.IsNullOrEmpty(dto.ExerciceTemplateId)) + { + var exists = await _exerciceTemplateRepo.ExistsAsync(t => t.Id == dto.ExerciceTemplateId); + if (!exists) + throw new ArgumentException("ExerciceTemplateId invalide."); + } + + var entity = _mapper.Map(dto); + await _exerciceRepo.InsertAsync(entity); + await _exerciceRepo.SaveChangesAsync(); // Persiste en base + + return _mapper.Map(entity); + } + + public async Task UpdateAsync(string id, UpdateExerciceInstanceDto dto) + { + var entity = await _exerciceRepo.GetByIdAsync(id, e => e.ExerciceTemplate); + if (entity == null) return false; + + _mapper.Map(dto, entity); + _exerciceRepo.Update(entity); + await _exerciceRepo.SaveChangesAsync(); // Persiste en base + return true; + } + + public async Task DeleteAsync(string id) + { + var entity = await _exerciceRepo.GetByIdAsync(id); + if (entity == null) return false; + _exerciceRepo.Delete(id); + await _exerciceRepo.SaveChangesAsync(); // Persiste en base + return true; + } } \ No newline at end of file From b19e762d78d65708af992a58a673d2df214efe5c Mon Sep 17 00:00:00 2001 From: tleodev Date: Tue, 17 Jun 2025 10:34:40 +0200 Subject: [PATCH 11/11] Functionnal program logic --- .../Controllers/SessionsController.cs | 93 ++++-------------- .../Controllers/TrainingProgramsController.cs | 32 +++++++ src/TrainingSvc/DTOs/TrainingProgramDto.cs | 17 ++++ src/TrainingSvc/DTOs/UpdateSessionDto.cs | 1 - src/TrainingSvc/IServices/ISessionService.cs | 12 +++ .../IServices/ITrainingProgramService.cs | 9 ++ src/TrainingSvc/Program.cs | 2 + .../ITrainingProgramRepository.cs | 2 + .../Repositories/SessionRepository.cs | 17 ++++ .../Repositories/TrainingProgramRepository.cs | 19 ++++ .../RequestHelpers/SessionProfile.cs | 3 +- .../RequestHelpers/TrainingProgramProfile.cs | 13 +++ src/TrainingSvc/Services/SessionService.cs | 95 +++++++++++++++++++ .../Services/TrainingProgramService.cs | 30 ++++++ 14 files changed, 266 insertions(+), 79 deletions(-) create mode 100644 src/TrainingSvc/Controllers/TrainingProgramsController.cs create mode 100644 src/TrainingSvc/DTOs/TrainingProgramDto.cs create mode 100644 src/TrainingSvc/IServices/ISessionService.cs create mode 100644 src/TrainingSvc/IServices/ITrainingProgramService.cs create mode 100644 src/TrainingSvc/RequestHelpers/TrainingProgramProfile.cs create mode 100644 src/TrainingSvc/Services/SessionService.cs create mode 100644 src/TrainingSvc/Services/TrainingProgramService.cs diff --git a/src/TrainingSvc/Controllers/SessionsController.cs b/src/TrainingSvc/Controllers/SessionsController.cs index 8627473..0baad3a 100644 --- a/src/TrainingSvc/Controllers/SessionsController.cs +++ b/src/TrainingSvc/Controllers/SessionsController.cs @@ -1,10 +1,6 @@ -using AutoMapper; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Shared.DTOs; -using TrainingSvc.Data; using TrainingSvc.DTOs; -using TrainingSvc.Entities; +using TrainingSvc.IServices; namespace TrainingSvc.Controllers; @@ -12,105 +8,48 @@ namespace TrainingSvc.Controllers; [Route("api/training/[controller]")] public class SessionsController : ControllerBase { - private readonly TrainingDbContext _context; - private readonly IMapper _mapper; + private readonly ISessionService _sessionService; - public SessionsController(TrainingDbContext context, IMapper mapper) + public SessionsController(ISessionService sessionService) { - _context = context; - _mapper = mapper; + _sessionService = sessionService; } [HttpGet] public async Task>> GetAll() { - var list = await _context.Sessions - .Include(s => s.Exercices) - .ThenInclude(e => e.ExerciceTemplate) - .ToListAsync(); - return Ok(_mapper.Map>(list)); + var list = await _sessionService.GetAllAsync(); + return Ok(list); } [HttpGet("{id}")] public async Task> GetById(string id) { - var entity = await _context.Sessions - .Include(s => s.Exercices) - .ThenInclude(e => e.ExerciceTemplate) - .FirstOrDefaultAsync(s => s.Id == id); - if (entity == null) return NotFound(); - return _mapper.Map(entity); + var dto = await _sessionService.GetByIdAsync(id); + if (dto == null) return NotFound(); + return Ok(dto); } [HttpPost] public async Task> Create([FromBody] CreateSessionDto dto) { - var session = _mapper.Map(dto); - _context.Sessions.Add(session); - await _context.SaveChangesAsync(); - - // Crée les ExerciceInstance associées - foreach (var exoDto in dto.Exercices) - { - var exo = _mapper.Map(exoDto); - exo.SessionId = session.Id; - _context.ExerciceInstances.Add(exo); - } - await _context.SaveChangesAsync(); - - // Recharge la session avec les exercices et templates - var entity = await _context.Sessions - .Include(s => s.Exercices) - .ThenInclude(e => e.ExerciceTemplate) - .FirstOrDefaultAsync(s => s.Id == session.Id); - - return CreatedAtAction(nameof(GetById), new { id = session.Id }, _mapper.Map(entity)); + var created = await _sessionService.CreateAsync(dto); + return CreatedAtAction(nameof(GetById), new { id = created.Id }, created); } [HttpPut("{id}")] public async Task Update(string id, [FromBody] UpdateSessionDto dto) { - var session = await _context.Sessions - .Include(s => s.Exercices) - .FirstOrDefaultAsync(s => s.Id == id); - if (session == null) return NotFound(); - - // Ne pas mapper TrainingProgramId pour éviter de casser la FK - session.Name = dto.Name; - session.Description = dto.Description; - session.Day = dto.Day; - session.Target = dto.Target; - // session.TrainingProgramId = dto.TrainingProgramId; // NE PAS TOUCHER - - // Supprime tous les exercices existants et sauvegarde - _context.ExerciceInstances.RemoveRange(session.Exercices); - await _context.SaveChangesAsync(); - - // Ajoute les nouveaux exercices - foreach (var exoDto in dto.Exercices) - { - var exo = _mapper.Map(exoDto); - exo.SessionId = session.Id; - _context.ExerciceInstances.Add(exo); - } - await _context.SaveChangesAsync(); - + var success = await _sessionService.UpdateAsync(id, dto); + if (!success) return NotFound(); return NoContent(); } - + [HttpDelete("{id}")] public async Task Delete(string id) { - var session = await _context.Sessions - .Include(s => s.Exercices) - .FirstOrDefaultAsync(s => s.Id == id); - - if (session == null) return NotFound(); - - _context.ExerciceInstances.RemoveRange(session.Exercices); - _context.Sessions.Remove(session); - - await _context.SaveChangesAsync(); + var success = await _sessionService.DeleteAsync(id); + if (!success) return NotFound(); return NoContent(); } } \ No newline at end of file diff --git a/src/TrainingSvc/Controllers/TrainingProgramsController.cs b/src/TrainingSvc/Controllers/TrainingProgramsController.cs new file mode 100644 index 0000000..0a92d3e --- /dev/null +++ b/src/TrainingSvc/Controllers/TrainingProgramsController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; +using TrainingSvc.DTOs; +using TrainingSvc.IServices; + +namespace TrainingSvc.Controllers; + +[ApiController] +[Route("api/training/[controller]")] +public class TrainingProgramsController : ControllerBase +{ + private readonly ITrainingProgramService _service; + + public TrainingProgramsController(ITrainingProgramService service) + { + _service = service; + } + + [HttpGet] + public async Task>> GetAll() + { + var list = await _service.GetAllAsync(); + return Ok(list); + } + + [HttpGet("{id}")] + public async Task> GetById(string id) + { + var dto = await _service.GetByIdAsync(id); + if (dto == null) return NotFound(); + return Ok(dto); + } +} \ No newline at end of file diff --git a/src/TrainingSvc/DTOs/TrainingProgramDto.cs b/src/TrainingSvc/DTOs/TrainingProgramDto.cs new file mode 100644 index 0000000..f3469fa --- /dev/null +++ b/src/TrainingSvc/DTOs/TrainingProgramDto.cs @@ -0,0 +1,17 @@ +using Shared.Enum; + +namespace TrainingSvc.DTOs; + +public class TrainingProgramDto +{ + public string Id { get; set; } + public string Lang { get; set; } + public string Name { get; set; } + public string? Description { get; set; } + public int WeekDuration { get; set; } + public int NbDays { get; set; } + public string OwnerId { get; set; } + public EGoal Goal { get; set; } + public EDifficulty Difficulty { get; set; } + public List Sessions { get; set; } = new(); +} \ No newline at end of file diff --git a/src/TrainingSvc/DTOs/UpdateSessionDto.cs b/src/TrainingSvc/DTOs/UpdateSessionDto.cs index f770e0e..35dd6a8 100644 --- a/src/TrainingSvc/DTOs/UpdateSessionDto.cs +++ b/src/TrainingSvc/DTOs/UpdateSessionDto.cs @@ -9,5 +9,4 @@ public class UpdateSessionDto public int Day { get; set; } public ETarget? Target { get; set; } public string TrainingProgramId { get; set; } - public List Exercices { get; set; } = new(); } \ No newline at end of file diff --git a/src/TrainingSvc/IServices/ISessionService.cs b/src/TrainingSvc/IServices/ISessionService.cs new file mode 100644 index 0000000..e05587b --- /dev/null +++ b/src/TrainingSvc/IServices/ISessionService.cs @@ -0,0 +1,12 @@ +using TrainingSvc.DTOs; + +namespace TrainingSvc.IServices; + +public interface ISessionService +{ + Task> GetAllAsync(); + Task GetByIdAsync(string id); + Task CreateAsync(CreateSessionDto dto); + Task UpdateAsync(string id, UpdateSessionDto dto); + Task DeleteAsync(string id); +} \ No newline at end of file diff --git a/src/TrainingSvc/IServices/ITrainingProgramService.cs b/src/TrainingSvc/IServices/ITrainingProgramService.cs new file mode 100644 index 0000000..dfa6546 --- /dev/null +++ b/src/TrainingSvc/IServices/ITrainingProgramService.cs @@ -0,0 +1,9 @@ +using TrainingSvc.DTOs; + +namespace TrainingSvc.IServices; + +public interface ITrainingProgramService +{ + Task> GetAllAsync(); + Task GetByIdAsync(string id); +} \ No newline at end of file diff --git a/src/TrainingSvc/Program.cs b/src/TrainingSvc/Program.cs index 368aaae..cd4e8de 100644 --- a/src/TrainingSvc/Program.cs +++ b/src/TrainingSvc/Program.cs @@ -11,7 +11,9 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); // Add services to the container. builder.Services.AddControllers() diff --git a/src/TrainingSvc/Repositories/ITrainingProgramRepository.cs b/src/TrainingSvc/Repositories/ITrainingProgramRepository.cs index d702daa..86133d3 100644 --- a/src/TrainingSvc/Repositories/ITrainingProgramRepository.cs +++ b/src/TrainingSvc/Repositories/ITrainingProgramRepository.cs @@ -5,4 +5,6 @@ namespace TrainingSvc.Repositories; public interface ITrainingProgramRepository : IRepository { + Task> GetAllWithSessionsAsync(); + Task GetByIdWithSessionsAsync(string id); } \ No newline at end of file diff --git a/src/TrainingSvc/Repositories/SessionRepository.cs b/src/TrainingSvc/Repositories/SessionRepository.cs index e941edb..8853ded 100644 --- a/src/TrainingSvc/Repositories/SessionRepository.cs +++ b/src/TrainingSvc/Repositories/SessionRepository.cs @@ -1,3 +1,4 @@ +using Microsoft.EntityFrameworkCore; using TrainingSvc.Data; using TrainingSvc.Entities; @@ -8,4 +9,20 @@ public class SessionRepository : GenericRepository, ISessionRepository public SessionRepository(TrainingDbContext context) : base(context) { } + + public async Task> GetAllWithExercicesAndTemplatesAsync() + { + return await context.Sessions + .Include(s => s.Exercices) + .ThenInclude(e => e.ExerciceTemplate) + .ToListAsync(); + } + + public async Task GetByIdWithExercicesAndTemplatesAsync(string id) + { + return await context.Sessions + .Include(s => s.Exercices) + .ThenInclude(e => e.ExerciceTemplate) + .FirstOrDefaultAsync(s => s.Id == id); + } } \ No newline at end of file diff --git a/src/TrainingSvc/Repositories/TrainingProgramRepository.cs b/src/TrainingSvc/Repositories/TrainingProgramRepository.cs index 4b771d7..3453b25 100644 --- a/src/TrainingSvc/Repositories/TrainingProgramRepository.cs +++ b/src/TrainingSvc/Repositories/TrainingProgramRepository.cs @@ -1,3 +1,4 @@ +using Microsoft.EntityFrameworkCore; using TrainingSvc.Data; using TrainingSvc.Entities; @@ -8,4 +9,22 @@ public class TrainingProgramRepository : GenericRepository, ITr public TrainingProgramRepository(TrainingDbContext context) : base(context) { } + + public async Task> GetAllWithSessionsAsync() + { + return await context.TrainingPrograms + .Include(tp => tp.Sessions) + .ThenInclude(s => s.Exercices) + .ThenInclude(e => e.ExerciceTemplate) + .ToListAsync(); + } + + public async Task GetByIdWithSessionsAsync(string id) + { + return await context.TrainingPrograms + .Include(tp => tp.Sessions) + .ThenInclude(s => s.Exercices) + .ThenInclude(e => e.ExerciceTemplate) + .FirstOrDefaultAsync(tp => tp.Id == id); + } } \ No newline at end of file diff --git a/src/TrainingSvc/RequestHelpers/SessionProfile.cs b/src/TrainingSvc/RequestHelpers/SessionProfile.cs index 752e8dc..a2601b2 100644 --- a/src/TrainingSvc/RequestHelpers/SessionProfile.cs +++ b/src/TrainingSvc/RequestHelpers/SessionProfile.cs @@ -11,7 +11,8 @@ public class SessionProfile : Profile CreateMap() .ForMember(dest => dest.Exercices, opt => opt.MapFrom(src => src.Exercices)); - CreateMap(); + CreateMap() + .ForMember(dest => dest.Exercices, opt => opt.Ignore()); // Ignore Exercices to handle them separately CreateMap(); } } \ No newline at end of file diff --git a/src/TrainingSvc/RequestHelpers/TrainingProgramProfile.cs b/src/TrainingSvc/RequestHelpers/TrainingProgramProfile.cs new file mode 100644 index 0000000..5339819 --- /dev/null +++ b/src/TrainingSvc/RequestHelpers/TrainingProgramProfile.cs @@ -0,0 +1,13 @@ +using AutoMapper; +using TrainingSvc.DTOs; +using TrainingSvc.Entities; + +namespace TrainingSvc.RequestHelpers; + +public class TrainingProgramProfile : Profile +{ + public TrainingProgramProfile() + { + CreateMap(); + } +} \ No newline at end of file diff --git a/src/TrainingSvc/Services/SessionService.cs b/src/TrainingSvc/Services/SessionService.cs new file mode 100644 index 0000000..74a9257 --- /dev/null +++ b/src/TrainingSvc/Services/SessionService.cs @@ -0,0 +1,95 @@ +using AutoMapper; +using TrainingSvc.DTOs; +using TrainingSvc.Entities; +using TrainingSvc.Repositories; +using TrainingSvc.IServices; + +namespace TrainingSvc.Services; + +public class SessionService : ISessionService +{ + private readonly ISessionRepository _sessionRepo; + private readonly IExerciceInstanceRepository _exerciceRepo; + private readonly IExerciceTemplateRepository _exerciceTemplateRepo; + private readonly IMapper _mapper; + + public SessionService( + ISessionRepository sessionRepo, + IExerciceInstanceRepository exerciceRepo, + IExerciceTemplateRepository exerciceTemplateRepo, + IMapper mapper) + { + _sessionRepo = sessionRepo; + _exerciceRepo = exerciceRepo; + _exerciceTemplateRepo = exerciceTemplateRepo; + _mapper = mapper; + } + + public async Task> GetAllAsync() + { + var sessions = await ((SessionRepository)_sessionRepo).GetAllWithExercicesAndTemplatesAsync(); + return _mapper.Map>(sessions); + } + + public async Task GetByIdAsync(string id) + { + var entity = await ((SessionRepository)_sessionRepo).GetByIdWithExercicesAndTemplatesAsync(id); + return entity == null ? null : _mapper.Map(entity); + } + + public async Task CreateAsync(CreateSessionDto dto) + { + // Ne mappe pas les exercices ici + var session = _mapper.Map(dto); + session.Exercices.Clear(); // S'assure qu'il n'y a pas d'exercices liés + + await _sessionRepo.InsertAsync(session); + await _sessionRepo.SaveChangesAsync(); + + foreach (var exoDto in dto.Exercices) + { + if (!string.IsNullOrEmpty(exoDto.ExerciceTemplateId)) + { + var exists = await _exerciceTemplateRepo.ExistsAsync(t => t.Id == exoDto.ExerciceTemplateId); + if (!exists) + throw new ArgumentException("ExerciceTemplateId invalide."); + } + var exo = _mapper.Map(exoDto); + exo.SessionId = session.Id; + await _exerciceRepo.InsertAsync(exo); + } + await _exerciceRepo.SaveChangesAsync(); + + var entity = await ((SessionRepository)_sessionRepo).GetByIdWithExercicesAndTemplatesAsync(session.Id); + return _mapper.Map(entity); + } + + public async Task UpdateAsync(string id, UpdateSessionDto dto) + { + var session = await ((SessionRepository)_sessionRepo).GetByIdWithExercicesAndTemplatesAsync(id); + if (session == null) return false; + + _mapper.Map(dto, session); + _sessionRepo.Update(session); + await _sessionRepo.SaveChangesAsync(); + + return true; + } + + public async Task DeleteAsync(string id) + { + var session = await _sessionRepo.GetByIdAsync(id, s => s.Exercices); + if (session == null) return false; + + foreach (var ex in session.Exercices.ToList()) + { + _exerciceRepo.Delete(ex.Id); + } + await _exerciceRepo.SaveChangesAsync(); + + _sessionRepo.Delete(id); + await _sessionRepo.SaveChangesAsync(); + + return true; + } +} \ No newline at end of file diff --git a/src/TrainingSvc/Services/TrainingProgramService.cs b/src/TrainingSvc/Services/TrainingProgramService.cs new file mode 100644 index 0000000..41fcd15 --- /dev/null +++ b/src/TrainingSvc/Services/TrainingProgramService.cs @@ -0,0 +1,30 @@ +using AutoMapper; +using TrainingSvc.DTOs; +using TrainingSvc.IServices; +using TrainingSvc.Repositories; + +namespace TrainingSvc.Services; + +public class TrainingProgramService : ITrainingProgramService +{ + private readonly ITrainingProgramRepository _repo; + private readonly IMapper _mapper; + + public TrainingProgramService(ITrainingProgramRepository repo, IMapper mapper) + { + _repo = repo; + _mapper = mapper; + } + + public async Task> GetAllAsync() + { + var list = await _repo.GetAllWithSessionsAsync(); + return _mapper.Map>(list); + } + + public async Task GetByIdAsync(string id) + { + var entity = await _repo.GetByIdWithSessionsAsync(id); + return entity == null ? null : _mapper.Map(entity); + } +} \ No newline at end of file