Compare commits

...

5 Commits

4
.gitignore vendored

@ -800,4 +800,6 @@ pyrightconfig.json
# End of https://www.toptal.com/developers/gitignore/api/rider,intellij,intellij+all,dotnetcore,csharp,python
**/appsettings*.json
**/appsettings*.json
.DS_Store

@ -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

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>disable</Nullable>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
@ -20,4 +20,8 @@
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.11" />
</ItemGroup>
<ItemGroup>
<Folder Include="Entities\" />
</ItemGroup>
</Project>

@ -0,0 +1,70 @@
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;
namespace CatalogService.Controllers;
[ApiController]
[Authorize]
[Route("api/catalog/[controller]")]
public class ExercicesTemplateController : ControllerBase
{
private readonly CatalogDbContext _context;
private readonly IMapper _mapper;
public ExercicesTemplateController(CatalogDbContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Create([FromBody] CreateExerciceTemplateDto dto)
{
var exercice = _mapper.Map<ExerciceTemplate>(dto);
_context.Exercices.Add(exercice);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetById), new { id = exercice.Id }, _mapper.Map<ExerciceTemplateDto>(exercice));
}
[HttpPut("{id}")]
[AllowAnonymous]
public async Task<IActionResult> Update(string id, [FromBody] UpdateExerciceTemplateDto dto)
{
var exercice = await _context.Exercices.FindAsync(id);
if (exercice == null) return NotFound();
_mapper.Map(dto, exercice);
exercice.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
return NoContent();
}
[HttpDelete("{id}")]
[AllowAnonymous]
public async Task<IActionResult> Delete(string id)
{
var exercice = await _context.Exercices.FindAsync(id);
if (exercice == null) return NotFound();
_context.Exercices.Remove(exercice);
await _context.SaveChangesAsync();
return NoContent();
}
[HttpGet("{id}")]
[AllowAnonymous]
public async Task<ActionResult<ExerciceTemplateDto>> GetById(string id)
{
var exercice = await _context.Exercices.FindAsync(id);
if (exercice == null) return NotFound();
return _mapper.Map<ExerciceTemplateDto>(exercice);
}
}

@ -6,10 +6,9 @@ namespace CatalogService.DTOs;
public class CreateExerciceTemplateDto
{
[Required]
public string Name { get; set; }
[Required]
public string Description { get; set; }
public required string Name { get; set; }
public string? Description { get; set; } = default;
public ETarget? Target { get; set; } = ETarget.None;

@ -1,6 +1,6 @@
using Shared.Enum;
namespace Shared.DTOs;
namespace CatalogService.DTOs;
public class ExerciceTemplateDto
{

@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
using Shared.Enum;
namespace CatalogService.DTOs;
public class UpdateExerciceTemplateDto
{
public string? Name { get; set; }
public string? Description { get; set; }
public ETarget? Target { get; set; }
public string? ImageUrl { get; set; }
public string? VideoUrl { get; set; }
}

@ -9,5 +9,5 @@ public class CatalogDbContext : DbContext
{
}
public DbSet<Exercice> Exercices { get; set; }
public DbSet<ExerciceTemplate> Exercices { get; set; }
}

@ -22,9 +22,9 @@ public class DbInitializer
return;
}
var exercices = new List<Exercice>()
var exercices = new List<ExerciceTemplate>()
{
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",

@ -10,6 +10,7 @@ builder.Services.AddDbContext<CatalogDbContext>(opt =>
opt.UseNpgsql(builder.Configuration.GetConnectionString("CatalogDb"));
});
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
var app = builder.Build();

@ -0,0 +1,16 @@
using AutoMapper;
using CatalogService.DTOs;
using CatalogService.Entities;
namespace CatalogService.RequestHelpers;
public class MappingProfiles : Profile
{
public MappingProfiles()
{
CreateMap<ExerciceTemplate, ExerciceTemplateDto>();
CreateMap<ExerciceTemplateDto, ExerciceTemplate>();
CreateMap<CreateExerciceTemplateDto, ExerciceTemplate>();
CreateMap<UpdateExerciceTemplateDto, ExerciceTemplate>();
}
}

