diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom.sln b/ExerciseTracker.selnoom/ExerciseTracker.selnoom.sln new file mode 100644 index 00000000..8b3f5d96 --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35818.85 d17.13 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExerciseTracker.selnoom", "ExerciseTracker.selnoom\ExerciseTracker.selnoom.csproj", "{A48F8834-F068-46C0-9489-D572B2F26B8B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A48F8834-F068-46C0-9489-D572B2F26B8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A48F8834-F068-46C0-9489-D572B2F26B8B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A48F8834-F068-46C0-9489-D572B2F26B8B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A48F8834-F068-46C0-9489-D572B2F26B8B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {96C93C50-9C5E-48D5-BD19-1A396D9A4806} + EndGlobalSection +EndGlobal diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Controllers/WeightExerciseController.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Controllers/WeightExerciseController.cs new file mode 100644 index 00000000..6f1ccd33 --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Controllers/WeightExerciseController.cs @@ -0,0 +1,39 @@ +using ExerciseTracker.selnoom.Models; +using ExerciseTracker.selnoom.Services; + +namespace ExerciseTracker.selnoom.Controllers; + +public class WeightExerciseController +{ + private readonly WeightExerciseService _weightExerciseService; + + public WeightExerciseController(WeightExerciseService weightExerciseService) + { + _weightExerciseService = weightExerciseService; + } + + public async Task GetExerciseByIdAsync(int id) + { + return await _weightExerciseService.GetByIdAsync(id); + } + + public async Task> GetExercisesAsync() + { + return await _weightExerciseService.GetExercisesAsync(); + } + + public async Task CreateExerciseAsync(WeightExercise exercise) + { + return await _weightExerciseService.CreateExerciseAsync(exercise); + } + + public async Task UpdateExerciseAsync(WeightExercise exercise) + { + return await _weightExerciseService.UpdateExerciseAsync(exercise); + } + + public async Task DeleteExerciseAsync(int id) + { + return await _weightExerciseService.DeleteExerciseAsync(id); + } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Data/ExerciseDbContext.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Data/ExerciseDbContext.cs new file mode 100644 index 00000000..bb8c0684 --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Data/ExerciseDbContext.cs @@ -0,0 +1,15 @@ +using ExerciseTracker.selnoom.Models; +using Microsoft.EntityFrameworkCore; + +namespace ExerciseTracker.Data +{ + public class ExerciseDbContext : DbContext + { + public ExerciseDbContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Exercises { get; set; } + } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Data/WeightExerciseRepository.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Data/WeightExerciseRepository.cs new file mode 100644 index 00000000..e7c17a18 --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Data/WeightExerciseRepository.cs @@ -0,0 +1,56 @@ +using ExerciseTracker.Data; +using ExerciseTracker.selnoom.Models; +using Microsoft.EntityFrameworkCore; + +namespace ExerciseTracker.selnoom.Data; + +public class WeightExerciseRepository +{ + private readonly ExerciseDbContext _exerciseDbContext; + + public WeightExerciseRepository(ExerciseDbContext exerciseDbContext) + { + _exerciseDbContext = exerciseDbContext; + } + + public async Task GetExerciseByIdAsync(int id) + { + return await _exerciseDbContext.Exercises.FindAsync(id); + } + + public async Task> GetExercisesAsync() + { + return await _exerciseDbContext.Exercises.ToListAsync(); + } + + public async Task CreateExerciseAsync(WeightExercise exercise) + { + var createdExercise = await _exerciseDbContext.Exercises.AddAsync(exercise); + await _exerciseDbContext.SaveChangesAsync(); + return createdExercise.Entity; + } + + public async Task UpdateExerciseAsync(WeightExercise exercise) + { + WeightExercise? savedExercise = await _exerciseDbContext.Exercises.FindAsync(exercise.Id); + + if (savedExercise == null) return null; + + _exerciseDbContext.Entry(savedExercise).CurrentValues.SetValues(exercise); + await _exerciseDbContext.SaveChangesAsync(); + + return savedExercise; + } + + public async Task DeleteExerciseAsync(int id) + { + WeightExercise? savedExercise = await _exerciseDbContext.Exercises.FindAsync(id); + + if (savedExercise == null) return null; + + _exerciseDbContext.Exercises.Remove(savedExercise); + await _exerciseDbContext.SaveChangesAsync(); + + return $"Exercise with Id {id} deleted successfully."; + } +} \ No newline at end of file diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/ExerciseTracker.selnoom.csproj b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/ExerciseTracker.selnoom.csproj new file mode 100644 index 00000000..a00a633f --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/ExerciseTracker.selnoom.csproj @@ -0,0 +1,30 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + PreserveNewest + + + + diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Helpers/Validation.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Helpers/Validation.cs new file mode 100644 index 00000000..54f42503 --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Helpers/Validation.cs @@ -0,0 +1,103 @@ +using Spectre.Console; + +namespace ExerciseTracker.selnoom.Helpers; + +class Validation +{ + public static string? ValidateTimeInput() + { + string timeInput = AnsiConsole.Ask("Enter date (yyyy-MM-dd HH:mm) or 0 to return to menu:"); + + if (timeInput == "0") + { + return null; + } + + DateTime parsedDate; + while (!DateTime.TryParseExact(timeInput, "yyyy-MM-dd HH:mm", null, System.Globalization.DateTimeStyles.None, out parsedDate)) + { + AnsiConsole.MarkupLine("[bold red]Invalid input. Please try again.[/]\n"); + timeInput = AnsiConsole.Ask("Enter date (yyyy-MM-dd HH:mm) or 0 to return to menu:"); + + if (timeInput == "0") + { + return null; + } + } + + return parsedDate.ToString("yyyy-MM-dd HH:mm"); + } + + internal static string? ValidateEndTimeInput(string? startTime) + { + if (startTime == null) + { + return null; + } + + while (true) + { + string? endTime = ValidateTimeInput(); + + if (endTime == null) + { + return null; + } + + if (!DateTime.TryParseExact(startTime, "yyyy-MM-dd HH:mm", null, System.Globalization.DateTimeStyles.None, out DateTime parsedStart)) + { + AnsiConsole.MarkupLine("[bold red]Invalid start time format.[/]\n"); + return null; + } + + if (!DateTime.TryParseExact(endTime, "yyyy-MM-dd HH:mm", null, System.Globalization.DateTimeStyles.None, out DateTime parsedEnd)) + { + AnsiConsole.MarkupLine("[bold red]Invalid end time format. Please try again.[/]\n"); + continue; + } + + if (parsedEnd < parsedStart) + { + AnsiConsole.MarkupLine("[bold red]The end time cannot be before the start time. Please try again.[/]\n"); + } + else + { + return endTime; + } + } + } + + public static double ValidatePositiveDouble(string prompt) + { + double value; + while (true) + { + var input = AnsiConsole.Ask(prompt); + if (double.TryParse(input, out value) && value >= 0) + { + return value; + } + else + { + AnsiConsole.MarkupLine("[red]Please enter a positive number or 0 to return to the menu:[/]"); + } + } + } + + public static int ValidatePositiveInt(string prompt) + { + int value; + while (true) + { + var input = AnsiConsole.Ask(prompt); + if (int.TryParse(input, out value) && value >= 0) + { + return value; + } + else + { + AnsiConsole.MarkupLine("[red]Please enter positive integer or 0 to return:[/]"); + } + } + } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Menu/Menu.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Menu/Menu.cs new file mode 100644 index 00000000..22896336 --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Menu/Menu.cs @@ -0,0 +1,313 @@ +using ExerciseTracker.selnoom.Controllers; +using ExerciseTracker.selnoom.Helpers; +using ExerciseTracker.selnoom.Models; +using Spectre.Console; + +namespace ExerciseTracker.selnoom.Menu; + +public class Menu +{ + private readonly WeightExerciseController _weightExerciseController; + + public Menu(WeightExerciseController weightExerciseController) + { + _weightExerciseController = weightExerciseController; + } + + public async Task ShowMenu() + { + while (true) + { + AnsiConsole.Clear(); + AnsiConsole.Markup("[bold underline]Exercise Tracker[/]\n\n"); + AnsiConsole.WriteLine("Select an option:"); + var menuChoice = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Please select an option:") + .AddChoices(Enum.GetValues()) + ); + + switch (menuChoice) + { + case MainMenuChoices.View: + await ViewExercises(); + break; + case MainMenuChoices.Create: + await CreateExercise(); + break; + case MainMenuChoices.Edit: + await UpdateExercise(); + break; + case MainMenuChoices.Delete: + await DeleteExercise(); + break; + case MainMenuChoices.Exit: + return; + } + } + } + + private async Task ViewExercises() + { + AnsiConsole.Clear(); + + await DisplayExercises(); + } + + public async Task CreateExercise() + { + AnsiConsole.Clear(); + + string employeeName = AnsiConsole.Prompt(new TextPrompt("Enter the exercise name or 0 to return:")); + if (employeeName == "0") return; + + double weight = Validation.ValidatePositiveDouble("Enter the weight used in the exercise or 0 to return:"); + if (weight == 0) return; + + int sets = Validation.ValidatePositiveInt("Enter the number of sets done or 0 to return:"); + if (sets == 0) return; + + int repetitions = Validation.ValidatePositiveInt("Enter the number of repetitions done in each set or 0 to return:"); + if (repetitions == 0) return; + + (string?, string?) times = GetStartAndEndTimes(); + if (times.Item1 == null || times.Item2 == null) return; + + WeightExercise newExercise = new WeightExercise + { + Name = employeeName, + Weight = weight, + Sets = sets, + Repetitions = repetitions, + DateStart = DateTime.Parse(times.Item1), + DateEnd = DateTime.Parse(times.Item2) + }; + + try + { + var createdExercise = await _weightExerciseController.CreateExerciseAsync(newExercise); + if (createdExercise == null) + { + AnsiConsole.MarkupLine("[red]Exercise creation failed.[/]"); + } + else + { + AnsiConsole.MarkupLine("[green]Exercise created successfully![/]"); + } + } + catch (Exception e) + { + AnsiConsole.MarkupLine("[red]There was an error while creating the exercise! Please try again later.[/]"); + } + + AnsiConsole.MarkupLine("\nPress enter to continue"); + AnsiConsole.Prompt(new TextPrompt("").AllowEmpty()); + } + + public async Task UpdateExercise() + { + AnsiConsole.Clear(); + + List exercises = await _weightExerciseController.GetExercisesAsync(); + + if (!exercises.Any()) + { + AnsiConsole.MarkupLine("[red]No exercises registered.[/]"); + AnsiConsole.MarkupLine("\nPress enter to continue"); + AnsiConsole.Prompt(new TextPrompt("").AllowEmpty()); + return; + } + + var selectedExercise = ChooseExercise(exercises); + if (selectedExercise == null) return; + + string employeeName = AnsiConsole.Prompt(new TextPrompt("Enter the new exercise name or 0 to return:")); + if (employeeName == "0") return; + + double weight = Validation.ValidatePositiveDouble("Enter the new weight used in the exercise or 0 to return:"); + if (weight == 0) return; + + int sets = Validation.ValidatePositiveInt("Enter the new number of sets done or 0 to return:"); + if (sets == 0) return; + + int repetitions = Validation.ValidatePositiveInt("Enter the new number of repetitions done in each set or 0 to return:"); + if (repetitions == 0) return; + + (string?, string?) times = GetStartAndEndTimes(); + if (times.Item1 == null || times.Item2 == null) return; + + WeightExercise newExercise = new WeightExercise + { + Id = selectedExercise.Id, + Name = employeeName, + Weight = weight, + Sets = sets, + Repetitions = repetitions, + DateStart = DateTime.Parse(times.Item1), + DateEnd = DateTime.Parse(times.Item2) + }; + + try + { + var updatedExercise = await _weightExerciseController.UpdateExerciseAsync(newExercise); + if (updatedExercise == null) + { + AnsiConsole.MarkupLine("[red]Exercise update failed.[/]"); + } + else + { + AnsiConsole.MarkupLine("[green]Exercise updated successfully![/]"); + } + } + catch (Exception e) + { + AnsiConsole.MarkupLine("[red]There was an error while updating the exercise! Please try again later.[/]"); + } + + AnsiConsole.MarkupLine("\nPress enter to continue"); + AnsiConsole.Prompt(new TextPrompt("").AllowEmpty()); + } + + public async Task DeleteExercise() + { + AnsiConsole.Clear(); + + List exercises = await _weightExerciseController.GetExercisesAsync(); + if (!exercises.Any()) + { + AnsiConsole.MarkupLine("[red]No exercises registered.[/]"); + AnsiConsole.MarkupLine("\nPress enter to continue"); + AnsiConsole.Prompt(new TextPrompt("").AllowEmpty()); + return; + } + + var selectedExercise = ChooseExercise(exercises); + if (selectedExercise == null) return; + + try + { + var deletedExercise = await _weightExerciseController.DeleteExerciseAsync(selectedExercise.Id); + if (deletedExercise == null) + { + AnsiConsole.MarkupLine("[red]Exercise deletion failed.[/]"); + } + else + { + AnsiConsole.MarkupLine("[green]Exercise deleted successfully![/]"); + } + } + catch (Exception e) + { + AnsiConsole.MarkupLine("[red]There was an error while deleting the exercise! Please try again later.[/]"); + } + + AnsiConsole.MarkupLine("\nPress enter to continue"); + AnsiConsole.Prompt(new TextPrompt("").AllowEmpty()); + } + + public static WeightExercise? ChooseExercise(List exercises) + { + try + { + var choices = new List { "None" }; + choices.AddRange(exercises); + + object selected = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Select an exercise (choose 'None' to return to the menu):") + .PageSize(10) + .AddChoices(choices) + .UseConverter(obj => + { + if (obj is string str) + return str; + else if (obj is WeightExercise exercise) + return $"Date Start: {exercise.DateStart}\tName: {exercise.Name}\tDuration: {exercise.Duration}\tSets: {exercise.Sets}\tRepetitions: {exercise.Repetitions}"; + return string.Empty; + }) + ); + + if (selected is string s && s == "None") + { + return null; + } + else if (selected is WeightExercise exercise) + { + return exercise; + } + else + { + return null; + } + } + catch (Exception ex) + { + AnsiConsole.MarkupLine("[red]There was an error when retrieving the exercises! Please try again later.[/]"); + AnsiConsole.MarkupLine("\nPress enter to continue"); + AnsiConsole.Prompt(new TextPrompt("").AllowEmpty()); + return null; + } + } + + public async Task DisplayExercises() + { + try + { + var exercises = await _weightExerciseController.GetExercisesAsync(); + if (!exercises.Any()) + { + AnsiConsole.MarkupLine("[red]No exercises registered.[/]"); + AnsiConsole.MarkupLine("\nPress enter to continue"); + AnsiConsole.Prompt(new TextPrompt("").AllowEmpty()); + return; + } + + var sortedExercises = exercises.OrderBy(ex => ex.DateStart).ToList(); + + AnsiConsole.MarkupLine("[blue bold underline]Exercises:[/]"); + foreach (var exercise in sortedExercises) + { + AnsiConsole.WriteLine($"Date Start: {exercise.DateStart}\tName: {exercise.Name}\tDuration: {exercise.Duration}\tSets: {exercise.Sets}\tRepetitions: {exercise.Repetitions}"); + } + } + catch (Exception e) + { + AnsiConsole.MarkupLine("[red]There was an error while retrieving the exercises! Please try again later.[/]"); + } + + AnsiConsole.MarkupLine("\nPress enter to continue"); + AnsiConsole.Prompt(new TextPrompt("").AllowEmpty()); + } + + internal static (string?, string?) GetStartAndEndTimes() + { + string? startTime; + string? endTime; + + startTime = Validation.ValidateTimeInput(); + if (startTime == null) + { + return (null, null); + } + + AnsiConsole.Clear(); + AnsiConsole.MarkupLine("[bold]Now, type the ending time of your exercise or 0 to return[/]"); + endTime = Validation.ValidateEndTimeInput(startTime); + if (endTime == null) + { + return (null, null); + } + + return (startTime, endTime); + } + + + public enum MainMenuChoices + { + View, + Create, + Edit, + Delete, + Exit + } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250324212905_InitialCreate.Designer.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250324212905_InitialCreate.Designer.cs new file mode 100644 index 00000000..c1eb3080 --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250324212905_InitialCreate.Designer.cs @@ -0,0 +1,56 @@ +// +using System; +using ExerciseTracker.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ExerciseTracker.selnoom.Migrations +{ + [DbContext(typeof(ExerciseDbContext))] + [Migration("20250324212905_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.3"); + + modelBuilder.Entity("ExerciseTracker.selnoom.Models.WeightExercise", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comments") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Repetitions") + .HasColumnType("INTEGER"); + + b.Property("Sets") + .HasColumnType("INTEGER"); + + b.Property("Weight") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.ToTable("Exercises"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250324212905_InitialCreate.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250324212905_InitialCreate.cs new file mode 100644 index 00000000..3665a2a3 --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250324212905_InitialCreate.cs @@ -0,0 +1,40 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ExerciseTracker.selnoom.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Exercises", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Date = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + Weight = table.Column(type: "REAL", nullable: false), + Sets = table.Column(type: "INTEGER", nullable: false), + Repetitions = table.Column(type: "INTEGER", nullable: false), + Comments = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Exercises", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Exercises"); + } + } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250324223334_ExerciseDuration.Designer.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250324223334_ExerciseDuration.Designer.cs new file mode 100644 index 00000000..76554d44 --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250324223334_ExerciseDuration.Designer.cs @@ -0,0 +1,52 @@ +// +using ExerciseTracker.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ExerciseTracker.selnoom.Migrations +{ + [DbContext(typeof(ExerciseDbContext))] + [Migration("20250324223334_ExerciseDuration")] + partial class ExerciseDuration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.3"); + + modelBuilder.Entity("ExerciseTracker.selnoom.Models.WeightExercise", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comments") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Repetitions") + .HasColumnType("INTEGER"); + + b.Property("Sets") + .HasColumnType("INTEGER"); + + b.Property("Weight") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.ToTable("Exercises"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250324223334_ExerciseDuration.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250324223334_ExerciseDuration.cs new file mode 100644 index 00000000..2575bd1d --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250324223334_ExerciseDuration.cs @@ -0,0 +1,30 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ExerciseTracker.selnoom.Migrations +{ + /// + public partial class ExerciseDuration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Date", + table: "Exercises"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Date", + table: "Exercises", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + } + } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250324223712_ExerciseDuration2.Designer.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250324223712_ExerciseDuration2.Designer.cs new file mode 100644 index 00000000..9b1d30cc --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250324223712_ExerciseDuration2.Designer.cs @@ -0,0 +1,52 @@ +// +using ExerciseTracker.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ExerciseTracker.selnoom.Migrations +{ + [DbContext(typeof(ExerciseDbContext))] + [Migration("20250324223712_ExerciseDuration2")] + partial class ExerciseDuration2 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.3"); + + modelBuilder.Entity("ExerciseTracker.selnoom.Models.WeightExercise", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comments") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Repetitions") + .HasColumnType("INTEGER"); + + b.Property("Sets") + .HasColumnType("INTEGER"); + + b.Property("Weight") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.ToTable("Exercises"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250324223712_ExerciseDuration2.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250324223712_ExerciseDuration2.cs new file mode 100644 index 00000000..ca07b670 --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250324223712_ExerciseDuration2.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ExerciseTracker.selnoom.Migrations +{ + /// + public partial class ExerciseDuration2 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250325020803_UpdateMigration.Designer.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250325020803_UpdateMigration.Designer.cs new file mode 100644 index 00000000..83183828 --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250325020803_UpdateMigration.Designer.cs @@ -0,0 +1,62 @@ +// +using System; +using ExerciseTracker.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ExerciseTracker.selnoom.Migrations +{ + [DbContext(typeof(ExerciseDbContext))] + [Migration("20250325020803_UpdateMigration")] + partial class UpdateMigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.3"); + + modelBuilder.Entity("ExerciseTracker.selnoom.Models.WeightExercise", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comments") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DateEnd") + .HasColumnType("TEXT"); + + b.Property("DateStart") + .HasColumnType("TEXT"); + + b.Property("Duration") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Repetitions") + .HasColumnType("INTEGER"); + + b.Property("Sets") + .HasColumnType("INTEGER"); + + b.Property("Weight") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.ToTable("Exercises"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250325020803_UpdateMigration.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250325020803_UpdateMigration.cs new file mode 100644 index 00000000..a5149d20 --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250325020803_UpdateMigration.cs @@ -0,0 +1,52 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ExerciseTracker.selnoom.Migrations +{ + /// + public partial class UpdateMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "DateEnd", + table: "Exercises", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "DateStart", + table: "Exercises", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "Duration", + table: "Exercises", + type: "TEXT", + nullable: false, + defaultValue: new TimeSpan(0, 0, 0, 0, 0)); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DateEnd", + table: "Exercises"); + + migrationBuilder.DropColumn( + name: "DateStart", + table: "Exercises"); + + migrationBuilder.DropColumn( + name: "Duration", + table: "Exercises"); + } + } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250325133021_RemovedWeightComments.Designer.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250325133021_RemovedWeightComments.Designer.cs new file mode 100644 index 00000000..d6624e22 --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250325133021_RemovedWeightComments.Designer.cs @@ -0,0 +1,58 @@ +// +using System; +using ExerciseTracker.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ExerciseTracker.selnoom.Migrations +{ + [DbContext(typeof(ExerciseDbContext))] + [Migration("20250325133021_RemovedWeightComments")] + partial class RemovedWeightComments + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.3"); + + modelBuilder.Entity("ExerciseTracker.selnoom.Models.WeightExercise", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateEnd") + .HasColumnType("TEXT"); + + b.Property("DateStart") + .HasColumnType("TEXT"); + + b.Property("Duration") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Repetitions") + .HasColumnType("INTEGER"); + + b.Property("Sets") + .HasColumnType("INTEGER"); + + b.Property("Weight") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.ToTable("Exercises"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250325133021_RemovedWeightComments.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250325133021_RemovedWeightComments.cs new file mode 100644 index 00000000..05eade1b --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250325133021_RemovedWeightComments.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ExerciseTracker.selnoom.Migrations +{ + /// + public partial class RemovedWeightComments : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Comments", + table: "Exercises"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Comments", + table: "Exercises", + type: "TEXT", + nullable: false, + defaultValue: ""); + } + } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250325134923_ChangedDurationCalc.Designer.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250325134923_ChangedDurationCalc.Designer.cs new file mode 100644 index 00000000..77f42537 --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250325134923_ChangedDurationCalc.Designer.cs @@ -0,0 +1,55 @@ +// +using System; +using ExerciseTracker.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ExerciseTracker.selnoom.Migrations +{ + [DbContext(typeof(ExerciseDbContext))] + [Migration("20250325134923_ChangedDurationCalc")] + partial class ChangedDurationCalc + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.3"); + + modelBuilder.Entity("ExerciseTracker.selnoom.Models.WeightExercise", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateEnd") + .HasColumnType("TEXT"); + + b.Property("DateStart") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Repetitions") + .HasColumnType("INTEGER"); + + b.Property("Sets") + .HasColumnType("INTEGER"); + + b.Property("Weight") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.ToTable("Exercises"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250325134923_ChangedDurationCalc.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250325134923_ChangedDurationCalc.cs new file mode 100644 index 00000000..92072a45 --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/20250325134923_ChangedDurationCalc.cs @@ -0,0 +1,30 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ExerciseTracker.selnoom.Migrations +{ + /// + public partial class ChangedDurationCalc : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Duration", + table: "Exercises"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Duration", + table: "Exercises", + type: "TEXT", + nullable: false, + defaultValue: new TimeSpan(0, 0, 0, 0, 0)); + } + } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/ExerciseDbContextModelSnapshot.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/ExerciseDbContextModelSnapshot.cs new file mode 100644 index 00000000..a6802fa1 --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Migrations/ExerciseDbContextModelSnapshot.cs @@ -0,0 +1,52 @@ +// +using System; +using ExerciseTracker.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ExerciseTracker.selnoom.Migrations +{ + [DbContext(typeof(ExerciseDbContext))] + partial class ExerciseDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.3"); + + modelBuilder.Entity("ExerciseTracker.selnoom.Models.WeightExercise", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateEnd") + .HasColumnType("TEXT"); + + b.Property("DateStart") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Repetitions") + .HasColumnType("INTEGER"); + + b.Property("Sets") + .HasColumnType("INTEGER"); + + b.Property("Weight") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.ToTable("Exercises"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Models/WeightExercise.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Models/WeightExercise.cs new file mode 100644 index 00000000..b5c963d8 --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Models/WeightExercise.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace ExerciseTracker.selnoom.Models; + +public class WeightExercise +{ + public int Id { get; set; } + public DateTime DateStart { get; set; } + public DateTime DateEnd { get; set; } + + [NotMapped] + public TimeSpan Duration + { + get { return DateEnd - DateStart; } + } + + public string Name { get; set; } = string.Empty; + public double Weight { get; set; } + public int Sets { get; set; } + public int Repetitions { get; set; } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Program.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Program.cs new file mode 100644 index 00000000..f2c568c4 --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Program.cs @@ -0,0 +1,42 @@ +using ExerciseTracker.Data; +using ExerciseTracker.selnoom.Controllers; +using ExerciseTracker.selnoom.Data; +using ExerciseTracker.selnoom.Menu; +using ExerciseTracker.selnoom.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +var host = Host.CreateDefaultBuilder(args) + .ConfigureLogging(logging => + { + logging.ClearProviders(); + logging.AddConsole(); + logging.SetMinimumLevel(LogLevel.Warning); + }) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); + }) + .ConfigureServices((context, services) => + { + var connectionString = context.Configuration.GetConnectionString("DefaultConnection"); + services.AddDbContext(options => + options.UseSqlite(connectionString)); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + }) + .Build(); + +using (var scope = host.Services.CreateScope()) +{ + var context = scope.ServiceProvider.GetRequiredService(); + context.Database.Migrate(); + + var menu = scope.ServiceProvider.GetRequiredService(); + await menu.ShowMenu(); +} \ No newline at end of file diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Services/WeightExerciseService.cs b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Services/WeightExerciseService.cs new file mode 100644 index 00000000..380f7bfa --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/Services/WeightExerciseService.cs @@ -0,0 +1,79 @@ +using ExerciseTracker.selnoom.Data; +using ExerciseTracker.selnoom.Models; + +namespace ExerciseTracker.selnoom.Services; + +public class WeightExerciseService +{ + private readonly WeightExerciseRepository _weightExerciseRepository; + + public WeightExerciseService(WeightExerciseRepository weightExerciseRepository) + { + _weightExerciseRepository = weightExerciseRepository; + } + + public async Task GetByIdAsync(int id) + { + return await _weightExerciseRepository.GetExerciseByIdAsync(id); + } + + public async Task> GetExercisesAsync() + { + return await _weightExerciseRepository.GetExercisesAsync(); + } + + public async Task CreateExerciseAsync(WeightExercise exercise) + { + if (exercise.DateEnd < exercise.DateStart) + { + throw new ArgumentException("Start date must be before the end date."); + } + + if (exercise.Weight <= 0) + { + throw new ArgumentException("Weight must be greater than 0."); + } + + if (exercise.Sets <= 0) + { + throw new ArgumentException("Sets must be greater than 0."); + } + + if (exercise.Repetitions <= 0) + { + throw new ArgumentException("Repetitions must be greater than 0."); + } + + return await _weightExerciseRepository.CreateExerciseAsync(exercise); + } + + public async Task UpdateExerciseAsync(WeightExercise exercise) + { + if (exercise.DateEnd < exercise.DateStart) + { + throw new ArgumentException("Start date must be before the end date."); + } + + if (exercise.Weight <= 0) + { + throw new ArgumentException("Weight must be greater than 0."); + } + + if (exercise.Sets <= 0) + { + throw new ArgumentException("Sets must be greater than 0."); + } + + if (exercise.Repetitions <= 0) + { + throw new ArgumentException("Repetitions must be greater than 0."); + } + + return await _weightExerciseRepository.UpdateExerciseAsync(exercise); + } + + public async Task DeleteExerciseAsync(int id) + { + return await _weightExerciseRepository.DeleteExerciseAsync(id); + } +} diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/appsettings.json b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/appsettings.json new file mode 100644 index 00000000..38dcd8ee --- /dev/null +++ b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Data Source=exercise.db" + } +} \ No newline at end of file diff --git a/ExerciseTracker.selnoom/ExerciseTracker.selnoom/exercise.db b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/exercise.db new file mode 100644 index 00000000..afec0390 Binary files /dev/null and b/ExerciseTracker.selnoom/ExerciseTracker.selnoom/exercise.db differ