diff --git a/OptifitWebServices.sln b/OptifitWebServices.sln index 07fd5fc..70c9d2a 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 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdentitySvc", "src\IdentitySvc\IdentitySvc.csproj", "{74C8ACD5-5DC4-4466-8846-B552FF131304}" EndProject Global @@ -28,6 +30,10 @@ 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 {74C8ACD5-5DC4-4466-8846-B552FF131304}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {74C8ACD5-5DC4-4466-8846-B552FF131304}.Debug|Any CPU.Build.0 = Debug|Any CPU {74C8ACD5-5DC4-4466-8846-B552FF131304}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -36,6 +42,7 @@ Global 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} {74C8ACD5-5DC4-4466-8846-B552FF131304} = {2A7200CA-F40B-4715-8726-4ED30C785FA4} EndGlobalSection EndGlobal diff --git a/src/CatalogService/Controllers/ExercicesController.cs b/src/CatalogService/Controllers/ExercicesTemplateController.cs similarity index 94% rename from src/CatalogService/Controllers/ExercicesController.cs rename to src/CatalogService/Controllers/ExercicesTemplateController.cs index 5c88f39..1895180 100644 --- a/src/CatalogService/Controllers/ExercicesController.cs +++ b/src/CatalogService/Controllers/ExercicesTemplateController.cs @@ -25,11 +25,12 @@ public class ExercicesController : ControllerBase //[Authorize] [HttpPost] + [AllowAnonymous] public async Task Create([FromBody] CreateExerciceTemplateDto dto) { //if (User.Identity.Name != "admin") return Forbid(); - - 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)); @@ -37,6 +38,7 @@ public class ExercicesController : ControllerBase //[Authorize] [HttpPut("{id}")] + [AllowAnonymous] public async Task Update(string id, [FromBody] UpdateExerciceTemplateDto dto) { //if (User.Identity.Name != "admin") return Forbid(); @@ -52,6 +54,7 @@ public class ExercicesController : ControllerBase //[Authorize] [HttpDelete("{id}")] + [AllowAnonymous] public async Task Delete(string id) { //if (User.Identity.Name != "admin") return Forbid(); @@ -66,6 +69,7 @@ public class ExercicesController : ControllerBase //[Authorize] [HttpGet("{id}")] + [AllowAnonymous] public async Task> GetById(string id) { //if (User.Identity.Name != "admin") return Forbid(); 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; } 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/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/Shared/Infrastructure/IRepository.cs b/src/Shared/Infrastructure/IRepository.cs new file mode 100644 index 0000000..b5bb837 --- /dev/null +++ b/src/Shared/Infrastructure/IRepository.cs @@ -0,0 +1,27 @@ +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); + + Task SaveChangesAsync(); +} \ No newline at end of file diff --git a/src/TrainingSvc/Controllers/ExercicesController.cs b/src/TrainingSvc/Controllers/ExercicesController.cs new file mode 100644 index 0000000..237dd3f --- /dev/null +++ b/src/TrainingSvc/Controllers/ExercicesController.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Mvc; +using TrainingSvc.DTOs; +using TrainingSvc.IServices; + +namespace TrainingSvc.Controllers; + +[ApiController] +[Route("api/training/[controller]")] +public class ExercicesController : ControllerBase +{ + private readonly IExerciceService _exerciceService; + + public ExercicesController(IExerciceService exerciceService) + { + _exerciceService = exerciceService; + } + + [HttpGet] + public async Task>> GetAll() + { + var list = await _exerciceService.GetAllAsync(); + return Ok(list); + } + + [HttpGet("{id}")] + public async Task> GetById(string id) + { + var dto = await _exerciceService.GetByIdAsync(id); + if (dto == null) return NotFound(); + return Ok(dto); + } + + [HttpPost] + public async Task> Create([FromBody] CreateExerciceInstanceDto dto) + { + 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 success = await _exerciceService.UpdateAsync(id, dto); + if (!success) return NotFound(); + return NoContent(); + } + + [HttpDelete("{id}")] + public async Task Delete(string id) + { + var success = await _exerciceService.DeleteAsync(id); + if (!success) return NotFound(); + return NoContent(); + } +} \ No newline at end of file diff --git a/src/TrainingSvc/Controllers/SessionsController.cs b/src/TrainingSvc/Controllers/SessionsController.cs new file mode 100644 index 0000000..0baad3a --- /dev/null +++ b/src/TrainingSvc/Controllers/SessionsController.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Mvc; +using TrainingSvc.DTOs; +using TrainingSvc.IServices; + +namespace TrainingSvc.Controllers; + +[ApiController] +[Route("api/training/[controller]")] +public class SessionsController : ControllerBase +{ + private readonly ISessionService _sessionService; + + public SessionsController(ISessionService sessionService) + { + _sessionService = sessionService; + } + + [HttpGet] + public async Task>> GetAll() + { + var list = await _sessionService.GetAllAsync(); + return Ok(list); + } + + [HttpGet("{id}")] + public async Task> GetById(string id) + { + var dto = await _sessionService.GetByIdAsync(id); + if (dto == null) return NotFound(); + return Ok(dto); + } + + [HttpPost] + public async Task> Create([FromBody] CreateSessionDto dto) + { + 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 success = await _sessionService.UpdateAsync(id, dto); + if (!success) return NotFound(); + return NoContent(); + } + + [HttpDelete("{id}")] + public async Task Delete(string id) + { + 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/CreateExerciceDto.cs b/src/TrainingSvc/DTOs/CreateExerciceDto.cs new file mode 100644 index 0000000..d61c534 --- /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/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/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/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/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/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/DTOs/UpdateSessionDto.cs b/src/TrainingSvc/DTOs/UpdateSessionDto.cs new file mode 100644 index 0000000..35dd6a8 --- /dev/null +++ b/src/TrainingSvc/DTOs/UpdateSessionDto.cs @@ -0,0 +1,12 @@ +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; } +} \ No newline at end of file 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 new file mode 100644 index 0000000..5d047bf --- /dev/null +++ b/src/TrainingSvc/Data/TrainingDbContext.cs @@ -0,0 +1,21 @@ +using CatalogService.Entities; +using Microsoft.EntityFrameworkCore; +using TrainingSvc.Entities; + +namespace TrainingSvc.Data; + +public class TrainingDbContext : DbContext +{ + public TrainingDbContext(DbContextOptions options) : base(options) + { + } + + 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..dd4bd16 --- /dev/null +++ b/src/TrainingSvc/Data/TrainingDbInitializer.cs @@ -0,0 +1,138 @@ +using CatalogService.Entities; +using Microsoft.EntityFrameworkCore; +using TrainingSvc.Entities; + +namespace TrainingSvc.Data; + +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(); + + // 1. Seed ExerciceTemplates si vide + if (!context.ExerciceTemplates.Any()) + { + 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(); + } + + // 2. Créer un TrainingProgram si aucun n'existe + var program = context.TrainingPrograms.FirstOrDefault(); + if (program == null) + { + program = new TrainingProgram + { + Id = Guid.NewGuid().ToString(), + 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 = "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 + { + 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 new file mode 100644 index 0000000..529098e --- /dev/null +++ b/src/TrainingSvc/Entities/ExerciceInstance.cs @@ -0,0 +1,31 @@ +using System.ComponentModel.DataAnnotations; +using CatalogService.Entities; +using Shared.Entities; + +namespace TrainingSvc.Entities; + +public class ExerciceInstance : EntityBase +{ + public required string Name { get; set; } + + public string? ExerciceTemplateId { get; set; } + + public ExerciceTemplate? ExerciceTemplate { 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..00b2513 --- /dev/null +++ b/src/TrainingSvc/Entities/Session.cs @@ -0,0 +1,25 @@ +using System.Collections.ObjectModel; +using System.ComponentModel.DataAnnotations; +using Shared.Entities; +using Shared.Enum; + +namespace TrainingSvc.Entities; + +public class Session : EntityBase +{ + [Required] + 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/IServices/IExerciceService.cs b/src/TrainingSvc/IServices/IExerciceService.cs new file mode 100644 index 0000000..b6e1f0b --- /dev/null +++ b/src/TrainingSvc/IServices/IExerciceService.cs @@ -0,0 +1,12 @@ +using TrainingSvc.DTOs; + +namespace TrainingSvc.IServices; + +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/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 new file mode 100644 index 0000000..cd4e8de --- /dev/null +++ b/src/TrainingSvc/Program.cs @@ -0,0 +1,50 @@ +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(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +// Add services to the container. +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")); + +}); +builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); + + +var app = builder.Build(); + +app.UseAuthorization(); + +app.MapControllers(); + +try +{ + TrainingDbInitializer.InitDb(app); +} +catch (Exception e) +{ + Console.WriteLine(e); +} + +app.Run(); + + 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/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..dddab58 --- /dev/null +++ b/src/TrainingSvc/Repositories/GenericRepository.cs @@ -0,0 +1,119 @@ +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); + } + + public async Task SaveChangesAsync() + { + await context.SaveChangesAsync(); + } + } \ 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..86133d3 --- /dev/null +++ b/src/TrainingSvc/Repositories/ITrainingProgramRepository.cs @@ -0,0 +1,10 @@ +using Shared.Infrastructure; +using TrainingSvc.Entities; + +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 new file mode 100644 index 0000000..8853ded --- /dev/null +++ b/src/TrainingSvc/Repositories/SessionRepository.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore; +using TrainingSvc.Data; +using TrainingSvc.Entities; + +namespace TrainingSvc.Repositories; + +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 new file mode 100644 index 0000000..3453b25 --- /dev/null +++ b/src/TrainingSvc/Repositories/TrainingProgramRepository.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using TrainingSvc.Data; +using TrainingSvc.Entities; + +namespace TrainingSvc.Repositories; + +public class TrainingProgramRepository : GenericRepository, ITrainingProgramRepository +{ + 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/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/RequestHelpers/SessionProfile.cs b/src/TrainingSvc/RequestHelpers/SessionProfile.cs new file mode 100644 index 0000000..a2601b2 --- /dev/null +++ b/src/TrainingSvc/RequestHelpers/SessionProfile.cs @@ -0,0 +1,18 @@ +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() + .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/ExerciceService.cs b/src/TrainingSvc/Services/ExerciceService.cs new file mode 100644 index 0000000..2cad578 --- /dev/null +++ b/src/TrainingSvc/Services/ExerciceService.cs @@ -0,0 +1,72 @@ +using AutoMapper; +using TrainingSvc.DTOs; +using TrainingSvc.Entities; +using TrainingSvc.Repositories; +using TrainingSvc.IServices; + +namespace TrainingSvc.Services; + +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 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 diff --git a/src/TrainingSvc/TrainingSvc.csproj b/src/TrainingSvc/TrainingSvc.csproj new file mode 100644 index 0000000..777694e --- /dev/null +++ b/src/TrainingSvc/TrainingSvc.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + 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 + +###