@ -0,0 +1,9 @@
using System.ComponentModel.DataAnnotations;
namespace Shared.DTOs;
public class DeleteDto
{
[Required]
public required string Id { get; set; }
}

@ -3,7 +3,7 @@ using Shared.Enum;
namespace CatalogService.Entities;
public class Exercice : EntityBase
public class ExerciceTemplate : EntityBase
{
public string Name { get; set; }

@ -0,0 +1,10 @@
namespace Shared.Enum;
public enum EDifficulty
{
None,
Beginner,
Intermediate,
Advanced,
Expert
}

@ -0,0 +1,10 @@
namespace Shared.Enum;
public enum EGoal
{
None,
MuscleGain,
WeightLoss,
Endurance,
Strength,
}

@ -0,0 +1,231 @@
{
"info": {
"_postman_id": "c019ebd5-bed6-49ea-a584-086e91bfe503",
"name": "Catalog Exercices",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Créer un exercice",
"event": [
{
"listen": "test",
"script": {
"exec": [
"var jsonData = pm.response.json();",
"pm.test('Status code is 201', function() {",
" pm.response.to.have.status(201);",
"});",
"pm.test('L\'exercice a un id', function() {",
" pm.expect(jsonData.id).to.be.a('string');",
"});",
"pm.environment.set('exerciceId', jsonData.id);"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"Pompes\",\n \"description\": \"Exercice de musculation pour les pectoraux\",\n \"target\": 1,\n \"imageUrl\": \"images/pompes.jpg\",\n \"videoUrl\": \"https://www.youtube.com/watch?v=IODxDxX7oi4\"\n}"
},
"url": {
"raw": "{{catalogApi}}/api/exercices",
"host": [
"{{catalogApi}}"
],
"path": [
"api",
"exercices"
]
}
}
},
{
"name": "Récupérer l'exercice par id",
"event": [
{
"listen": "test",
"script": {
"exec": [
"var jsonData = pm.response.json();",
"pm.test('Status code is 200', function() {",
" pm.response.to.have.status(200);",
"});",
"pm.test('Nom de l\\'exercice est Pompes', function() {",
" pm.expect(jsonData.name).to.eq('Pompes');",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{catalogApi}}/api/exercices/{{exerciceId}}",
"host": [
"{{catalogApi}}"
],
"path": [
"api",
"exercices",
"{{exerciceId}}"
]
}
}
},
{
"name": "Mettre à jour l'exercice",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test('Status code is 204', function() {",
" pm.response.to.have.status(204);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "PUT",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"id\": \"{{exerciceId}}\",\n \"name\": \"Pompes modifiées\",\n \"description\": \"Exercice modifié\",\n \"target\": 2,\n \"imageUrl\": \"images/pompes_mod.jpg\",\n \"videoUrl\": \"https://www.youtube.com/watch?v=abcd\"\n}"
},
"url": {
"raw": "{{catalogApi}}/api/exercices/{{exerciceId}}",
"host": [
"{{catalogApi}}"
],
"path": [
"api",
"exercices",
"{{exerciceId}}"
]
}
}
},
{
"name": "Vérifier la mise à jour",
"event": [
{
"listen": "test",
"script": {
"exec": [
"var jsonData = pm.response.json();",
"pm.test('Status code is 200', function() {",
" pm.response.to.have.status(200);",
"});",
"pm.test('Nom de l\\'exercice est Pompes modifiées', function() {",
" pm.expect(jsonData.name).to.eq('Pompes modifiées');",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{catalogApi}}/api/exercices/{{exerciceId}}",
"host": [
"{{catalogApi}}"
],
"path": [
"api",
"exercices",
"{{exerciceId}}"
]
}
}
},
{
"name": "Supprimer l'exercice",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test('Status code is 204', function() {",
" pm.response.to.have.status(204);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "DELETE",
"header": [],
"url": {
"raw": "{{catalogApi}}/api/exercices/{{exerciceId}}",
"host": [
"{{catalogApi}}"
],
"path": [
"api",
"exercices",
"{{exerciceId}}"
]
}
}
},
{
"name": "Vérifier la suppression",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test('Status code is 404', function() {",
" pm.response.to.have.status(404);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{catalogApi}}/api/exercices/{{exerciceId}}",
"host": [
"{{catalogApi}}"
],
"path": [
"api",
"exercices",
"{{exerciceId}}"
]
}
}
}
],
"variable": [
{
"key": "catalogApi",
"value": "http://localhost:5000"
}
]
}

@ -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<ActionResult<IEnumerable<ExerciceDto>>> GetAll()
{
var list = await _context.ExerciceInstances
.Include(e => e.ExerciceTemplate)
.ToListAsync();
return Ok(_mapper.Map<IEnumerable<ExerciceDto>>(list));
}
[HttpGet("{id}")]
public async Task<ActionResult<ExerciceDto>> 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<ExerciceDto>(entity);
}
[HttpPost]
public async Task<ActionResult<ExerciceDto>> Create([FromBody] CreateExerciceInstanceDto dto)
{
var entity = _mapper.Map<ExerciceInstance>(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<ExerciceDto>(entity));
}
[HttpPut("{id}")]
public async Task<IActionResult> 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<IActionResult> Delete(string id)
{
var entity = await _context.ExerciceInstances.FindAsync(id);
if (entity == null) return NotFound();
_context.ExerciceInstances.Remove(entity);
await _context.SaveChangesAsync();
return NoContent();
}
}

@ -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; }
}

@ -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; }
}

@ -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; }
}

@ -0,0 +1,204 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<string>("Id")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ImageUrl")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Target")
.HasColumnType("integer");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("VideoUrl")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("ExerciceTemplates");
});
modelBuilder.Entity("TrainingSvc.Entities.ExerciceInstance", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<float>("Duration")
.HasColumnType("real");
b.Property<string>("ExerciceTemplateId")
.HasColumnType("text");
b.Property<bool>("IsDone")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("NbReps")
.HasColumnType("integer");
b.Property<int>("NbSets")
.HasColumnType("integer");
b.Property<float>("RestingTime")
.HasColumnType("real");
b.Property<string>("SessionId")
.IsRequired()
.HasColumnType("text");
b.Property<float?>("Weight")
.HasColumnType("real");
b.HasKey("Id");
b.HasIndex("SessionId");
b.ToTable("ExerciceInstances");
});
modelBuilder.Entity("TrainingSvc.Entities.Session", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<int>("Day")
.HasColumnType("integer");
b.Property<string>("Description")
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int?>("Target")
.HasColumnType("integer");
b.Property<string>("TrainingProgramId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("TrainingProgramId");
b.ToTable("Sessions");
});
modelBuilder.Entity("TrainingSvc.Entities.TrainingProgram", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("Description")
.HasColumnType("text");
b.Property<int>("Difficulty")
.HasColumnType("integer");
b.Property<int>("Goal")
.HasColumnType("integer");
b.Property<string>("Lang")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("NbDays")
.HasColumnType("integer");
b.Property<string>("OwnerId")
.IsRequired()
.HasColumnType("text");
b.Property<int>("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
}
}
}

@ -0,0 +1,126 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace TrainingSvc.Data.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ExerciceTemplates",
columns: table => new
{
Id = table.Column<string>(type: "text", nullable: false),
Name = table.Column<string>(type: "text", nullable: false),
Description = table.Column<string>(type: "text", nullable: false),
Target = table.Column<int>(type: "integer", nullable: false),
ImageUrl = table.Column<string>(type: "text", nullable: false),
VideoUrl = table.Column<string>(type: "text", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(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<string>(type: "text", nullable: false),
Lang = table.Column<string>(type: "text", nullable: false),
Name = table.Column<string>(type: "text", nullable: false),
Description = table.Column<string>(type: "text", nullable: true),
WeekDuration = table.Column<int>(type: "integer", nullable: false),
NbDays = table.Column<int>(type: "integer", nullable: false),
OwnerId = table.Column<string>(type: "text", nullable: false),
Goal = table.Column<int>(type: "integer", nullable: false),
Difficulty = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_TrainingPrograms", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Sessions",
columns: table => new
{
Id = table.Column<string>(type: "text", nullable: false),
Name = table.Column<string>(type: "text", nullable: false),
Description = table.Column<string>(type: "text", nullable: true),
Day = table.Column<int>(type: "integer", nullable: false),
Target = table.Column<int>(type: "integer", nullable: true),
TrainingProgramId = table.Column<string>(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<string>(type: "text", nullable: false),
Name = table.Column<string>(type: "text", nullable: false),
ExerciceTemplateId = table.Column<string>(type: "text", nullable: true),
Duration = table.Column<float>(type: "real", nullable: false),
NbSets = table.Column<int>(type: "integer", nullable: false),
NbReps = table.Column<int>(type: "integer", nullable: false),
RestingTime = table.Column<float>(type: "real", nullable: false),
Weight = table.Column<float>(type: "real", nullable: true),
IsDone = table.Column<bool>(type: "boolean", nullable: false),
SessionId = table.Column<string>(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");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ExerciceInstances");
migrationBuilder.DropTable(
name: "ExerciceTemplates");
migrationBuilder.DropTable(
name: "Sessions");
migrationBuilder.DropTable(
name: "TrainingPrograms");
}
}
}

@ -0,0 +1,201 @@
// <auto-generated />
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<string>("Id")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ImageUrl")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Target")
.HasColumnType("integer");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("VideoUrl")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("ExerciceTemplates");
});
modelBuilder.Entity("TrainingSvc.Entities.ExerciceInstance", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<float>("Duration")
.HasColumnType("real");
b.Property<string>("ExerciceTemplateId")
.HasColumnType("text");
b.Property<bool>("IsDone")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("NbReps")
.HasColumnType("integer");
b.Property<int>("NbSets")
.HasColumnType("integer");
b.Property<float>("RestingTime")
.HasColumnType("real");
b.Property<string>("SessionId")
.IsRequired()
.HasColumnType("text");
b.Property<float?>("Weight")
.HasColumnType("real");
b.HasKey("Id");
b.HasIndex("SessionId");
b.ToTable("ExerciceInstances");
});
modelBuilder.Entity("TrainingSvc.Entities.Session", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<int>("Day")
.HasColumnType("integer");
b.Property<string>("Description")
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int?>("Target")
.HasColumnType("integer");
b.Property<string>("TrainingProgramId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("TrainingProgramId");
b.ToTable("Sessions");
});
modelBuilder.Entity("TrainingSvc.Entities.TrainingProgram", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("Description")
.HasColumnType("text");
b.Property<int>("Difficulty")
.HasColumnType("integer");
b.Property<int>("Goal")
.HasColumnType("integer");
b.Property<string>("Lang")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("NbDays")
.HasColumnType("integer");
b.Property<string>("OwnerId")
.IsRequired()
.HasColumnType("text");
b.Property<int>("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
}
}
}

@ -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<ExerciceTemplate> ExerciceTemplates { get; set; }
public DbSet<ExerciceInstance> ExerciceInstances { get; set; }
public DbSet<Session> Sessions { get; set; }
public DbSet<TrainingProgram> TrainingPrograms { get; set; }
}

@ -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<TrainingDbContext>());
}
private static void SeedData(TrainingDbContext context)
{
context.Database.Migrate();
// 1. Seed ExerciceTemplates si vide
if (!context.ExerciceTemplates.Any())
{
var exercices = new List<ExerciceTemplate>()
{
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<ExerciceInstance>
{
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();
}
}
}

@ -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; }
}

@ -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<ExerciceInstance> Exercices { get; set; } = new Collection<ExerciceInstance>();
}

@ -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<Session> Sessions { get; set; } = new Collection<Session>();
}

@ -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<TrainingDbContext>(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();

@ -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"
}
}
}
}

@ -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<ExerciceInstance, ExerciceDto>()
.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<CreateExerciceInstanceDto, ExerciceInstance>();
CreateMap<UpdateExerciceInstanceDto, ExerciceInstance>();
}
}

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.15" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.15">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,6 @@
@TrainingSvc_HostAddress = http://localhost:5269
GET {{TrainingSvc_HostAddress}}/weatherforecast/
Accept: application/json
###
Loading…
Cancel
Save