From 6affee7b8c5c8e3f194faadad657b9abc192c086 Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Wed, 5 Mar 2025 17:29:37 +0100 Subject: [PATCH 001/183] Initial Migration --- .gitignore | 1 + .../Models/BackendModels/Favorit/Favorit.cs | 10 ++++++++++ .../Models/BackendModels/File/MediaFile.cs | 11 +++++++++++ .../File/MediaFileImageInstruction.cs | 9 +++++++++ .../BackendModels/File/MediaFileImageRecipe.cs | 9 +++++++++ .../BackendModels/File/MediaFileVideoRecipe.cs | 8 ++++++++ .../Models/BackendModels/Ingredient/Ingredient.cs | 7 ++++++- .../IngredientsShoppingList.cs | 12 ++++++++++++ .../BackendModels/Instruction/Instruction.cs | 2 ++ .../Models/BackendModels/Recipe/Difficulty.cs | 12 ++++++++++++ .../Models/BackendModels/Recipe/Recipe.cs | 14 +++++++++++--- .../RecipeIngredient/RecipeIngredient.cs | 5 +++-- .../BackendModels/Shoppinglist/ShoppingList.cs | 11 +++++++++++ .../Models/BackendModels/Unit/Unit.cs | 14 ++++++++------ 14 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 Francesco.Recipes.World/Models/BackendModels/Favorit/Favorit.cs create mode 100644 Francesco.Recipes.World/Models/BackendModels/File/MediaFile.cs create mode 100644 Francesco.Recipes.World/Models/BackendModels/File/MediaFileImageInstruction.cs create mode 100644 Francesco.Recipes.World/Models/BackendModels/File/MediaFileImageRecipe.cs create mode 100644 Francesco.Recipes.World/Models/BackendModels/File/MediaFileVideoRecipe.cs create mode 100644 Francesco.Recipes.World/Models/BackendModels/IngredientShoppingList/IngredientsShoppingList.cs create mode 100644 Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs create mode 100644 Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs diff --git a/.gitignore b/.gitignore index 61101b4..ad9a362 100644 --- a/.gitignore +++ b/.gitignore @@ -397,3 +397,4 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml src/Recruitment.Tool.xml +/Francesco.Recipes.World/Migrations/20250304095839_AddFewNewBackendModels.Designer.cs diff --git a/Francesco.Recipes.World/Models/BackendModels/Favorit/Favorit.cs b/Francesco.Recipes.World/Models/BackendModels/Favorit/Favorit.cs new file mode 100644 index 0000000..0e91d0e --- /dev/null +++ b/Francesco.Recipes.World/Models/BackendModels/Favorit/Favorit.cs @@ -0,0 +1,10 @@ +namespace Francesco.Recipes.World.Models.BackendModels.Favorit +{ + using Francesco.Recipes.World.Models.BackendModels.Recipe; + public class Favorit + { + public Guid Id { get; set; } + public DateTime CreatedAt { get; set; } + public virtual Recipe Recipe { get; set; } = new(); + } +} diff --git a/Francesco.Recipes.World/Models/BackendModels/File/MediaFile.cs b/Francesco.Recipes.World/Models/BackendModels/File/MediaFile.cs new file mode 100644 index 0000000..888a735 --- /dev/null +++ b/Francesco.Recipes.World/Models/BackendModels/File/MediaFile.cs @@ -0,0 +1,11 @@ +using Francesco.Recipes.World.Models.BackendModels.File; +using Francesco.Recipes.World.Models.BackendModels.Instruction; +using Francesco.Recipes.World.Models.BackendModels.Recipe; + +public abstract class MediaFile +{ + public Guid Id { get; set; } = Guid.NewGuid(); + public string? FileName { get; set; } + public string? MimeType { get; set; } + public byte[]? Data { get; set; } +} diff --git a/Francesco.Recipes.World/Models/BackendModels/File/MediaFileImageInstruction.cs b/Francesco.Recipes.World/Models/BackendModels/File/MediaFileImageInstruction.cs new file mode 100644 index 0000000..70ac229 --- /dev/null +++ b/Francesco.Recipes.World/Models/BackendModels/File/MediaFileImageInstruction.cs @@ -0,0 +1,9 @@ +namespace Francesco.Recipes.World.Models.BackendModels.File +{ + using Francesco.Recipes.World.Models.BackendModels.Instruction; + + public class MediaFileImageInstruction : MediaFile + { + public virtual Instruction Instruction { get; set; } + } +} diff --git a/Francesco.Recipes.World/Models/BackendModels/File/MediaFileImageRecipe.cs b/Francesco.Recipes.World/Models/BackendModels/File/MediaFileImageRecipe.cs new file mode 100644 index 0000000..fdac8c4 --- /dev/null +++ b/Francesco.Recipes.World/Models/BackendModels/File/MediaFileImageRecipe.cs @@ -0,0 +1,9 @@ +namespace Francesco.Recipes.World.Models.BackendModels.File +{ + using Francesco.Recipes.World.Models.BackendModels.Recipe; + + public class MediaFileImageRecipe : MediaFile + { + public virtual Recipe Recipe { get; set; } + } +} diff --git a/Francesco.Recipes.World/Models/BackendModels/File/MediaFileVideoRecipe.cs b/Francesco.Recipes.World/Models/BackendModels/File/MediaFileVideoRecipe.cs new file mode 100644 index 0000000..14dd710 --- /dev/null +++ b/Francesco.Recipes.World/Models/BackendModels/File/MediaFileVideoRecipe.cs @@ -0,0 +1,8 @@ +namespace Francesco.Recipes.World.Models.BackendModels.File +{ + using Francesco.Recipes.World.Models.BackendModels.Recipe; + public class MediaFileVideoRecipe : MediaFile + { + public virtual Recipe Recipe { get; set; } + } +} diff --git a/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs b/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs index 96f377f..a657e98 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs @@ -1,11 +1,16 @@ namespace Francesco.Recipes.World.Models.BackendModels.Ingredient { + using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; + using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; using Francesco.Recipes.World.Models.BackendModels.Unit; + public class Ingredient { public Guid Id { get; set; } public string Name { get; set; } - public virtual Unit Unit { get; set; } = new(); public int Quantity { get; set; } + public virtual ICollection RecipeIngredients { get; set; } = new List(); + public virtual ICollection IngredientShoppingLists { get; set; } = new List(); + public virtual Unit Unit { get; set; } = new(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/IngredientShoppingList/IngredientsShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/IngredientShoppingList/IngredientsShoppingList.cs new file mode 100644 index 0000000..c527693 --- /dev/null +++ b/Francesco.Recipes.World/Models/BackendModels/IngredientShoppingList/IngredientsShoppingList.cs @@ -0,0 +1,12 @@ +namespace Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList +{ + using Francesco.Recipes.World.Models.BackendModels.Ingredient; + using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; + + public class IngredientsShoppingList + { + public Guid Id { get; set; } + public ShoppingList Shoppinglist { get; set; } = new(); + public Ingredient Ingredient { get; set; } = new(); + } +} diff --git a/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs b/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs index 36a2bd1..ee67e89 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs @@ -1,5 +1,6 @@ namespace Francesco.Recipes.World.Models.BackendModels.Instruction { + using Francesco.Recipes.World.Models.BackendModels.File; using Francesco.Recipes.World.Models.BackendModels.Recipe; public class Instruction { @@ -7,5 +8,6 @@ public string Description { get; set; } public string Number { get; set; } public virtual Recipe Recipe { get; set; } = new(); + public ICollection MediaFileImageInstructions { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs b/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs new file mode 100644 index 0000000..3c3036e --- /dev/null +++ b/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs @@ -0,0 +1,12 @@ +namespace Francesco.Recipes.World.Models.BackendModels.Recipe +{ + public enum Difficulty + { + VeryEasy = 1, + Easy = 2, + Medium = 3, + Hard = 4, + Expert = 5 + } + +} diff --git a/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs b/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs index 1712072..2ce3413 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs @@ -1,5 +1,8 @@ namespace Francesco.Recipes.World.Models.BackendModels.Recipe { + using Francesco.Recipes.World.Models.BackendModels.Category; + using Francesco.Recipes.World.Models.BackendModels.Favorit; + using Francesco.Recipes.World.Models.BackendModels.File; using Francesco.Recipes.World.Models.BackendModels.Instruction; using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; @@ -8,11 +11,16 @@ public Guid Id { get; set; } public string Name { get; set; } public string Description { get; set; } - public string Difficulty { get; set; } + public Difficulty Difficulty { get; set; } public int Servings { get; set; } public TimeSpan PreparationTime { get; set; } public TimeSpan CookingTime { get; set; } - public virtual ICollection RecipeIngredients { get; set; } = new List(); - public virtual ICollection Instructions { get; set; } = new List(); + public bool IsFavorite { get; set; } + public virtual ICollection RecipeIngredients { get; set; } = new List(); + public virtual ICollection Instructions { get; set; } = new List(); + public virtual ICollection Images { get; set; } = new List(); + public virtual ICollection Videos { get; set; } = new List(); + public virtual Category Category { get; set; } = new(); + public virtual Favorit Favorit { get; set; } = new(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/RecipeIngredient/RecipeIngredient.cs b/Francesco.Recipes.World/Models/BackendModels/RecipeIngredient/RecipeIngredient.cs index 79dea52..84ddb82 100644 --- a/Francesco.Recipes.World/Models/BackendModels/RecipeIngredient/RecipeIngredient.cs +++ b/Francesco.Recipes.World/Models/BackendModels/RecipeIngredient/RecipeIngredient.cs @@ -5,7 +5,8 @@ public class RecipeIngredient { public Guid Id { get; set; } - public virtual Recipe Recipe { get; set; } = new (); - public virtual Ingredient Ingredient { get; set; } = new (); + public Guid RecipeId { get; set; } + public virtual Recipe Recipe { get; set; } = new(); + public virtual Ingredient Ingredient { get; set; } = new(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs new file mode 100644 index 0000000..bcb1629 --- /dev/null +++ b/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs @@ -0,0 +1,11 @@ +using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; + +namespace Francesco.Recipes.World.Models.BackendModels.Shoppinglist +{ + public class ShoppingList + { + public Guid Id { get; set; } + public DateTime CreatedAt { get; set; } + public virtual ICollection IngredientsShoppingLists { get; set; } = new List(); + } +} diff --git a/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs b/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs index 19ba712..3806b7c 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs @@ -1,11 +1,13 @@ -namespace Francesco.Recipes.World.Models.BackendModels.Unit + + +namespace Francesco.Recipes.World.Models.BackendModels.Unit { using Francesco.Recipes.World.Models.BackendModels.Ingredient; - public class Unit { - public Guid Id { get; set; } - public string Name { get; set; } - public virtual ICollection Recipes { get; set; } = new List(); - } + public Guid Id{ get; set; } + public string Name{ get; set; } + public string Symbol { get; set; } + public virtual ICollection Ingredients { get; set; } = new List(); + } } From 449533cf0b65f1f17c5727735f300b627d07ab28 Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Tue, 18 Mar 2025 12:02:01 +0100 Subject: [PATCH 002/183] Create all Models --- ...0250114131454_InitialMigration.Designer.cs | 230 -------- .../20250114131454_InitialMigration.cs | 184 ------- ...0250318105823_InitialMigration.Designer.cs | 500 ++++++++++++++++++ .../20250318105823_InitialMigration.cs | 342 ++++++++++++ ...escosRecipesWorldDbContextModelSnapshot.cs | 320 ++++++++++- .../Models/BackendModels/Category/Category.cs | 2 +- .../Models/BackendModels/Favorit/Favorit.cs | 2 +- .../Models/BackendModels/File/MediaFile.cs | 11 - .../File/MediaFileImageInstruction.cs | 9 - .../File/MediaFileImageRecipe.cs | 9 - .../File/MediaFileVideoRecipe.cs | 8 - .../BackendModels/ITimeStampedEntity.cs | 9 + .../BackendModels/Ingredient/Ingredient.cs | 4 +- .../BackendModels/Instruction/Instruction.cs | 8 +- .../BackendModels/MediaFile/MediaFile.cs | 17 + .../Models/BackendModels/Recipe/Recipe.cs | 11 +- .../RecipeIngredient/RecipeIngredient.cs | 4 +- .../Models/BackendModels/Unit/Unit.cs | 7 +- 18 files changed, 1182 insertions(+), 495 deletions(-) delete mode 100644 Francesco.Recipes.World/Migrations/20250114131454_InitialMigration.Designer.cs delete mode 100644 Francesco.Recipes.World/Migrations/20250114131454_InitialMigration.cs create mode 100644 Francesco.Recipes.World/Migrations/20250318105823_InitialMigration.Designer.cs create mode 100644 Francesco.Recipes.World/Migrations/20250318105823_InitialMigration.cs delete mode 100644 Francesco.Recipes.World/Models/BackendModels/File/MediaFile.cs delete mode 100644 Francesco.Recipes.World/Models/BackendModels/File/MediaFileImageInstruction.cs delete mode 100644 Francesco.Recipes.World/Models/BackendModels/File/MediaFileImageRecipe.cs delete mode 100644 Francesco.Recipes.World/Models/BackendModels/File/MediaFileVideoRecipe.cs create mode 100644 Francesco.Recipes.World/Models/BackendModels/ITimeStampedEntity.cs create mode 100644 Francesco.Recipes.World/Models/BackendModels/MediaFile/MediaFile.cs diff --git a/Francesco.Recipes.World/Migrations/20250114131454_InitialMigration.Designer.cs b/Francesco.Recipes.World/Migrations/20250114131454_InitialMigration.Designer.cs deleted file mode 100644 index 77d228f..0000000 --- a/Francesco.Recipes.World/Migrations/20250114131454_InitialMigration.Designer.cs +++ /dev/null @@ -1,230 +0,0 @@ -// -using System; -using Francesco.Recipes.World.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace Francesco.Recipes.World.Migrations -{ - [DbContext(typeof(FrancescosRecipesWorldDbContext))] - [Migration("20250114131454_InitialMigration")] - partial class InitialMigration - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Categories"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("UnitId"); - - b.ToTable("Ingredients"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Description") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Number") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("RecipeId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("RecipeId"); - - b.ToTable("Instructions"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("CategoryId") - .HasColumnType("uniqueidentifier"); - - b.Property("CookingTime") - .HasColumnType("time"); - - b.Property("Description") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Difficulty") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("PreparationTime") - .HasColumnType("time"); - - b.Property("Servings") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.ToTable("Recipes"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("IngredientId") - .HasColumnType("uniqueidentifier"); - - b.Property("RecipeId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("IngredientId"); - - b.HasIndex("RecipeId"); - - b.ToTable("RecipeIngredients"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Unit"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b => - { - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit") - .WithMany("Recipes") - .HasForeignKey("UnitId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Unit"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => - { - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") - .WithMany("Instructions") - .HasForeignKey("RecipeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Recipe"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => - { - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", null) - .WithMany("Recipes") - .HasForeignKey("CategoryId"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b => - { - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient") - .WithMany() - .HasForeignKey("IngredientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") - .WithMany("RecipeIngredients") - .HasForeignKey("RecipeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ingredient"); - - b.Navigation("Recipe"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b => - { - b.Navigation("Recipes"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => - { - b.Navigation("Instructions"); - - b.Navigation("RecipeIngredients"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b => - { - b.Navigation("Recipes"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Francesco.Recipes.World/Migrations/20250114131454_InitialMigration.cs b/Francesco.Recipes.World/Migrations/20250114131454_InitialMigration.cs deleted file mode 100644 index b90b0ed..0000000 --- a/Francesco.Recipes.World/Migrations/20250114131454_InitialMigration.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Francesco.Recipes.World.Migrations -{ - /// - public partial class InitialMigration : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - if (migrationBuilder == null) - { - throw new ArgumentNullException(nameof(migrationBuilder)); - } - - migrationBuilder.CreateTable( - name: "Categories", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(max)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Categories", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Unit", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(max)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Unit", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Recipes", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(max)", nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: false), - Difficulty = table.Column(type: "nvarchar(max)", nullable: false), - Servings = table.Column(type: "int", nullable: false), - PreparationTime = table.Column(type: "time", nullable: false), - CookingTime = table.Column(type: "time", nullable: false), - CategoryId = table.Column(type: "uniqueidentifier", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Recipes", x => x.Id); - table.ForeignKey( - name: "FK_Recipes_Categories_CategoryId", - column: x => x.CategoryId, - principalTable: "Categories", - principalColumn: "Id"); - }); - - migrationBuilder.CreateTable( - name: "Ingredients", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(max)", nullable: false), - UnitId = table.Column(type: "uniqueidentifier", nullable: false), - Quantity = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Ingredients", x => x.Id); - table.ForeignKey( - name: "FK_Ingredients_Unit_UnitId", - column: x => x.UnitId, - principalTable: "Unit", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Instructions", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: false), - Number = table.Column(type: "nvarchar(max)", nullable: false), - RecipeId = table.Column(type: "uniqueidentifier", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Instructions", x => x.Id); - table.ForeignKey( - name: "FK_Instructions_Recipes_RecipeId", - column: x => x.RecipeId, - principalTable: "Recipes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "RecipeIngredients", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - RecipeId = table.Column(type: "uniqueidentifier", nullable: false), - IngredientId = table.Column(type: "uniqueidentifier", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_RecipeIngredients", x => x.Id); - table.ForeignKey( - name: "FK_RecipeIngredients_Ingredients_IngredientId", - column: x => x.IngredientId, - principalTable: "Ingredients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_RecipeIngredients_Recipes_RecipeId", - column: x => x.RecipeId, - principalTable: "Recipes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_Ingredients_UnitId", - table: "Ingredients", - column: "UnitId"); - - migrationBuilder.CreateIndex( - name: "IX_Instructions_RecipeId", - table: "Instructions", - column: "RecipeId"); - - migrationBuilder.CreateIndex( - name: "IX_RecipeIngredients_IngredientId", - table: "RecipeIngredients", - column: "IngredientId"); - - migrationBuilder.CreateIndex( - name: "IX_RecipeIngredients_RecipeId", - table: "RecipeIngredients", - column: "RecipeId"); - - migrationBuilder.CreateIndex( - name: "IX_Recipes_CategoryId", - table: "Recipes", - column: "CategoryId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - if (migrationBuilder == null) - { - throw new ArgumentNullException(nameof(migrationBuilder)); - } - - migrationBuilder.DropTable( - name: "Instructions"); - - migrationBuilder.DropTable( - name: "RecipeIngredients"); - - migrationBuilder.DropTable( - name: "Ingredients"); - - migrationBuilder.DropTable( - name: "Recipes"); - - migrationBuilder.DropTable( - name: "Unit"); - - migrationBuilder.DropTable( - name: "Categories"); - } - } -} diff --git a/Francesco.Recipes.World/Migrations/20250318105823_InitialMigration.Designer.cs b/Francesco.Recipes.World/Migrations/20250318105823_InitialMigration.Designer.cs new file mode 100644 index 0000000..ac67bcb --- /dev/null +++ b/Francesco.Recipes.World/Migrations/20250318105823_InitialMigration.Designer.cs @@ -0,0 +1,500 @@ +// +using System; +using Francesco.Recipes.World.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Francesco.Recipes.World.Migrations +{ + [DbContext(typeof(FrancescosRecipesWorldDbContext))] + [Migration("20250318105823_InitialMigration")] + partial class InitialMigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + + b.HasData( + new + { + Id = new Guid("b248244f-f21c-4555-a14d-5dd49a2717cf"), + Name = "Vorspeisen & Snacks" + }, + new + { + Id = new Guid("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"), + Name = "Erste Gänge" + }, + new + { + Id = new Guid("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"), + Name = "Hauptgerichte" + }, + new + { + Id = new Guid("90deec39-dcd0-422d-9018-ac8389e332e1"), + Name = "Desserts & Süßspeisen" + }, + new + { + Id = new Guid("28e39168-701a-4084-81da-d96c987c462f"), + Name = "Beilagen & Salate" + }, + new + { + Id = new Guid("20585b74-4805-4aff-a6df-aa6b7af04ff1"), + Name = "Kuchen" + }, + new + { + Id = new Guid("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"), + Name = "Hefegebäck & Brot" + }, + new + { + Id = new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"), + Name = "Soßen & Saucen" + }, + new + { + Id = new Guid("23b1c740-e427-44f9-a6ea-d33d3f30f05a"), + Name = "Marmeladen & Eingemachtes" + }, + new + { + Id = new Guid("0a91a200-dc76-4e00-b38c-b38cab5b69d7"), + Name = "Getränke" + }); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Favorits"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Ingredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.IngredientsShoppingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("IngredientId") + .HasColumnType("uniqueidentifier"); + + b.Property("ShoppinglistId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IngredientId"); + + b.HasIndex("ShoppinglistId"); + + b.ToTable("IngredientsShoppingLists"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Number") + .HasColumnType("nvarchar(max)"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RecipeId"); + + b.ToTable("Instructions"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Data") + .HasColumnType("varbinary(max)"); + + b.Property("FileName") + .HasColumnType("nvarchar(max)"); + + b.Property("InstructionId") + .HasColumnType("uniqueidentifier"); + + b.Property("MimeType") + .HasColumnType("nvarchar(max)"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("InstructionId"); + + b.HasIndex("RecipeId"); + + b.ToTable("MediaFiles"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("CookingTime") + .HasColumnType("time"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Difficulty") + .HasColumnType("int"); + + b.Property("FavoritId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsFavorite") + .HasColumnType("bit"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("PreparationTime") + .HasColumnType("time"); + + b.Property("Servings") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("FavoritId"); + + b.ToTable("Recipes"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("IngredientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.Property("UnitId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IngredientId"); + + b.HasIndex("RecipeId"); + + b.HasIndex("UnitId"); + + b.ToTable("RecipeIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("ShoppingLists"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Symbol") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Units"); + + b.HasData( + new + { + Id = new Guid("62eb2c11-8768-46fb-9db0-c77f940bb4aa"), + Name = "liter", + Symbol = "l" + }, + new + { + Id = new Guid("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"), + Name = "gramm", + Symbol = "g" + }, + new + { + Id = new Guid("7e3d1b86-ac48-45d6-814e-d3492a86db1d"), + Name = "kilogramm", + Symbol = "kg" + }, + new + { + Id = new Guid("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"), + Name = "stücke", + Symbol = "stk" + }, + new + { + Id = new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"), + Name = "blatt", + Symbol = "blatt" + }, + new + { + Id = new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"), + Name = "messerspitze", + Symbol = "msp" + }, + new + { + Id = new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"), + Name = "stange", + Symbol = "stange" + }, + new + { + Id = new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"), + Name = "bund", + Symbol = "bund" + }, + new + { + Id = new Guid("66556e0e-eb2b-4bc3-9a56-135dd508ed09"), + Name = "zehe", + Symbol = "zehe" + }, + new + { + Id = new Guid("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"), + Name = "teelöffel", + Symbol = "TL" + }, + new + { + Id = new Guid("24a91b89-3389-4465-89e4-2f70d2ea6fd7"), + Name = "esslöffel", + Symbol = "EL" + }); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.IngredientsShoppingList", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient") + .WithMany("IngredientShoppingLists") + .HasForeignKey("IngredientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "Shoppinglist") + .WithMany("IngredientsShoppingLists") + .HasForeignKey("ShoppinglistId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ingredient"); + + b.Navigation("Shoppinglist"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany("Instructions") + .HasForeignKey("RecipeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Recipe"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", "Instruction") + .WithMany("MediaFiles") + .HasForeignKey("InstructionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany("MediaFiles") + .HasForeignKey("RecipeId"); + + b.Navigation("Instruction"); + + b.Navigation("Recipe"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", null) + .WithMany("Recipes") + .HasForeignKey("CategoryId"); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorit") + .WithMany("Recipe") + .HasForeignKey("FavoritId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Favorit"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient") + .WithMany("RecipeIngredients") + .HasForeignKey("IngredientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany("RecipeIngredients") + .HasForeignKey("RecipeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit") + .WithMany("RecipeIngredient") + .HasForeignKey("UnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ingredient"); + + b.Navigation("Recipe"); + + b.Navigation("Unit"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b => + { + b.Navigation("Recipes"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b => + { + b.Navigation("Recipe"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b => + { + b.Navigation("IngredientShoppingLists"); + + b.Navigation("RecipeIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => + { + b.Navigation("MediaFiles"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => + { + b.Navigation("Instructions"); + + b.Navigation("MediaFiles"); + + b.Navigation("RecipeIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => + { + b.Navigation("IngredientsShoppingLists"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b => + { + b.Navigation("RecipeIngredient"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Francesco.Recipes.World/Migrations/20250318105823_InitialMigration.cs b/Francesco.Recipes.World/Migrations/20250318105823_InitialMigration.cs new file mode 100644 index 0000000..1e8d90d --- /dev/null +++ b/Francesco.Recipes.World/Migrations/20250318105823_InitialMigration.cs @@ -0,0 +1,342 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace Francesco.Recipes.World.Migrations +{ + /// + public partial class InitialMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + { + throw new ArgumentNullException(nameof(migrationBuilder)); + } + migrationBuilder.CreateTable( + name: "Categories", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Categories", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Favorits", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Favorits", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Ingredients", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Ingredients", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ShoppingLists", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ShoppingLists", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Units", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(max)", nullable: true), + Symbol = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Units", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Recipes", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(max)", nullable: true), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Difficulty = table.Column(type: "int", nullable: false), + Servings = table.Column(type: "int", nullable: false), + PreparationTime = table.Column(type: "time", nullable: false), + CookingTime = table.Column(type: "time", nullable: false), + IsFavorite = table.Column(type: "bit", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false), + FavoritId = table.Column(type: "uniqueidentifier", nullable: false), + CategoryId = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Recipes", x => x.Id); + table.ForeignKey( + name: "FK_Recipes_Categories_CategoryId", + column: x => x.CategoryId, + principalTable: "Categories", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_Recipes_Favorits_FavoritId", + column: x => x.FavoritId, + principalTable: "Favorits", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IngredientsShoppingLists", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + ShoppinglistId = table.Column(type: "uniqueidentifier", nullable: false), + IngredientId = table.Column(type: "uniqueidentifier", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IngredientsShoppingLists", x => x.Id); + table.ForeignKey( + name: "FK_IngredientsShoppingLists_Ingredients_IngredientId", + column: x => x.IngredientId, + principalTable: "Ingredients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_IngredientsShoppingLists_ShoppingLists_ShoppinglistId", + column: x => x.ShoppinglistId, + principalTable: "ShoppingLists", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Instructions", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Number = table.Column(type: "nvarchar(max)", nullable: true), + RecipeId = table.Column(type: "uniqueidentifier", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Instructions", x => x.Id); + table.ForeignKey( + name: "FK_Instructions_Recipes_RecipeId", + column: x => x.RecipeId, + principalTable: "Recipes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RecipeIngredients", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + RecipeId = table.Column(type: "uniqueidentifier", nullable: false), + IngredientId = table.Column(type: "uniqueidentifier", nullable: false), + UnitId = table.Column(type: "uniqueidentifier", nullable: false), + Quantity = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RecipeIngredients", x => x.Id); + table.ForeignKey( + name: "FK_RecipeIngredients_Ingredients_IngredientId", + column: x => x.IngredientId, + principalTable: "Ingredients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RecipeIngredients_Recipes_RecipeId", + column: x => x.RecipeId, + principalTable: "Recipes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RecipeIngredients_Units_UnitId", + column: x => x.UnitId, + principalTable: "Units", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MediaFiles", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + FileName = table.Column(type: "nvarchar(max)", nullable: true), + MimeType = table.Column(type: "nvarchar(max)", nullable: true), + Data = table.Column(type: "varbinary(max)", nullable: true), + RecipeId = table.Column(type: "uniqueidentifier", nullable: true), + InstructionId = table.Column(type: "uniqueidentifier", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MediaFiles", x => x.Id); + table.ForeignKey( + name: "FK_MediaFiles_Instructions_InstructionId", + column: x => x.InstructionId, + principalTable: "Instructions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MediaFiles_Recipes_RecipeId", + column: x => x.RecipeId, + principalTable: "Recipes", + principalColumn: "Id"); + }); + + migrationBuilder.InsertData( + table: "Categories", + columns: new[] { "Id", "Name" }, + values: new object[,] + { + { new Guid("0a91a200-dc76-4e00-b38c-b38cab5b69d7"), "Getränke" }, + { new Guid("20585b74-4805-4aff-a6df-aa6b7af04ff1"), "Kuchen" }, + { new Guid("23b1c740-e427-44f9-a6ea-d33d3f30f05a"), "Marmeladen & Eingemachtes" }, + { new Guid("28e39168-701a-4084-81da-d96c987c462f"), "Beilagen & Salate" }, + { new Guid("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"), "Erste Gänge" }, + { new Guid("90deec39-dcd0-422d-9018-ac8389e332e1"), "Desserts & Süßspeisen" }, + { new Guid("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"), "Hauptgerichte" }, + { new Guid("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"), "Hefegebäck & Brot" }, + { new Guid("b248244f-f21c-4555-a14d-5dd49a2717cf"), "Vorspeisen & Snacks" }, + { new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"), "Soßen & Saucen" } + }); + + migrationBuilder.InsertData( + table: "Units", + columns: new[] { "Id", "Name", "Symbol" }, + values: new object[,] + { + { new Guid("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"), "stücke", "stk" }, + { new Guid("24a91b89-3389-4465-89e4-2f70d2ea6fd7"), "esslöffel", "EL" }, + { new Guid("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"), "teelöffel", "TL" }, + { new Guid("62eb2c11-8768-46fb-9db0-c77f940bb4aa"), "liter", "l" }, + { new Guid("66556e0e-eb2b-4bc3-9a56-135dd508ed09"), "zehe", "zehe" }, + { new Guid("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"), "gramm", "g" }, + { new Guid("7e3d1b86-ac48-45d6-814e-d3492a86db1d"), "kilogramm", "kg" }, + { new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"), "bund", "bund" }, + { new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"), "blatt", "blatt" }, + { new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"), "messerspitze", "msp" }, + { new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"), "stange", "stange" } + }); + + migrationBuilder.CreateIndex( + name: "IX_IngredientsShoppingLists_IngredientId", + table: "IngredientsShoppingLists", + column: "IngredientId"); + + migrationBuilder.CreateIndex( + name: "IX_IngredientsShoppingLists_ShoppinglistId", + table: "IngredientsShoppingLists", + column: "ShoppinglistId"); + + migrationBuilder.CreateIndex( + name: "IX_Instructions_RecipeId", + table: "Instructions", + column: "RecipeId"); + + migrationBuilder.CreateIndex( + name: "IX_MediaFiles_InstructionId", + table: "MediaFiles", + column: "InstructionId"); + + migrationBuilder.CreateIndex( + name: "IX_MediaFiles_RecipeId", + table: "MediaFiles", + column: "RecipeId"); + + migrationBuilder.CreateIndex( + name: "IX_RecipeIngredients_IngredientId", + table: "RecipeIngredients", + column: "IngredientId"); + + migrationBuilder.CreateIndex( + name: "IX_RecipeIngredients_RecipeId", + table: "RecipeIngredients", + column: "RecipeId"); + + migrationBuilder.CreateIndex( + name: "IX_RecipeIngredients_UnitId", + table: "RecipeIngredients", + column: "UnitId"); + + migrationBuilder.CreateIndex( + name: "IX_Recipes_CategoryId", + table: "Recipes", + column: "CategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_Recipes_FavoritId", + table: "Recipes", + column: "FavoritId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + { + throw new ArgumentNullException(nameof(migrationBuilder)); + } + migrationBuilder.DropTable( + name: "IngredientsShoppingLists"); + + migrationBuilder.DropTable( + name: "MediaFiles"); + + migrationBuilder.DropTable( + name: "RecipeIngredients"); + + migrationBuilder.DropTable( + name: "ShoppingLists"); + + migrationBuilder.DropTable( + name: "Instructions"); + + migrationBuilder.DropTable( + name: "Ingredients"); + + migrationBuilder.DropTable( + name: "Units"); + + migrationBuilder.DropTable( + name: "Recipes"); + + migrationBuilder.DropTable( + name: "Categories"); + + migrationBuilder.DropTable( + name: "Favorits"); + } + } +} diff --git a/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs b/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs index 309c9b2..06d6ff4 100644 --- a/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs +++ b/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs @@ -29,12 +29,77 @@ namespace Francesco.Recipes.World.Migrations .HasColumnType("uniqueidentifier"); b.Property("Name") - .IsRequired() .HasColumnType("nvarchar(max)"); b.HasKey("Id"); b.ToTable("Categories"); + + b.HasData( + new + { + Id = new Guid("b248244f-f21c-4555-a14d-5dd49a2717cf"), + Name = "Vorspeisen & Snacks" + }, + new + { + Id = new Guid("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"), + Name = "Erste Gänge" + }, + new + { + Id = new Guid("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"), + Name = "Hauptgerichte" + }, + new + { + Id = new Guid("90deec39-dcd0-422d-9018-ac8389e332e1"), + Name = "Desserts & Süßspeisen" + }, + new + { + Id = new Guid("28e39168-701a-4084-81da-d96c987c462f"), + Name = "Beilagen & Salate" + }, + new + { + Id = new Guid("20585b74-4805-4aff-a6df-aa6b7af04ff1"), + Name = "Kuchen" + }, + new + { + Id = new Guid("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"), + Name = "Hefegebäck & Brot" + }, + new + { + Id = new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"), + Name = "Soßen & Saucen" + }, + new + { + Id = new Guid("23b1c740-e427-44f9-a6ea-d33d3f30f05a"), + Name = "Marmeladen & Eingemachtes" + }, + new + { + Id = new Guid("0a91a200-dc76-4e00-b38c-b38cab5b69d7"), + Name = "Getränke" + }); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Favorits"); }); modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b => @@ -44,20 +109,32 @@ namespace Francesco.Recipes.World.Migrations .HasColumnType("uniqueidentifier"); b.Property("Name") - .IsRequired() .HasColumnType("nvarchar(max)"); - b.Property("Quantity") - .HasColumnType("int"); + b.HasKey("Id"); - b.Property("UnitId") + b.ToTable("Ingredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.IngredientsShoppingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("IngredientId") + .HasColumnType("uniqueidentifier"); + + b.Property("ShoppinglistId") .HasColumnType("uniqueidentifier"); b.HasKey("Id"); - b.HasIndex("UnitId"); + b.HasIndex("IngredientId"); - b.ToTable("Ingredients"); + b.HasIndex("ShoppinglistId"); + + b.ToTable("IngredientsShoppingLists"); }); modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => @@ -67,11 +144,9 @@ namespace Francesco.Recipes.World.Migrations .HasColumnType("uniqueidentifier"); b.Property("Description") - .IsRequired() .HasColumnType("nvarchar(max)"); b.Property("Number") - .IsRequired() .HasColumnType("nvarchar(max)"); b.Property("RecipeId") @@ -84,6 +159,36 @@ namespace Francesco.Recipes.World.Migrations b.ToTable("Instructions"); }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Data") + .HasColumnType("varbinary(max)"); + + b.Property("FileName") + .HasColumnType("nvarchar(max)"); + + b.Property("InstructionId") + .HasColumnType("uniqueidentifier"); + + b.Property("MimeType") + .HasColumnType("nvarchar(max)"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("InstructionId"); + + b.HasIndex("RecipeId"); + + b.ToTable("MediaFiles"); + }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => { b.Property("Id") @@ -96,16 +201,22 @@ namespace Francesco.Recipes.World.Migrations b.Property("CookingTime") .HasColumnType("time"); + b.Property("CreatedAt") + .HasColumnType("datetime2"); + b.Property("Description") - .IsRequired() .HasColumnType("nvarchar(max)"); - b.Property("Difficulty") - .IsRequired() - .HasColumnType("nvarchar(max)"); + b.Property("Difficulty") + .HasColumnType("int"); + + b.Property("FavoritId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsFavorite") + .HasColumnType("bit"); b.Property("Name") - .IsRequired() .HasColumnType("nvarchar(max)"); b.Property("PreparationTime") @@ -118,6 +229,8 @@ namespace Francesco.Recipes.World.Migrations b.HasIndex("CategoryId"); + b.HasIndex("FavoritId"); + b.ToTable("Recipes"); }); @@ -130,18 +243,40 @@ namespace Francesco.Recipes.World.Migrations b.Property("IngredientId") .HasColumnType("uniqueidentifier"); + b.Property("Quantity") + .HasColumnType("int"); + b.Property("RecipeId") .HasColumnType("uniqueidentifier"); + b.Property("UnitId") + .HasColumnType("uniqueidentifier"); + b.HasKey("Id"); b.HasIndex("IngredientId"); b.HasIndex("RecipeId"); + b.HasIndex("UnitId"); + b.ToTable("RecipeIngredients"); }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("ShoppingLists"); + }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b => { b.Property("Id") @@ -149,23 +284,101 @@ namespace Francesco.Recipes.World.Migrations .HasColumnType("uniqueidentifier"); b.Property("Name") - .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Symbol") .HasColumnType("nvarchar(max)"); b.HasKey("Id"); - b.ToTable("Unit"); + b.ToTable("Units"); + + b.HasData( + new + { + Id = new Guid("62eb2c11-8768-46fb-9db0-c77f940bb4aa"), + Name = "liter", + Symbol = "l" + }, + new + { + Id = new Guid("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"), + Name = "gramm", + Symbol = "g" + }, + new + { + Id = new Guid("7e3d1b86-ac48-45d6-814e-d3492a86db1d"), + Name = "kilogramm", + Symbol = "kg" + }, + new + { + Id = new Guid("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"), + Name = "stücke", + Symbol = "stk" + }, + new + { + Id = new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"), + Name = "blatt", + Symbol = "blatt" + }, + new + { + Id = new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"), + Name = "messerspitze", + Symbol = "msp" + }, + new + { + Id = new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"), + Name = "stange", + Symbol = "stange" + }, + new + { + Id = new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"), + Name = "bund", + Symbol = "bund" + }, + new + { + Id = new Guid("66556e0e-eb2b-4bc3-9a56-135dd508ed09"), + Name = "zehe", + Symbol = "zehe" + }, + new + { + Id = new Guid("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"), + Name = "teelöffel", + Symbol = "TL" + }, + new + { + Id = new Guid("24a91b89-3389-4465-89e4-2f70d2ea6fd7"), + Name = "esslöffel", + Symbol = "EL" + }); }); - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b => + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.IngredientsShoppingList", b => { - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit") - .WithMany("Recipes") - .HasForeignKey("UnitId") + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient") + .WithMany("IngredientShoppingLists") + .HasForeignKey("IngredientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("Unit"); + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "Shoppinglist") + .WithMany("IngredientsShoppingLists") + .HasForeignKey("ShoppinglistId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ingredient"); + + b.Navigation("Shoppinglist"); }); modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => @@ -179,17 +392,42 @@ namespace Francesco.Recipes.World.Migrations b.Navigation("Recipe"); }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", "Instruction") + .WithMany("MediaFiles") + .HasForeignKey("InstructionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany("MediaFiles") + .HasForeignKey("RecipeId"); + + b.Navigation("Instruction"); + + b.Navigation("Recipe"); + }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => { b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", null) .WithMany("Recipes") .HasForeignKey("CategoryId"); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorit") + .WithMany("Recipe") + .HasForeignKey("FavoritId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Favorit"); }); modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b => { b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient") - .WithMany() + .WithMany("RecipeIngredients") .HasForeignKey("IngredientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -200,9 +438,17 @@ namespace Francesco.Recipes.World.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit") + .WithMany("RecipeIngredient") + .HasForeignKey("UnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.Navigation("Ingredient"); b.Navigation("Recipe"); + + b.Navigation("Unit"); }); modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b => @@ -210,16 +456,40 @@ namespace Francesco.Recipes.World.Migrations b.Navigation("Recipes"); }); - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b => { - b.Navigation("Instructions"); + b.Navigation("Recipe"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b => + { + b.Navigation("IngredientShoppingLists"); b.Navigation("RecipeIngredients"); }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => + { + b.Navigation("MediaFiles"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => + { + b.Navigation("Instructions"); + + b.Navigation("MediaFiles"); + + b.Navigation("RecipeIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => + { + b.Navigation("IngredientsShoppingLists"); + }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b => { - b.Navigation("Recipes"); + b.Navigation("RecipeIngredient"); }); #pragma warning restore 612, 618 } diff --git a/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs b/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs index 5fa42d4..64934a2 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs @@ -4,7 +4,7 @@ public class Category { public Guid Id { get; set; } - public string Name { get; set; } + public string? Name { get; set; } public virtual ICollection Recipes { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Favorit/Favorit.cs b/Francesco.Recipes.World/Models/BackendModels/Favorit/Favorit.cs index 0e91d0e..538dd99 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Favorit/Favorit.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Favorit/Favorit.cs @@ -5,6 +5,6 @@ { public Guid Id { get; set; } public DateTime CreatedAt { get; set; } - public virtual Recipe Recipe { get; set; } = new(); + public virtual ICollection Recipe { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/File/MediaFile.cs b/Francesco.Recipes.World/Models/BackendModels/File/MediaFile.cs deleted file mode 100644 index 888a735..0000000 --- a/Francesco.Recipes.World/Models/BackendModels/File/MediaFile.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Francesco.Recipes.World.Models.BackendModels.File; -using Francesco.Recipes.World.Models.BackendModels.Instruction; -using Francesco.Recipes.World.Models.BackendModels.Recipe; - -public abstract class MediaFile -{ - public Guid Id { get; set; } = Guid.NewGuid(); - public string? FileName { get; set; } - public string? MimeType { get; set; } - public byte[]? Data { get; set; } -} diff --git a/Francesco.Recipes.World/Models/BackendModels/File/MediaFileImageInstruction.cs b/Francesco.Recipes.World/Models/BackendModels/File/MediaFileImageInstruction.cs deleted file mode 100644 index 70ac229..0000000 --- a/Francesco.Recipes.World/Models/BackendModels/File/MediaFileImageInstruction.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Francesco.Recipes.World.Models.BackendModels.File -{ - using Francesco.Recipes.World.Models.BackendModels.Instruction; - - public class MediaFileImageInstruction : MediaFile - { - public virtual Instruction Instruction { get; set; } - } -} diff --git a/Francesco.Recipes.World/Models/BackendModels/File/MediaFileImageRecipe.cs b/Francesco.Recipes.World/Models/BackendModels/File/MediaFileImageRecipe.cs deleted file mode 100644 index fdac8c4..0000000 --- a/Francesco.Recipes.World/Models/BackendModels/File/MediaFileImageRecipe.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Francesco.Recipes.World.Models.BackendModels.File -{ - using Francesco.Recipes.World.Models.BackendModels.Recipe; - - public class MediaFileImageRecipe : MediaFile - { - public virtual Recipe Recipe { get; set; } - } -} diff --git a/Francesco.Recipes.World/Models/BackendModels/File/MediaFileVideoRecipe.cs b/Francesco.Recipes.World/Models/BackendModels/File/MediaFileVideoRecipe.cs deleted file mode 100644 index 14dd710..0000000 --- a/Francesco.Recipes.World/Models/BackendModels/File/MediaFileVideoRecipe.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Francesco.Recipes.World.Models.BackendModels.File -{ - using Francesco.Recipes.World.Models.BackendModels.Recipe; - public class MediaFileVideoRecipe : MediaFile - { - public virtual Recipe Recipe { get; set; } - } -} diff --git a/Francesco.Recipes.World/Models/BackendModels/ITimeStampedEntity.cs b/Francesco.Recipes.World/Models/BackendModels/ITimeStampedEntity.cs new file mode 100644 index 0000000..baa9f35 --- /dev/null +++ b/Francesco.Recipes.World/Models/BackendModels/ITimeStampedEntity.cs @@ -0,0 +1,9 @@ +namespace Francesco.Recipes.World.Models.BackendModels +{ + public interface ITimeStampedEntity + { + DateTime CreatedAt { get; set; } + + DateTime? ModifiedAt { get; set; } + } +} diff --git a/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs b/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs index a657e98..291da18 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs @@ -7,10 +7,8 @@ public class Ingredient { public Guid Id { get; set; } - public string Name { get; set; } - public int Quantity { get; set; } + public string? Name { get; set; } public virtual ICollection RecipeIngredients { get; set; } = new List(); public virtual ICollection IngredientShoppingLists { get; set; } = new List(); - public virtual Unit Unit { get; set; } = new(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs b/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs index ee67e89..fc14bd6 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs @@ -1,13 +1,13 @@ namespace Francesco.Recipes.World.Models.BackendModels.Instruction { - using Francesco.Recipes.World.Models.BackendModels.File; + using Francesco.Recipes.World.Models.BackendModels.MediaFile; using Francesco.Recipes.World.Models.BackendModels.Recipe; public class Instruction { public Guid Id { get; set; } - public string Description { get; set; } - public string Number { get; set; } + public string? Description { get; set; } + public string? Number { get; set; } public virtual Recipe Recipe { get; set; } = new(); - public ICollection MediaFileImageInstructions { get; set; } = new List(); + public virtual ICollection MediaFiles { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/MediaFile/MediaFile.cs b/Francesco.Recipes.World/Models/BackendModels/MediaFile/MediaFile.cs new file mode 100644 index 0000000..56c083a --- /dev/null +++ b/Francesco.Recipes.World/Models/BackendModels/MediaFile/MediaFile.cs @@ -0,0 +1,17 @@ +namespace Francesco.Recipes.World.Models.BackendModels.MediaFile +{ + using Francesco.Recipes.World.Models.BackendModels.Instruction; + using Francesco.Recipes.World.Models.BackendModels.Recipe; + public class MediaFile + { + public Guid Id { get; set; } = Guid.NewGuid(); + + public string? FileName { get; set; } + + public string? MimeType { get; set; } + + public byte[]? Data { get; set; } + public virtual Recipe? Recipe { get; set; } = new(); + public virtual Instruction Instruction { get; set; } = new(); + } +} diff --git a/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs b/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs index 2ce3413..6c352a5 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs @@ -2,25 +2,24 @@ { using Francesco.Recipes.World.Models.BackendModels.Category; using Francesco.Recipes.World.Models.BackendModels.Favorit; - using Francesco.Recipes.World.Models.BackendModels.File; using Francesco.Recipes.World.Models.BackendModels.Instruction; + using Francesco.Recipes.World.Models.BackendModels.MediaFile; using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; public class Recipe { public Guid Id { get; set; } - public string Name { get; set; } - public string Description { get; set; } + public string? Name { get; set; } + public string? Description { get; set; } public Difficulty Difficulty { get; set; } public int Servings { get; set; } public TimeSpan PreparationTime { get; set; } public TimeSpan CookingTime { get; set; } public bool IsFavorite { get; set; } + public DateTime CreatedAt { get; set; } public virtual ICollection RecipeIngredients { get; set; } = new List(); public virtual ICollection Instructions { get; set; } = new List(); - public virtual ICollection Images { get; set; } = new List(); - public virtual ICollection Videos { get; set; } = new List(); - public virtual Category Category { get; set; } = new(); + public virtual ICollection MediaFiles { get; set; } = new List(); public virtual Favorit Favorit { get; set; } = new(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/RecipeIngredient/RecipeIngredient.cs b/Francesco.Recipes.World/Models/BackendModels/RecipeIngredient/RecipeIngredient.cs index 84ddb82..30456ad 100644 --- a/Francesco.Recipes.World/Models/BackendModels/RecipeIngredient/RecipeIngredient.cs +++ b/Francesco.Recipes.World/Models/BackendModels/RecipeIngredient/RecipeIngredient.cs @@ -2,11 +2,13 @@ { using Francesco.Recipes.World.Models.BackendModels.Ingredient; using Francesco.Recipes.World.Models.BackendModels.Recipe; + using Francesco.Recipes.World.Models.BackendModels.Unit; public class RecipeIngredient { public Guid Id { get; set; } - public Guid RecipeId { get; set; } public virtual Recipe Recipe { get; set; } = new(); public virtual Ingredient Ingredient { get; set; } = new(); + public virtual Unit Unit { get; set; } = new(); + public int Quantity { get; set; } } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs b/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs index 3806b7c..902657a 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs @@ -3,11 +3,12 @@ namespace Francesco.Recipes.World.Models.BackendModels.Unit { using Francesco.Recipes.World.Models.BackendModels.Ingredient; + using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; public class Unit { public Guid Id{ get; set; } - public string Name{ get; set; } - public string Symbol { get; set; } - public virtual ICollection Ingredients { get; set; } = new List(); + public string? Name { get; set; } + public string? Symbol { get; set; } + public virtual ICollection RecipeIngredient { get; set; } = new List(); } } From 36e107d48e874407f0cec1db83b35d7a77579426 Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Tue, 18 Mar 2025 13:15:08 +0100 Subject: [PATCH 003/183] Fixed some properties on models --- .../Models/BackendModels/Ingredient/Ingredient.cs | 2 +- .../Models/BackendModels/Instruction/Instruction.cs | 4 ++-- .../Models/BackendModels/Recipe/Recipe.cs | 6 +++--- .../Models/BackendModels/Shoppinglist/ShoppingList.cs | 3 ++- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs b/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs index 291da18..f8bf8eb 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs @@ -7,7 +7,7 @@ public class Ingredient { public Guid Id { get; set; } - public string? Name { get; set; } + public string Name { get; set; } public virtual ICollection RecipeIngredients { get; set; } = new List(); public virtual ICollection IngredientShoppingLists { get; set; } = new List(); } diff --git a/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs b/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs index fc14bd6..6f0919c 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs @@ -5,8 +5,8 @@ public class Instruction { public Guid Id { get; set; } - public string? Description { get; set; } - public string? Number { get; set; } + public string Description { get; set; } + public string Number { get; set; } public virtual Recipe Recipe { get; set; } = new(); public virtual ICollection MediaFiles { get; set; } = new List(); } diff --git a/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs b/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs index 6c352a5..68d180c 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs @@ -6,10 +6,10 @@ using Francesco.Recipes.World.Models.BackendModels.MediaFile; using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; - public class Recipe + public class Recipe : ITimeStampedEntity { public Guid Id { get; set; } - public string? Name { get; set; } + public string Name { get; set; } public string? Description { get; set; } public Difficulty Difficulty { get; set; } public int Servings { get; set; } @@ -17,9 +17,9 @@ public TimeSpan CookingTime { get; set; } public bool IsFavorite { get; set; } public DateTime CreatedAt { get; set; } + public DateTime? ModifiedAt { get; set; } public virtual ICollection RecipeIngredients { get; set; } = new List(); public virtual ICollection Instructions { get; set; } = new List(); public virtual ICollection MediaFiles { get; set; } = new List(); public virtual Favorit Favorit { get; set; } = new(); } -} diff --git a/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs index bcb1629..a6f764e 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs @@ -2,10 +2,11 @@ namespace Francesco.Recipes.World.Models.BackendModels.Shoppinglist { - public class ShoppingList + public class ShoppingList : ITimeStampedEntity { public Guid Id { get; set; } public DateTime CreatedAt { get; set; } + public DateTime? ModifiedAt { get; set; } public virtual ICollection IngredientsShoppingLists { get; set; } = new List(); } } From 45702aa4f2fe4ea29a671744243d755370aeb384 Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Tue, 18 Mar 2025 13:54:37 +0100 Subject: [PATCH 004/183] Correct Models Unit and Category --- .gitignore | 1 - .../Controllers/Favorit/FavoritController.cs | 6 + .../Ingredient/IngredientController.cs | 6 + .../Instruction/InstructionController.cs | 6 + .../MediaFile/MediaFilesController.cs | 9 ++ .../Controllers/Recipe/RecipeController.cs | 6 + .../ShoppingList/ShoppingLIstController.cs | 6 + .../Data/FrancescosRecipesWorldDbContext.cs | 111 +++++++++++++++--- .../Repository/Category/CategoryRepository.cs | 6 + .../Category/ICategoryRepository.cs | 6 + .../Data/Repository/ErrorViewModel.cs | 9 ++ .../Repository/Favorit/FavoritRepository.cs | 6 + .../Repository/Favorit/IFavoritRepository.cs | 6 + .../Ingredient/IIngredientRepository.cs | 17 +++ .../Ingredient/IngredientRepository.cs | 66 +++++++++++ .../Instruction/IInstructionsRepository.cs | 6 + .../Instruction/InstructionsRepository.cs | 6 + .../MediaFile/IMediaFileRepository.cs | 6 + .../MediaFile/MediaFileRepository.cs | 6 + .../Repository/Recipe/IRecipeRepository.cs | 6 + .../Repository/Recipe/RecipeRepository.cs | 6 + .../ShoppingLIst/IShoppingListRepository.cs | 6 + .../ShoppingLIst/ShoppingListRepository.cs | 6 + .../Data/Repository/Unit/IUnitRepository.cs | 6 + .../Data/Repository/Unit/UnitRepository.cs | 6 + .../Francesco.Recipes.World.csproj | 5 + .../Models/BackendModels/Category/Category.cs | 2 +- .../Models/BackendModels/Unit/Unit.cs | 4 +- .../Services/Category/CategoryService.cs | 6 + .../Services/Favorit/FavoritService.cs | 6 + .../Services/Ingredient/IngredientService.cs | 6 + .../Instruction/InstructionsService.cs | 6 + .../Services/MediaFile/MediaFileService.cs | 6 + .../Services/Recipe/RecipeService.cs | 6 + .../ShoppingLIst/ShoppingListService.cs | 6 + .../Services/Unit/UnitService.cs | 6 + 36 files changed, 368 insertions(+), 18 deletions(-) create mode 100644 Francesco.Recipes.World/Controllers/Favorit/FavoritController.cs create mode 100644 Francesco.Recipes.World/Controllers/Ingredient/IngredientController.cs create mode 100644 Francesco.Recipes.World/Controllers/Instruction/InstructionController.cs create mode 100644 Francesco.Recipes.World/Controllers/MediaFile/MediaFilesController.cs create mode 100644 Francesco.Recipes.World/Controllers/Recipe/RecipeController.cs create mode 100644 Francesco.Recipes.World/Controllers/ShoppingList/ShoppingLIstController.cs create mode 100644 Francesco.Recipes.World/Data/Repository/Category/CategoryRepository.cs create mode 100644 Francesco.Recipes.World/Data/Repository/Category/ICategoryRepository.cs create mode 100644 Francesco.Recipes.World/Data/Repository/ErrorViewModel.cs create mode 100644 Francesco.Recipes.World/Data/Repository/Favorit/FavoritRepository.cs create mode 100644 Francesco.Recipes.World/Data/Repository/Favorit/IFavoritRepository.cs create mode 100644 Francesco.Recipes.World/Data/Repository/Ingredient/IIngredientRepository.cs create mode 100644 Francesco.Recipes.World/Data/Repository/Ingredient/IngredientRepository.cs create mode 100644 Francesco.Recipes.World/Data/Repository/Instruction/IInstructionsRepository.cs create mode 100644 Francesco.Recipes.World/Data/Repository/Instruction/InstructionsRepository.cs create mode 100644 Francesco.Recipes.World/Data/Repository/MediaFile/IMediaFileRepository.cs create mode 100644 Francesco.Recipes.World/Data/Repository/MediaFile/MediaFileRepository.cs create mode 100644 Francesco.Recipes.World/Data/Repository/Recipe/IRecipeRepository.cs create mode 100644 Francesco.Recipes.World/Data/Repository/Recipe/RecipeRepository.cs create mode 100644 Francesco.Recipes.World/Data/Repository/ShoppingLIst/IShoppingListRepository.cs create mode 100644 Francesco.Recipes.World/Data/Repository/ShoppingLIst/ShoppingListRepository.cs create mode 100644 Francesco.Recipes.World/Data/Repository/Unit/IUnitRepository.cs create mode 100644 Francesco.Recipes.World/Data/Repository/Unit/UnitRepository.cs create mode 100644 Francesco.Recipes.World/Services/Category/CategoryService.cs create mode 100644 Francesco.Recipes.World/Services/Favorit/FavoritService.cs create mode 100644 Francesco.Recipes.World/Services/Ingredient/IngredientService.cs create mode 100644 Francesco.Recipes.World/Services/Instruction/InstructionsService.cs create mode 100644 Francesco.Recipes.World/Services/MediaFile/MediaFileService.cs create mode 100644 Francesco.Recipes.World/Services/Recipe/RecipeService.cs create mode 100644 Francesco.Recipes.World/Services/ShoppingLIst/ShoppingListService.cs create mode 100644 Francesco.Recipes.World/Services/Unit/UnitService.cs diff --git a/.gitignore b/.gitignore index ad9a362..61101b4 100644 --- a/.gitignore +++ b/.gitignore @@ -397,4 +397,3 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml src/Recruitment.Tool.xml -/Francesco.Recipes.World/Migrations/20250304095839_AddFewNewBackendModels.Designer.cs diff --git a/Francesco.Recipes.World/Controllers/Favorit/FavoritController.cs b/Francesco.Recipes.World/Controllers/Favorit/FavoritController.cs new file mode 100644 index 0000000..7d66c1f --- /dev/null +++ b/Francesco.Recipes.World/Controllers/Favorit/FavoritController.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Controllers.Favorit +{ + public class FavoritController + { + } +} diff --git a/Francesco.Recipes.World/Controllers/Ingredient/IngredientController.cs b/Francesco.Recipes.World/Controllers/Ingredient/IngredientController.cs new file mode 100644 index 0000000..187acb8 --- /dev/null +++ b/Francesco.Recipes.World/Controllers/Ingredient/IngredientController.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Controllers.Ingredient +{ + public class IngredientController + { + } +} diff --git a/Francesco.Recipes.World/Controllers/Instruction/InstructionController.cs b/Francesco.Recipes.World/Controllers/Instruction/InstructionController.cs new file mode 100644 index 0000000..17c9b8d --- /dev/null +++ b/Francesco.Recipes.World/Controllers/Instruction/InstructionController.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Controllers.Instruction +{ + public class InstructionController + { + } +} diff --git a/Francesco.Recipes.World/Controllers/MediaFile/MediaFilesController.cs b/Francesco.Recipes.World/Controllers/MediaFile/MediaFilesController.cs new file mode 100644 index 0000000..d17c5fd --- /dev/null +++ b/Francesco.Recipes.World/Controllers/MediaFile/MediaFilesController.cs @@ -0,0 +1,9 @@ +namespace Francesco.Recipes.World.Controllers.MediaFile +{ + using Microsoft.AspNetCore.Mvc; + + public class MediaFilesController : Controller + { + + } +} diff --git a/Francesco.Recipes.World/Controllers/Recipe/RecipeController.cs b/Francesco.Recipes.World/Controllers/Recipe/RecipeController.cs new file mode 100644 index 0000000..a4b93ae --- /dev/null +++ b/Francesco.Recipes.World/Controllers/Recipe/RecipeController.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Controllers.Recipe +{ + public class RecipeController + { + } +} diff --git a/Francesco.Recipes.World/Controllers/ShoppingList/ShoppingLIstController.cs b/Francesco.Recipes.World/Controllers/ShoppingList/ShoppingLIstController.cs new file mode 100644 index 0000000..03e77c6 --- /dev/null +++ b/Francesco.Recipes.World/Controllers/ShoppingList/ShoppingLIstController.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Controllers.ShoppingList +{ + public class ShoppingLIstController + { + } +} diff --git a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs index fe4646e..e27bf78 100644 --- a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs +++ b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs @@ -1,25 +1,108 @@ -namespace Francesco.Recipes.World.Data -{ - using Francesco.Recipes.World.Models.BackendModels.Category; +namespace Francesco.Recipes.World.Data; + +using System.Runtime.CompilerServices; +using Francesco.Recipes.World.Models.BackendModels; +using Francesco.Recipes.World.Models.BackendModels.Category; + using Francesco.Recipes.World.Models.BackendModels.Favorit; using Francesco.Recipes.World.Models.BackendModels.Ingredient; + using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; using Francesco.Recipes.World.Models.BackendModels.Instruction; + using Francesco.Recipes.World.Models.BackendModels.MediaFile; using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; + using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; using Francesco.Recipes.World.Models.BackendModels.Unit; - using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; - public class FrancescosRecipesWorldDbContext : DbContext +public class FrancescosRecipesWorldDbContext : DbContext +{ + public FrancescosRecipesWorldDbContext(DbContextOptions options) + : base(options) { - public FrancescosRecipesWorldDbContext(DbContextOptions options) - : base(options) - { + } + + public DbSet Categories => Set(); + public DbSet Ingredients => Set(); + public DbSet Instructions => Set(); + public DbSet Recipes => Set(); + public DbSet RecipeIngredients => Set(); + public DbSet Units => Set(); + public DbSet Favorits => Set(); + public DbSet IngredientsShoppingLists => Set(); + public DbSet ShoppingLists => Set(); + public DbSet MediaFiles => Set(); + public override Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + foreach (var entry in ChangeTracker.Entries()) + { + if (entry.Entity is ITimeStampedEntity timeStampedEntity) + { + switch (entry.State) + { + case EntityState.Added: + timeStampedEntity.CreatedAt = DateTime.UtcNow; + break; + case EntityState.Modified: + timeStampedEntity.ModifiedAt = DateTime.UtcNow; + break; + } + } } - public DbSet Categories => Set(); - public DbSet Ingredients => Set(); - public DbSet Instructions => Set(); - public DbSet Recipes => Set(); - public DbSet RecipeIngredients => Set(); - public DbSet Units => Set(); + return base.SaveChangesAsync(cancellationToken); + } + protected override void OnModelCreating(ModelBuilder builder) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + SeedData(builder); + + base.OnModelCreating(builder); + } + + private static void SeedData(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasData(GetCategories()); + + modelBuilder.Entity() + .HasData(GetUnits()); + } + private static IEnumerable GetCategories() + { + return new List() + { + new Category { Id = Guid.Parse("b248244f-f21c-4555-a14d-5dd49a2717cf"), Name = "Vorspeisen & Snacks" }, + new Category { Id = Guid.Parse("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"), Name = "Erste Gänge" }, + new Category { Id = Guid.Parse("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"), Name = "Hauptgerichte" }, + new Category { Id = Guid.Parse("90deec39-dcd0-422d-9018-ac8389e332e1"), Name = "Desserts & Süßspeisen" }, + new Category { Id = Guid.Parse("28e39168-701a-4084-81da-d96c987c462f"), Name = "Beilagen & Salate" }, + new Category { Id = Guid.Parse("20585b74-4805-4aff-a6df-aa6b7af04ff1"), Name = "Kuchen" }, + new Category { Id = Guid.Parse("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"), Name = "Hefegebäck & Brot" }, + new Category { Id = Guid.Parse("d332f88d-d241-48c5-a2f2-bfd124eada7e"), Name = "Soßen & Saucen" }, + new Category { Id = Guid.Parse("23b1c740-e427-44f9-a6ea-d33d3f30f05a"), Name = "Marmeladen & Eingemachtes" }, + new Category { Id = Guid.Parse("0a91a200-dc76-4e00-b38c-b38cab5b69d7"), Name = "Getränke" }, + }; + } + + private static IEnumerable GetUnits() + { + return new List() + { + new Unit { Id = Guid.Parse("62eb2c11-8768-46fb-9db0-c77f940bb4aa"), Name = "liter", Symbol = "l" }, + new Unit { Id = Guid.Parse("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"), Name = "gramm", Symbol = "g" }, + new Unit { Id = Guid.Parse("7e3d1b86-ac48-45d6-814e-d3492a86db1d"), Name = "kilogramm", Symbol = "kg" }, + new Unit { Id = Guid.Parse("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"), Name = "stücke", Symbol = "stk" }, + new Unit { Id = Guid.Parse("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"), Name = "blatt", Symbol = "blatt" }, + new Unit { Id = Guid.Parse("df5cb4c3-4de6-4c6f-be8c-da41b2986408"), Name = "messerspitze", Symbol = "msp" }, + new Unit { Id = Guid.Parse("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"), Name = "stange", Symbol = "stange" }, + new Unit { Id = Guid.Parse("7ea2f51d-7493-4f19-a663-1f309186d3ae"), Name = "bund", Symbol = "bund" }, + new Unit { Id = Guid.Parse("66556e0e-eb2b-4bc3-9a56-135dd508ed09"), Name = "zehe", Symbol = "zehe" }, + new Unit { Id = Guid.Parse("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"), Name = "teelöffel", Symbol = "TL" }, + new Unit { Id = Guid.Parse("24a91b89-3389-4465-89e4-2f70d2ea6fd7"), Name = "esslöffel", Symbol = "EL" }, + }; } } diff --git a/Francesco.Recipes.World/Data/Repository/Category/CategoryRepository.cs b/Francesco.Recipes.World/Data/Repository/Category/CategoryRepository.cs new file mode 100644 index 0000000..60e0775 --- /dev/null +++ b/Francesco.Recipes.World/Data/Repository/Category/CategoryRepository.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Data.Repository.Category +{ + public class CategoryRepository + { + } +} diff --git a/Francesco.Recipes.World/Data/Repository/Category/ICategoryRepository.cs b/Francesco.Recipes.World/Data/Repository/Category/ICategoryRepository.cs new file mode 100644 index 0000000..cfd14f4 --- /dev/null +++ b/Francesco.Recipes.World/Data/Repository/Category/ICategoryRepository.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Data.Repository.Category +{ + public interface ICategoryRepository + { + } +} diff --git a/Francesco.Recipes.World/Data/Repository/ErrorViewModel.cs b/Francesco.Recipes.World/Data/Repository/ErrorViewModel.cs new file mode 100644 index 0000000..1f2259d --- /dev/null +++ b/Francesco.Recipes.World/Data/Repository/ErrorViewModel.cs @@ -0,0 +1,9 @@ +namespace Francesco.Recipes.World.Data.Repository +{ + public class ErrorViewModel + { + public string? RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + } +} diff --git a/Francesco.Recipes.World/Data/Repository/Favorit/FavoritRepository.cs b/Francesco.Recipes.World/Data/Repository/Favorit/FavoritRepository.cs new file mode 100644 index 0000000..3292412 --- /dev/null +++ b/Francesco.Recipes.World/Data/Repository/Favorit/FavoritRepository.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Data.Repository.Favorit +{ + public class FavoritRepository + { + } +} diff --git a/Francesco.Recipes.World/Data/Repository/Favorit/IFavoritRepository.cs b/Francesco.Recipes.World/Data/Repository/Favorit/IFavoritRepository.cs new file mode 100644 index 0000000..1691bb0 --- /dev/null +++ b/Francesco.Recipes.World/Data/Repository/Favorit/IFavoritRepository.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Data.Repository.Favorit +{ + public interface IFavoritRepository + { + } +} diff --git a/Francesco.Recipes.World/Data/Repository/Ingredient/IIngredientRepository.cs b/Francesco.Recipes.World/Data/Repository/Ingredient/IIngredientRepository.cs new file mode 100644 index 0000000..a24cb71 --- /dev/null +++ b/Francesco.Recipes.World/Data/Repository/Ingredient/IIngredientRepository.cs @@ -0,0 +1,17 @@ +namespace FrancescoRecipesWorld.Repositories +{ + using Francesco.Recipes.World.Models.BackendModels.Ingredient; + using Francesco.Recipes.World.Models.BackendModels.Recipe; + + public interface IIngredientRepository + { + Task CreateIngredientToRecipeAsync(int recipeId, string ingredientName); + Task UpdateIngredientAsync(Ingredient ingredient); + Task> GetIngredientsByRecipeIdAsync(Guid recipeId); + Task> GetRecipesByIngredientIdAsync(Guid ingredientId); + Task> GetIngredientsByNameAsync(string name); + Task RemoveIngredientFromRecipeAsync(Recipe recipe, Guid ingredientId); + Task GetIngredientByIdAsync(Guid ingredientId); + + } +} diff --git a/Francesco.Recipes.World/Data/Repository/Ingredient/IngredientRepository.cs b/Francesco.Recipes.World/Data/Repository/Ingredient/IngredientRepository.cs new file mode 100644 index 0000000..4d32131 --- /dev/null +++ b/Francesco.Recipes.World/Data/Repository/Ingredient/IngredientRepository.cs @@ -0,0 +1,66 @@ +namespace FrancescoRecipesWorld.Repositories +{ + using Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Models.BackendModels.Ingredient; + using Francesco.Recipes.World.Models.BackendModels.Recipe; + + public class IngredientRepository : IIngredientRepository + { + private readonly FrancescosRecipesWorldDbContext _context; + private readonly RecipeRepository _recipeRepository; + public IngredientRepository( + FrancescosRecipesWorldDbContext context, RecipeRepository recipeRepository) + { + _context = context; + _recipeRepository = recipeRepository; + } + + public async Task CreateIngredientToRecipeAsync(int recipeId, string ingredientName) + { + var recipe = await _context.Recipes.FindAsync(recipeId); + if (recipe == null) + { + throw new ArgumentException("Recipe not found", nameof(recipeId)); + } + + var newIngredient = new Ingredient + { + Name = ingredientName, + }; + _context.Ingredients.Add(newIngredient); + await _context.SaveChangesAsync(); + return newIngredient; + } + + public Task RemoveIngredientFromRecipeAsync(Recipe recipe, Guid ingredientId) + { + throw new NotImplementedException(); + } + + public Task> GetIngredientsByNameAsync(string name) + { + throw new NotImplementedException(); + } + + public Task> GetIngredientsByRecipeIdAsync(Guid recipeId) + { + throw new NotImplementedException(); + } + + public Task> GetRecipesByIngredientIdAsync(Guid ingredientId) + { + throw new NotImplementedException(); + } + + public Task UpdateIngredientAsync(Ingredient ingredient) + { + throw new NotImplementedException(); + } + + public async Task GetIngredientByIdAsync(Guid ingredientId) + { + var ingredient = await _context.Ingredients.FindAsync(ingredientId); + return ingredient ?? throw new InvalidDataException($"Address {ingredientId} not found."); + } + } +} diff --git a/Francesco.Recipes.World/Data/Repository/Instruction/IInstructionsRepository.cs b/Francesco.Recipes.World/Data/Repository/Instruction/IInstructionsRepository.cs new file mode 100644 index 0000000..9eb5c68 --- /dev/null +++ b/Francesco.Recipes.World/Data/Repository/Instruction/IInstructionsRepository.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Data.Repository.Instruction +{ + public interface IInstructionsRepository + { + } +} diff --git a/Francesco.Recipes.World/Data/Repository/Instruction/InstructionsRepository.cs b/Francesco.Recipes.World/Data/Repository/Instruction/InstructionsRepository.cs new file mode 100644 index 0000000..7306bb2 --- /dev/null +++ b/Francesco.Recipes.World/Data/Repository/Instruction/InstructionsRepository.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Data.Repository.Instruction +{ + public class InstructionsRepository + { + } +} diff --git a/Francesco.Recipes.World/Data/Repository/MediaFile/IMediaFileRepository.cs b/Francesco.Recipes.World/Data/Repository/MediaFile/IMediaFileRepository.cs new file mode 100644 index 0000000..e8c1058 --- /dev/null +++ b/Francesco.Recipes.World/Data/Repository/MediaFile/IMediaFileRepository.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Data.Repository.MediaFile +{ + public interface IMEdiaFileRepository + { + } +} diff --git a/Francesco.Recipes.World/Data/Repository/MediaFile/MediaFileRepository.cs b/Francesco.Recipes.World/Data/Repository/MediaFile/MediaFileRepository.cs new file mode 100644 index 0000000..0c3490e --- /dev/null +++ b/Francesco.Recipes.World/Data/Repository/MediaFile/MediaFileRepository.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Data.Repository.MediaFile +{ + public class MediaFileRepository + { + } +} diff --git a/Francesco.Recipes.World/Data/Repository/Recipe/IRecipeRepository.cs b/Francesco.Recipes.World/Data/Repository/Recipe/IRecipeRepository.cs new file mode 100644 index 0000000..cd4e97b --- /dev/null +++ b/Francesco.Recipes.World/Data/Repository/Recipe/IRecipeRepository.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Data.Repository.Recipe +{ + public interface IRecipeRepository + { + } +} diff --git a/Francesco.Recipes.World/Data/Repository/Recipe/RecipeRepository.cs b/Francesco.Recipes.World/Data/Repository/Recipe/RecipeRepository.cs new file mode 100644 index 0000000..bd57464 --- /dev/null +++ b/Francesco.Recipes.World/Data/Repository/Recipe/RecipeRepository.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Data.Repository.Recipe +{ + public class RecipeRepository + { + } +} diff --git a/Francesco.Recipes.World/Data/Repository/ShoppingLIst/IShoppingListRepository.cs b/Francesco.Recipes.World/Data/Repository/ShoppingLIst/IShoppingListRepository.cs new file mode 100644 index 0000000..c7e3943 --- /dev/null +++ b/Francesco.Recipes.World/Data/Repository/ShoppingLIst/IShoppingListRepository.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Data.Repository.ShoppingLIst +{ + public interface IShoppingListRepository + { + } +} diff --git a/Francesco.Recipes.World/Data/Repository/ShoppingLIst/ShoppingListRepository.cs b/Francesco.Recipes.World/Data/Repository/ShoppingLIst/ShoppingListRepository.cs new file mode 100644 index 0000000..5b0c2cb --- /dev/null +++ b/Francesco.Recipes.World/Data/Repository/ShoppingLIst/ShoppingListRepository.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Data.Repository.ShoppingLIst +{ + public class ShoppingListRepository + { + } +} diff --git a/Francesco.Recipes.World/Data/Repository/Unit/IUnitRepository.cs b/Francesco.Recipes.World/Data/Repository/Unit/IUnitRepository.cs new file mode 100644 index 0000000..443f0b1 --- /dev/null +++ b/Francesco.Recipes.World/Data/Repository/Unit/IUnitRepository.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Data.Repository.Unit +{ + public interface IUnitRepository + { + } +} diff --git a/Francesco.Recipes.World/Data/Repository/Unit/UnitRepository.cs b/Francesco.Recipes.World/Data/Repository/Unit/UnitRepository.cs new file mode 100644 index 0000000..ab66777 --- /dev/null +++ b/Francesco.Recipes.World/Data/Repository/Unit/UnitRepository.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Data.Repository.Unit +{ + public class UnitRepository + { + } +} diff --git a/Francesco.Recipes.World/Francesco.Recipes.World.csproj b/Francesco.Recipes.World/Francesco.Recipes.World.csproj index f9e0493..fff88d5 100644 --- a/Francesco.Recipes.World/Francesco.Recipes.World.csproj +++ b/Francesco.Recipes.World/Francesco.Recipes.World.csproj @@ -25,6 +25,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -38,4 +39,8 @@ + + + + diff --git a/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs b/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs index 64934a2..5fa42d4 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs @@ -4,7 +4,7 @@ public class Category { public Guid Id { get; set; } - public string? Name { get; set; } + public string Name { get; set; } public virtual ICollection Recipes { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs b/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs index 902657a..e11bce3 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs @@ -7,8 +7,8 @@ namespace Francesco.Recipes.World.Models.BackendModels.Unit public class Unit { public Guid Id{ get; set; } - public string? Name { get; set; } - public string? Symbol { get; set; } + public string Name { get; set; } + public string Symbol { get; set; } public virtual ICollection RecipeIngredient { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Services/Category/CategoryService.cs b/Francesco.Recipes.World/Services/Category/CategoryService.cs new file mode 100644 index 0000000..df69cb8 --- /dev/null +++ b/Francesco.Recipes.World/Services/Category/CategoryService.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Data.Repository.Category +{ + public class CategoryService + { + } +} diff --git a/Francesco.Recipes.World/Services/Favorit/FavoritService.cs b/Francesco.Recipes.World/Services/Favorit/FavoritService.cs new file mode 100644 index 0000000..a37845b --- /dev/null +++ b/Francesco.Recipes.World/Services/Favorit/FavoritService.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Data.Repository.Favorit +{ + public class FavoritService + { + } +} diff --git a/Francesco.Recipes.World/Services/Ingredient/IngredientService.cs b/Francesco.Recipes.World/Services/Ingredient/IngredientService.cs new file mode 100644 index 0000000..8bdfb20 --- /dev/null +++ b/Francesco.Recipes.World/Services/Ingredient/IngredientService.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Data.Repository.Ingredient +{ + public class IngredientService + { + } +} diff --git a/Francesco.Recipes.World/Services/Instruction/InstructionsService.cs b/Francesco.Recipes.World/Services/Instruction/InstructionsService.cs new file mode 100644 index 0000000..1b156dd --- /dev/null +++ b/Francesco.Recipes.World/Services/Instruction/InstructionsService.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Repository.Instructions +{ + public class InstructionsService + { + } +} diff --git a/Francesco.Recipes.World/Services/MediaFile/MediaFileService.cs b/Francesco.Recipes.World/Services/MediaFile/MediaFileService.cs new file mode 100644 index 0000000..03c64e1 --- /dev/null +++ b/Francesco.Recipes.World/Services/MediaFile/MediaFileService.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Repository.MediaFile +{ + public class MediaFileService + { + } +} diff --git a/Francesco.Recipes.World/Services/Recipe/RecipeService.cs b/Francesco.Recipes.World/Services/Recipe/RecipeService.cs new file mode 100644 index 0000000..f6bc9dc --- /dev/null +++ b/Francesco.Recipes.World/Services/Recipe/RecipeService.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Repository.Recipe +{ + public class RecipeService + { + } +} diff --git a/Francesco.Recipes.World/Services/ShoppingLIst/ShoppingListService.cs b/Francesco.Recipes.World/Services/ShoppingLIst/ShoppingListService.cs new file mode 100644 index 0000000..84b68ee --- /dev/null +++ b/Francesco.Recipes.World/Services/ShoppingLIst/ShoppingListService.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Repository.ShoppingLIst +{ + public class ShoppingListService + { + } +} diff --git a/Francesco.Recipes.World/Services/Unit/UnitService.cs b/Francesco.Recipes.World/Services/Unit/UnitService.cs new file mode 100644 index 0000000..82c2c8d --- /dev/null +++ b/Francesco.Recipes.World/Services/Unit/UnitService.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Repository.Unit +{ + public class UnitService + { + } +} From d53729f798708f1a536d5c36e97f9510ed04dbe4 Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Tue, 18 Mar 2025 14:20:53 +0100 Subject: [PATCH 005/183] Corret Models Unit and Category --- .gitignore | 1 + .../Data/FrancescosRecipesWorldDbContext.cs | 125 +++++++++--------- .../Models/BackendModels/Category/Category.cs | 2 +- .../Models/BackendModels/Unit/Unit.cs | 4 +- 4 files changed, 66 insertions(+), 66 deletions(-) diff --git a/.gitignore b/.gitignore index 61101b4..ad9a362 100644 --- a/.gitignore +++ b/.gitignore @@ -397,3 +397,4 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml src/Recruitment.Tool.xml +/Francesco.Recipes.World/Migrations/20250304095839_AddFewNewBackendModels.Designer.cs diff --git a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs index e27bf78..0f6c858 100644 --- a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs +++ b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs @@ -1,8 +1,7 @@ -namespace Francesco.Recipes.World.Data; - -using System.Runtime.CompilerServices; -using Francesco.Recipes.World.Models.BackendModels; -using Francesco.Recipes.World.Models.BackendModels.Category; +namespace Francesco.Recipes.World.Data +{ + using Francesco.Recipes.World.Models.BackendModels; + using Francesco.Recipes.World.Models.BackendModels.Category; using Francesco.Recipes.World.Models.BackendModels.Favorit; using Francesco.Recipes.World.Models.BackendModels.Ingredient; using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; @@ -12,70 +11,69 @@ using Francesco.Recipes.World.Models.BackendModels.Category; using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; using Francesco.Recipes.World.Models.BackendModels.Unit; -using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore; -public class FrancescosRecipesWorldDbContext : DbContext -{ - public FrancescosRecipesWorldDbContext(DbContextOptions options) - : base(options) + public class FrancescosRecipesWorldDbContext : DbContext { - } - - public DbSet Categories => Set(); - public DbSet Ingredients => Set(); - public DbSet Instructions => Set(); - public DbSet Recipes => Set(); - public DbSet RecipeIngredients => Set(); - public DbSet Units => Set(); - public DbSet Favorits => Set(); - public DbSet IngredientsShoppingLists => Set(); - public DbSet ShoppingLists => Set(); - public DbSet MediaFiles => Set(); - public override Task SaveChangesAsync(CancellationToken cancellationToken = default) - { - foreach (var entry in ChangeTracker.Entries()) + public FrancescosRecipesWorldDbContext(DbContextOptions options) + : base(options) { - if (entry.Entity is ITimeStampedEntity timeStampedEntity) + } + + public DbSet Categories => Set(); + public DbSet Ingredients => Set(); + public DbSet Instructions => Set(); + public DbSet Recipes => Set(); + public DbSet RecipeIngredients => Set(); + public DbSet Units => Set(); + public DbSet Favorits => Set(); + public DbSet IngredientsShoppingLists => Set(); + public DbSet ShoppingLists => Set(); + public DbSet MediaFiles => Set(); + public override Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + foreach (var entry in ChangeTracker.Entries()) { - switch (entry.State) + if (entry.Entity is ITimeStampedEntity timeStampedEntity) { - case EntityState.Added: - timeStampedEntity.CreatedAt = DateTime.UtcNow; - break; - case EntityState.Modified: - timeStampedEntity.ModifiedAt = DateTime.UtcNow; - break; + switch (entry.State) + { + case EntityState.Added: + timeStampedEntity.CreatedAt = DateTime.UtcNow; + break; + case EntityState.Modified: + timeStampedEntity.ModifiedAt = DateTime.UtcNow; + break; + } } } + + return base.SaveChangesAsync(cancellationToken); } - return base.SaveChangesAsync(cancellationToken); - } - protected override void OnModelCreating(ModelBuilder builder) - { - if (builder is null) + protected override void OnModelCreating(ModelBuilder builder) { - throw new ArgumentNullException(nameof(builder)); + ArgumentNullException.ThrowIfNull(builder); + + SeedData(builder); + + base.OnModelCreating(builder); } - SeedData(builder); - - base.OnModelCreating(builder); - } - - private static void SeedData(ModelBuilder modelBuilder) - { - modelBuilder.Entity() - .HasData(GetCategories()); - - modelBuilder.Entity() - .HasData(GetUnits()); - } - private static IEnumerable GetCategories() - { - return new List() + private static void SeedData(ModelBuilder modelBuilder) { - new Category { Id = Guid.Parse("b248244f-f21c-4555-a14d-5dd49a2717cf"), Name = "Vorspeisen & Snacks" }, + modelBuilder.Entity() + .HasData(GetCategories()); + + modelBuilder.Entity() + .HasData(GetUnits()); + } + + private static IEnumerable GetCategories() + { + return + [ + new Category { Id = Guid.Parse("b248244f-f21c-4555-a14d-5dd49a2717cf"), Name = "Vorspeisen & Snacks" }, new Category { Id = Guid.Parse("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"), Name = "Erste Gänge" }, new Category { Id = Guid.Parse("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"), Name = "Hauptgerichte" }, new Category { Id = Guid.Parse("90deec39-dcd0-422d-9018-ac8389e332e1"), Name = "Desserts & Süßspeisen" }, @@ -85,14 +83,14 @@ public class FrancescosRecipesWorldDbContext : DbContext new Category { Id = Guid.Parse("d332f88d-d241-48c5-a2f2-bfd124eada7e"), Name = "Soßen & Saucen" }, new Category { Id = Guid.Parse("23b1c740-e427-44f9-a6ea-d33d3f30f05a"), Name = "Marmeladen & Eingemachtes" }, new Category { Id = Guid.Parse("0a91a200-dc76-4e00-b38c-b38cab5b69d7"), Name = "Getränke" }, - }; - } + ]; + } - private static IEnumerable GetUnits() - { - return new List() + private static IEnumerable GetUnits() { - new Unit { Id = Guid.Parse("62eb2c11-8768-46fb-9db0-c77f940bb4aa"), Name = "liter", Symbol = "l" }, + return + [ + new Unit { Id = Guid.Parse("62eb2c11-8768-46fb-9db0-c77f940bb4aa"), Name = "liter", Symbol = "l" }, new Unit { Id = Guid.Parse("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"), Name = "gramm", Symbol = "g" }, new Unit { Id = Guid.Parse("7e3d1b86-ac48-45d6-814e-d3492a86db1d"), Name = "kilogramm", Symbol = "kg" }, new Unit { Id = Guid.Parse("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"), Name = "stücke", Symbol = "stk" }, @@ -103,6 +101,7 @@ public class FrancescosRecipesWorldDbContext : DbContext new Unit { Id = Guid.Parse("66556e0e-eb2b-4bc3-9a56-135dd508ed09"), Name = "zehe", Symbol = "zehe" }, new Unit { Id = Guid.Parse("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"), Name = "teelöffel", Symbol = "TL" }, new Unit { Id = Guid.Parse("24a91b89-3389-4465-89e4-2f70d2ea6fd7"), Name = "esslöffel", Symbol = "EL" }, - }; + ]; + } } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs b/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs index 5fa42d4..64934a2 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs @@ -4,7 +4,7 @@ public class Category { public Guid Id { get; set; } - public string Name { get; set; } + public string? Name { get; set; } public virtual ICollection Recipes { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs b/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs index e11bce3..902657a 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs @@ -7,8 +7,8 @@ namespace Francesco.Recipes.World.Models.BackendModels.Unit public class Unit { public Guid Id{ get; set; } - public string Name { get; set; } - public string Symbol { get; set; } + public string? Name { get; set; } + public string? Symbol { get; set; } public virtual ICollection RecipeIngredient { get; set; } = new List(); } } From 6125eb82ee492e4d7bdd66794a894bd4256d82b3 Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Tue, 18 Mar 2025 15:25:30 +0100 Subject: [PATCH 006/183] Correct Models --- .../Models/BackendModels/Category/Category.cs | 2 +- .../Models/BackendModels/Recipe/Recipe.cs | 47 +++++++++---------- .../Models/BackendModels/Unit/Unit.cs | 4 +- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs b/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs index 64934a2..5fa42d4 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs @@ -4,7 +4,7 @@ public class Category { public Guid Id { get; set; } - public string? Name { get; set; } + public string Name { get; set; } public virtual ICollection Recipes { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs b/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs index 68d180c..7d4e01e 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs @@ -1,25 +1,24 @@ -namespace Francesco.Recipes.World.Models.BackendModels.Recipe -{ - using Francesco.Recipes.World.Models.BackendModels.Category; - using Francesco.Recipes.World.Models.BackendModels.Favorit; - using Francesco.Recipes.World.Models.BackendModels.Instruction; - using Francesco.Recipes.World.Models.BackendModels.MediaFile; - using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; +namespace Francesco.Recipes.World.Models.BackendModels.Recipe; - public class Recipe : ITimeStampedEntity - { - public Guid Id { get; set; } - public string Name { get; set; } - public string? Description { get; set; } - public Difficulty Difficulty { get; set; } - public int Servings { get; set; } - public TimeSpan PreparationTime { get; set; } - public TimeSpan CookingTime { get; set; } - public bool IsFavorite { get; set; } - public DateTime CreatedAt { get; set; } - public DateTime? ModifiedAt { get; set; } - public virtual ICollection RecipeIngredients { get; set; } = new List(); - public virtual ICollection Instructions { get; set; } = new List(); - public virtual ICollection MediaFiles { get; set; } = new List(); - public virtual Favorit Favorit { get; set; } = new(); - } +using Francesco.Recipes.World.Models.BackendModels.Favorit; +using Francesco.Recipes.World.Models.BackendModels.Instruction; +using Francesco.Recipes.World.Models.BackendModels.MediaFile; +using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; + +public class Recipe : ITimeStampedEntity +{ + public Guid Id { get; set; } + public string Name { get; set; } + public string? Description { get; set; } + public Difficulty Difficulty { get; set; } + public int Servings { get; set; } + public TimeSpan PreparationTime { get; set; } + public TimeSpan CookingTime { get; set; } + public bool IsFavorite { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? ModifiedAt { get; set; } + public virtual ICollection RecipeIngredients { get; set; } = new List(); + public virtual ICollection Instructions { get; set; } = new List(); + public virtual ICollection MediaFiles { get; set; } = new List(); + public virtual Favorit Favorit { get; set; } = new(); +} diff --git a/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs b/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs index 902657a..e11bce3 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs @@ -7,8 +7,8 @@ namespace Francesco.Recipes.World.Models.BackendModels.Unit public class Unit { public Guid Id{ get; set; } - public string? Name { get; set; } - public string? Symbol { get; set; } + public string Name { get; set; } + public string Symbol { get; set; } public virtual ICollection RecipeIngredient { get; set; } = new List(); } } From dfd2d2ee3f6475cd1e732c659852470ee5b7e5a1 Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Tue, 18 Mar 2025 15:29:11 +0100 Subject: [PATCH 007/183] Delete unnecessary files --- .../Controllers/Favorit/FavoritController.cs | 6 -- .../Controllers/HomeController.cs | 32 --------- .../Ingredient/IngredientController.cs | 6 -- .../Instruction/InstructionController.cs | 6 -- .../MediaFile/MediaFilesController.cs | 9 --- .../Controllers/Recipe/RecipeController.cs | 6 -- .../ShoppingList/ShoppingLIstController.cs | 6 -- .../Repository/Category/CategoryRepository.cs | 6 -- .../Category/ICategoryRepository.cs | 6 -- .../Data/Repository/ErrorViewModel.cs | 9 --- .../Repository/Favorit/FavoritRepository.cs | 6 -- .../Repository/Favorit/IFavoritRepository.cs | 6 -- .../Ingredient/IIngredientRepository.cs | 17 ----- .../Ingredient/IngredientRepository.cs | 66 ------------------- .../Instruction/IInstructionsRepository.cs | 6 -- .../Instruction/InstructionsRepository.cs | 6 -- .../MediaFile/IMediaFileRepository.cs | 6 -- .../MediaFile/MediaFileRepository.cs | 6 -- .../Repository/Recipe/IRecipeRepository.cs | 6 -- .../Repository/Recipe/RecipeRepository.cs | 6 -- .../ShoppingLIst/IShoppingListRepository.cs | 6 -- .../ShoppingLIst/ShoppingListRepository.cs | 6 -- .../Data/Repository/Unit/IUnitRepository.cs | 6 -- .../Data/Repository/Unit/UnitRepository.cs | 6 -- .../Francesco.Recipes.World.csproj | 5 -- .../Services/Category/CategoryService.cs | 6 -- .../Services/Favorit/FavoritService.cs | 6 -- .../Services/Ingredient/IngredientService.cs | 6 -- .../Instruction/InstructionsService.cs | 6 -- .../Services/MediaFile/MediaFileService.cs | 6 -- .../Services/Recipe/RecipeService.cs | 6 -- .../ShoppingLIst/ShoppingListService.cs | 6 -- .../Services/Unit/UnitService.cs | 6 -- 33 files changed, 300 deletions(-) delete mode 100644 Francesco.Recipes.World/Controllers/Favorit/FavoritController.cs delete mode 100644 Francesco.Recipes.World/Controllers/HomeController.cs delete mode 100644 Francesco.Recipes.World/Controllers/Ingredient/IngredientController.cs delete mode 100644 Francesco.Recipes.World/Controllers/Instruction/InstructionController.cs delete mode 100644 Francesco.Recipes.World/Controllers/MediaFile/MediaFilesController.cs delete mode 100644 Francesco.Recipes.World/Controllers/Recipe/RecipeController.cs delete mode 100644 Francesco.Recipes.World/Controllers/ShoppingList/ShoppingLIstController.cs delete mode 100644 Francesco.Recipes.World/Data/Repository/Category/CategoryRepository.cs delete mode 100644 Francesco.Recipes.World/Data/Repository/Category/ICategoryRepository.cs delete mode 100644 Francesco.Recipes.World/Data/Repository/ErrorViewModel.cs delete mode 100644 Francesco.Recipes.World/Data/Repository/Favorit/FavoritRepository.cs delete mode 100644 Francesco.Recipes.World/Data/Repository/Favorit/IFavoritRepository.cs delete mode 100644 Francesco.Recipes.World/Data/Repository/Ingredient/IIngredientRepository.cs delete mode 100644 Francesco.Recipes.World/Data/Repository/Ingredient/IngredientRepository.cs delete mode 100644 Francesco.Recipes.World/Data/Repository/Instruction/IInstructionsRepository.cs delete mode 100644 Francesco.Recipes.World/Data/Repository/Instruction/InstructionsRepository.cs delete mode 100644 Francesco.Recipes.World/Data/Repository/MediaFile/IMediaFileRepository.cs delete mode 100644 Francesco.Recipes.World/Data/Repository/MediaFile/MediaFileRepository.cs delete mode 100644 Francesco.Recipes.World/Data/Repository/Recipe/IRecipeRepository.cs delete mode 100644 Francesco.Recipes.World/Data/Repository/Recipe/RecipeRepository.cs delete mode 100644 Francesco.Recipes.World/Data/Repository/ShoppingLIst/IShoppingListRepository.cs delete mode 100644 Francesco.Recipes.World/Data/Repository/ShoppingLIst/ShoppingListRepository.cs delete mode 100644 Francesco.Recipes.World/Data/Repository/Unit/IUnitRepository.cs delete mode 100644 Francesco.Recipes.World/Data/Repository/Unit/UnitRepository.cs delete mode 100644 Francesco.Recipes.World/Services/Category/CategoryService.cs delete mode 100644 Francesco.Recipes.World/Services/Favorit/FavoritService.cs delete mode 100644 Francesco.Recipes.World/Services/Ingredient/IngredientService.cs delete mode 100644 Francesco.Recipes.World/Services/Instruction/InstructionsService.cs delete mode 100644 Francesco.Recipes.World/Services/MediaFile/MediaFileService.cs delete mode 100644 Francesco.Recipes.World/Services/Recipe/RecipeService.cs delete mode 100644 Francesco.Recipes.World/Services/ShoppingLIst/ShoppingListService.cs delete mode 100644 Francesco.Recipes.World/Services/Unit/UnitService.cs diff --git a/Francesco.Recipes.World/Controllers/Favorit/FavoritController.cs b/Francesco.Recipes.World/Controllers/Favorit/FavoritController.cs deleted file mode 100644 index 7d66c1f..0000000 --- a/Francesco.Recipes.World/Controllers/Favorit/FavoritController.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Controllers.Favorit -{ - public class FavoritController - { - } -} diff --git a/Francesco.Recipes.World/Controllers/HomeController.cs b/Francesco.Recipes.World/Controllers/HomeController.cs deleted file mode 100644 index b5b0244..0000000 --- a/Francesco.Recipes.World/Controllers/HomeController.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Francesco.Recipes.World.Controllers -{ - using System.Diagnostics; - using Francesco.Recipes.World.Models; - using Microsoft.AspNetCore.Mvc; - - public class HomeController : Controller - { - private readonly ILogger _logger; - - public HomeController(ILogger logger) - { - _logger = logger; - } - - public IActionResult Index() - { - return View(); - } - - public IActionResult Privacy() - { - return View(); - } - - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - public IActionResult Error() - { - return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); - } - } -} diff --git a/Francesco.Recipes.World/Controllers/Ingredient/IngredientController.cs b/Francesco.Recipes.World/Controllers/Ingredient/IngredientController.cs deleted file mode 100644 index 187acb8..0000000 --- a/Francesco.Recipes.World/Controllers/Ingredient/IngredientController.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Controllers.Ingredient -{ - public class IngredientController - { - } -} diff --git a/Francesco.Recipes.World/Controllers/Instruction/InstructionController.cs b/Francesco.Recipes.World/Controllers/Instruction/InstructionController.cs deleted file mode 100644 index 17c9b8d..0000000 --- a/Francesco.Recipes.World/Controllers/Instruction/InstructionController.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Controllers.Instruction -{ - public class InstructionController - { - } -} diff --git a/Francesco.Recipes.World/Controllers/MediaFile/MediaFilesController.cs b/Francesco.Recipes.World/Controllers/MediaFile/MediaFilesController.cs deleted file mode 100644 index d17c5fd..0000000 --- a/Francesco.Recipes.World/Controllers/MediaFile/MediaFilesController.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Francesco.Recipes.World.Controllers.MediaFile -{ - using Microsoft.AspNetCore.Mvc; - - public class MediaFilesController : Controller - { - - } -} diff --git a/Francesco.Recipes.World/Controllers/Recipe/RecipeController.cs b/Francesco.Recipes.World/Controllers/Recipe/RecipeController.cs deleted file mode 100644 index a4b93ae..0000000 --- a/Francesco.Recipes.World/Controllers/Recipe/RecipeController.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Controllers.Recipe -{ - public class RecipeController - { - } -} diff --git a/Francesco.Recipes.World/Controllers/ShoppingList/ShoppingLIstController.cs b/Francesco.Recipes.World/Controllers/ShoppingList/ShoppingLIstController.cs deleted file mode 100644 index 03e77c6..0000000 --- a/Francesco.Recipes.World/Controllers/ShoppingList/ShoppingLIstController.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Controllers.ShoppingList -{ - public class ShoppingLIstController - { - } -} diff --git a/Francesco.Recipes.World/Data/Repository/Category/CategoryRepository.cs b/Francesco.Recipes.World/Data/Repository/Category/CategoryRepository.cs deleted file mode 100644 index 60e0775..0000000 --- a/Francesco.Recipes.World/Data/Repository/Category/CategoryRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Data.Repository.Category -{ - public class CategoryRepository - { - } -} diff --git a/Francesco.Recipes.World/Data/Repository/Category/ICategoryRepository.cs b/Francesco.Recipes.World/Data/Repository/Category/ICategoryRepository.cs deleted file mode 100644 index cfd14f4..0000000 --- a/Francesco.Recipes.World/Data/Repository/Category/ICategoryRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Data.Repository.Category -{ - public interface ICategoryRepository - { - } -} diff --git a/Francesco.Recipes.World/Data/Repository/ErrorViewModel.cs b/Francesco.Recipes.World/Data/Repository/ErrorViewModel.cs deleted file mode 100644 index 1f2259d..0000000 --- a/Francesco.Recipes.World/Data/Repository/ErrorViewModel.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Francesco.Recipes.World.Data.Repository -{ - public class ErrorViewModel - { - public string? RequestId { get; set; } - - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - } -} diff --git a/Francesco.Recipes.World/Data/Repository/Favorit/FavoritRepository.cs b/Francesco.Recipes.World/Data/Repository/Favorit/FavoritRepository.cs deleted file mode 100644 index 3292412..0000000 --- a/Francesco.Recipes.World/Data/Repository/Favorit/FavoritRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Data.Repository.Favorit -{ - public class FavoritRepository - { - } -} diff --git a/Francesco.Recipes.World/Data/Repository/Favorit/IFavoritRepository.cs b/Francesco.Recipes.World/Data/Repository/Favorit/IFavoritRepository.cs deleted file mode 100644 index 1691bb0..0000000 --- a/Francesco.Recipes.World/Data/Repository/Favorit/IFavoritRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Data.Repository.Favorit -{ - public interface IFavoritRepository - { - } -} diff --git a/Francesco.Recipes.World/Data/Repository/Ingredient/IIngredientRepository.cs b/Francesco.Recipes.World/Data/Repository/Ingredient/IIngredientRepository.cs deleted file mode 100644 index a24cb71..0000000 --- a/Francesco.Recipes.World/Data/Repository/Ingredient/IIngredientRepository.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace FrancescoRecipesWorld.Repositories -{ - using Francesco.Recipes.World.Models.BackendModels.Ingredient; - using Francesco.Recipes.World.Models.BackendModels.Recipe; - - public interface IIngredientRepository - { - Task CreateIngredientToRecipeAsync(int recipeId, string ingredientName); - Task UpdateIngredientAsync(Ingredient ingredient); - Task> GetIngredientsByRecipeIdAsync(Guid recipeId); - Task> GetRecipesByIngredientIdAsync(Guid ingredientId); - Task> GetIngredientsByNameAsync(string name); - Task RemoveIngredientFromRecipeAsync(Recipe recipe, Guid ingredientId); - Task GetIngredientByIdAsync(Guid ingredientId); - - } -} diff --git a/Francesco.Recipes.World/Data/Repository/Ingredient/IngredientRepository.cs b/Francesco.Recipes.World/Data/Repository/Ingredient/IngredientRepository.cs deleted file mode 100644 index 4d32131..0000000 --- a/Francesco.Recipes.World/Data/Repository/Ingredient/IngredientRepository.cs +++ /dev/null @@ -1,66 +0,0 @@ -namespace FrancescoRecipesWorld.Repositories -{ - using Francesco.Recipes.World.Data; - using Francesco.Recipes.World.Models.BackendModels.Ingredient; - using Francesco.Recipes.World.Models.BackendModels.Recipe; - - public class IngredientRepository : IIngredientRepository - { - private readonly FrancescosRecipesWorldDbContext _context; - private readonly RecipeRepository _recipeRepository; - public IngredientRepository( - FrancescosRecipesWorldDbContext context, RecipeRepository recipeRepository) - { - _context = context; - _recipeRepository = recipeRepository; - } - - public async Task CreateIngredientToRecipeAsync(int recipeId, string ingredientName) - { - var recipe = await _context.Recipes.FindAsync(recipeId); - if (recipe == null) - { - throw new ArgumentException("Recipe not found", nameof(recipeId)); - } - - var newIngredient = new Ingredient - { - Name = ingredientName, - }; - _context.Ingredients.Add(newIngredient); - await _context.SaveChangesAsync(); - return newIngredient; - } - - public Task RemoveIngredientFromRecipeAsync(Recipe recipe, Guid ingredientId) - { - throw new NotImplementedException(); - } - - public Task> GetIngredientsByNameAsync(string name) - { - throw new NotImplementedException(); - } - - public Task> GetIngredientsByRecipeIdAsync(Guid recipeId) - { - throw new NotImplementedException(); - } - - public Task> GetRecipesByIngredientIdAsync(Guid ingredientId) - { - throw new NotImplementedException(); - } - - public Task UpdateIngredientAsync(Ingredient ingredient) - { - throw new NotImplementedException(); - } - - public async Task GetIngredientByIdAsync(Guid ingredientId) - { - var ingredient = await _context.Ingredients.FindAsync(ingredientId); - return ingredient ?? throw new InvalidDataException($"Address {ingredientId} not found."); - } - } -} diff --git a/Francesco.Recipes.World/Data/Repository/Instruction/IInstructionsRepository.cs b/Francesco.Recipes.World/Data/Repository/Instruction/IInstructionsRepository.cs deleted file mode 100644 index 9eb5c68..0000000 --- a/Francesco.Recipes.World/Data/Repository/Instruction/IInstructionsRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Data.Repository.Instruction -{ - public interface IInstructionsRepository - { - } -} diff --git a/Francesco.Recipes.World/Data/Repository/Instruction/InstructionsRepository.cs b/Francesco.Recipes.World/Data/Repository/Instruction/InstructionsRepository.cs deleted file mode 100644 index 7306bb2..0000000 --- a/Francesco.Recipes.World/Data/Repository/Instruction/InstructionsRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Data.Repository.Instruction -{ - public class InstructionsRepository - { - } -} diff --git a/Francesco.Recipes.World/Data/Repository/MediaFile/IMediaFileRepository.cs b/Francesco.Recipes.World/Data/Repository/MediaFile/IMediaFileRepository.cs deleted file mode 100644 index e8c1058..0000000 --- a/Francesco.Recipes.World/Data/Repository/MediaFile/IMediaFileRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Data.Repository.MediaFile -{ - public interface IMEdiaFileRepository - { - } -} diff --git a/Francesco.Recipes.World/Data/Repository/MediaFile/MediaFileRepository.cs b/Francesco.Recipes.World/Data/Repository/MediaFile/MediaFileRepository.cs deleted file mode 100644 index 0c3490e..0000000 --- a/Francesco.Recipes.World/Data/Repository/MediaFile/MediaFileRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Data.Repository.MediaFile -{ - public class MediaFileRepository - { - } -} diff --git a/Francesco.Recipes.World/Data/Repository/Recipe/IRecipeRepository.cs b/Francesco.Recipes.World/Data/Repository/Recipe/IRecipeRepository.cs deleted file mode 100644 index cd4e97b..0000000 --- a/Francesco.Recipes.World/Data/Repository/Recipe/IRecipeRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Data.Repository.Recipe -{ - public interface IRecipeRepository - { - } -} diff --git a/Francesco.Recipes.World/Data/Repository/Recipe/RecipeRepository.cs b/Francesco.Recipes.World/Data/Repository/Recipe/RecipeRepository.cs deleted file mode 100644 index bd57464..0000000 --- a/Francesco.Recipes.World/Data/Repository/Recipe/RecipeRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Data.Repository.Recipe -{ - public class RecipeRepository - { - } -} diff --git a/Francesco.Recipes.World/Data/Repository/ShoppingLIst/IShoppingListRepository.cs b/Francesco.Recipes.World/Data/Repository/ShoppingLIst/IShoppingListRepository.cs deleted file mode 100644 index c7e3943..0000000 --- a/Francesco.Recipes.World/Data/Repository/ShoppingLIst/IShoppingListRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Data.Repository.ShoppingLIst -{ - public interface IShoppingListRepository - { - } -} diff --git a/Francesco.Recipes.World/Data/Repository/ShoppingLIst/ShoppingListRepository.cs b/Francesco.Recipes.World/Data/Repository/ShoppingLIst/ShoppingListRepository.cs deleted file mode 100644 index 5b0c2cb..0000000 --- a/Francesco.Recipes.World/Data/Repository/ShoppingLIst/ShoppingListRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Data.Repository.ShoppingLIst -{ - public class ShoppingListRepository - { - } -} diff --git a/Francesco.Recipes.World/Data/Repository/Unit/IUnitRepository.cs b/Francesco.Recipes.World/Data/Repository/Unit/IUnitRepository.cs deleted file mode 100644 index 443f0b1..0000000 --- a/Francesco.Recipes.World/Data/Repository/Unit/IUnitRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Data.Repository.Unit -{ - public interface IUnitRepository - { - } -} diff --git a/Francesco.Recipes.World/Data/Repository/Unit/UnitRepository.cs b/Francesco.Recipes.World/Data/Repository/Unit/UnitRepository.cs deleted file mode 100644 index ab66777..0000000 --- a/Francesco.Recipes.World/Data/Repository/Unit/UnitRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Data.Repository.Unit -{ - public class UnitRepository - { - } -} diff --git a/Francesco.Recipes.World/Francesco.Recipes.World.csproj b/Francesco.Recipes.World/Francesco.Recipes.World.csproj index fff88d5..f9e0493 100644 --- a/Francesco.Recipes.World/Francesco.Recipes.World.csproj +++ b/Francesco.Recipes.World/Francesco.Recipes.World.csproj @@ -25,7 +25,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -39,8 +38,4 @@ - - - - diff --git a/Francesco.Recipes.World/Services/Category/CategoryService.cs b/Francesco.Recipes.World/Services/Category/CategoryService.cs deleted file mode 100644 index df69cb8..0000000 --- a/Francesco.Recipes.World/Services/Category/CategoryService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Data.Repository.Category -{ - public class CategoryService - { - } -} diff --git a/Francesco.Recipes.World/Services/Favorit/FavoritService.cs b/Francesco.Recipes.World/Services/Favorit/FavoritService.cs deleted file mode 100644 index a37845b..0000000 --- a/Francesco.Recipes.World/Services/Favorit/FavoritService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Data.Repository.Favorit -{ - public class FavoritService - { - } -} diff --git a/Francesco.Recipes.World/Services/Ingredient/IngredientService.cs b/Francesco.Recipes.World/Services/Ingredient/IngredientService.cs deleted file mode 100644 index 8bdfb20..0000000 --- a/Francesco.Recipes.World/Services/Ingredient/IngredientService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Data.Repository.Ingredient -{ - public class IngredientService - { - } -} diff --git a/Francesco.Recipes.World/Services/Instruction/InstructionsService.cs b/Francesco.Recipes.World/Services/Instruction/InstructionsService.cs deleted file mode 100644 index 1b156dd..0000000 --- a/Francesco.Recipes.World/Services/Instruction/InstructionsService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Repository.Instructions -{ - public class InstructionsService - { - } -} diff --git a/Francesco.Recipes.World/Services/MediaFile/MediaFileService.cs b/Francesco.Recipes.World/Services/MediaFile/MediaFileService.cs deleted file mode 100644 index 03c64e1..0000000 --- a/Francesco.Recipes.World/Services/MediaFile/MediaFileService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Repository.MediaFile -{ - public class MediaFileService - { - } -} diff --git a/Francesco.Recipes.World/Services/Recipe/RecipeService.cs b/Francesco.Recipes.World/Services/Recipe/RecipeService.cs deleted file mode 100644 index f6bc9dc..0000000 --- a/Francesco.Recipes.World/Services/Recipe/RecipeService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Repository.Recipe -{ - public class RecipeService - { - } -} diff --git a/Francesco.Recipes.World/Services/ShoppingLIst/ShoppingListService.cs b/Francesco.Recipes.World/Services/ShoppingLIst/ShoppingListService.cs deleted file mode 100644 index 84b68ee..0000000 --- a/Francesco.Recipes.World/Services/ShoppingLIst/ShoppingListService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Repository.ShoppingLIst -{ - public class ShoppingListService - { - } -} diff --git a/Francesco.Recipes.World/Services/Unit/UnitService.cs b/Francesco.Recipes.World/Services/Unit/UnitService.cs deleted file mode 100644 index 82c2c8d..0000000 --- a/Francesco.Recipes.World/Services/Unit/UnitService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Francesco.Recipes.World.Repository.Unit -{ - public class UnitService - { - } -} From a91ca743ab54059c12c076fa95788c740ddefcfe Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Tue, 18 Mar 2025 15:45:14 +0100 Subject: [PATCH 008/183] Revert "Correct Models Unit and Category" This reverts commit 45702aa4f2fe4ea29a671744243d755370aeb384. --- .../Data/FrancescosRecipesWorldDbContext.cs | 21 ++++++++++++++++--- .../Models/BackendModels/Category/Category.cs | 2 +- .../Models/BackendModels/Unit/Unit.cs | 4 ++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs index 0f6c858..6a691b5 100644 --- a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs +++ b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs @@ -1,20 +1,22 @@ namespace Francesco.Recipes.World.Data { +<<<<<<< HEAD using Francesco.Recipes.World.Models.BackendModels; using Francesco.Recipes.World.Models.BackendModels.Category; using Francesco.Recipes.World.Models.BackendModels.Favorit; +======= + using Francesco.Recipes.World.Models.BackendModels.Category; +>>>>>>> parent of 45702aa (Correct Models Unit and Category) using Francesco.Recipes.World.Models.BackendModels.Ingredient; - using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; using Francesco.Recipes.World.Models.BackendModels.Instruction; - using Francesco.Recipes.World.Models.BackendModels.MediaFile; using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; - using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; using Francesco.Recipes.World.Models.BackendModels.Unit; using Microsoft.EntityFrameworkCore; public class FrancescosRecipesWorldDbContext : DbContext { +<<<<<<< HEAD public FrancescosRecipesWorldDbContext(DbContextOptions options) : base(options) { @@ -103,5 +105,18 @@ new Unit { Id = Guid.Parse("24a91b89-3389-4465-89e4-2f70d2ea6fd7"), Name = "esslöffel", Symbol = "EL" }, ]; } +======= + public FrancescosRecipesWorldDbContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Categories => Set(); + public DbSet Ingredients => Set(); + public DbSet Instructions => Set(); + public DbSet Recipes => Set(); + public DbSet RecipeIngredients => Set(); + public DbSet Units => Set(); +>>>>>>> parent of 45702aa (Correct Models Unit and Category) } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs b/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs index 5fa42d4..64934a2 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs @@ -4,7 +4,7 @@ public class Category { public Guid Id { get; set; } - public string Name { get; set; } + public string? Name { get; set; } public virtual ICollection Recipes { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs b/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs index e11bce3..902657a 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs @@ -7,8 +7,8 @@ namespace Francesco.Recipes.World.Models.BackendModels.Unit public class Unit { public Guid Id{ get; set; } - public string Name { get; set; } - public string Symbol { get; set; } + public string? Name { get; set; } + public string? Symbol { get; set; } public virtual ICollection RecipeIngredient { get; set; } = new List(); } } From c498e23a07d3715dbd7dfad6ce685d2732022310 Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Tue, 18 Mar 2025 16:14:01 +0100 Subject: [PATCH 009/183] Correct models unit and recipe and add a new migration --- .../Data/FrancescosRecipesWorldDbContext.cs | 21 +++------------- ...250318150949_InitialMIgration.Designer.cs} | 17 +++++++++++-- ....cs => 20250318150949_InitialMIgration.cs} | 24 ++++++++++--------- ...escosRecipesWorldDbContextModelSnapshot.cs | 13 ++++++++++ .../Models/BackendModels/Category/Category.cs | 2 +- .../Models/BackendModels/Unit/Unit.cs | 4 ++-- 6 files changed, 47 insertions(+), 34 deletions(-) rename Francesco.Recipes.World/Migrations/{20250318105823_InitialMigration.Designer.cs => 20250318150949_InitialMIgration.Designer.cs} (97%) rename Francesco.Recipes.World/Migrations/{20250318105823_InitialMigration.cs => 20250318150949_InitialMIgration.cs} (96%) diff --git a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs index 6a691b5..0f6c858 100644 --- a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs +++ b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs @@ -1,22 +1,20 @@ namespace Francesco.Recipes.World.Data { -<<<<<<< HEAD using Francesco.Recipes.World.Models.BackendModels; using Francesco.Recipes.World.Models.BackendModels.Category; using Francesco.Recipes.World.Models.BackendModels.Favorit; -======= - using Francesco.Recipes.World.Models.BackendModels.Category; ->>>>>>> parent of 45702aa (Correct Models Unit and Category) using Francesco.Recipes.World.Models.BackendModels.Ingredient; + using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; using Francesco.Recipes.World.Models.BackendModels.Instruction; + using Francesco.Recipes.World.Models.BackendModels.MediaFile; using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; + using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; using Francesco.Recipes.World.Models.BackendModels.Unit; using Microsoft.EntityFrameworkCore; public class FrancescosRecipesWorldDbContext : DbContext { -<<<<<<< HEAD public FrancescosRecipesWorldDbContext(DbContextOptions options) : base(options) { @@ -105,18 +103,5 @@ new Unit { Id = Guid.Parse("24a91b89-3389-4465-89e4-2f70d2ea6fd7"), Name = "esslöffel", Symbol = "EL" }, ]; } -======= - public FrancescosRecipesWorldDbContext(DbContextOptions options) - : base(options) - { - } - - public DbSet Categories => Set(); - public DbSet Ingredients => Set(); - public DbSet Instructions => Set(); - public DbSet Recipes => Set(); - public DbSet RecipeIngredients => Set(); - public DbSet Units => Set(); ->>>>>>> parent of 45702aa (Correct Models Unit and Category) } } diff --git a/Francesco.Recipes.World/Migrations/20250318105823_InitialMigration.Designer.cs b/Francesco.Recipes.World/Migrations/20250318150949_InitialMIgration.Designer.cs similarity index 97% rename from Francesco.Recipes.World/Migrations/20250318105823_InitialMigration.Designer.cs rename to Francesco.Recipes.World/Migrations/20250318150949_InitialMIgration.Designer.cs index ac67bcb..3f89444 100644 --- a/Francesco.Recipes.World/Migrations/20250318105823_InitialMigration.Designer.cs +++ b/Francesco.Recipes.World/Migrations/20250318150949_InitialMIgration.Designer.cs @@ -12,8 +12,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Francesco.Recipes.World.Migrations { [DbContext(typeof(FrancescosRecipesWorldDbContext))] - [Migration("20250318105823_InitialMigration")] - partial class InitialMigration + [Migration("20250318150949_InitialMIgration")] + partial class InitialMIgration { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -32,6 +32,7 @@ namespace Francesco.Recipes.World.Migrations .HasColumnType("uniqueidentifier"); b.Property("Name") + .IsRequired() .HasColumnType("nvarchar(max)"); b.HasKey("Id"); @@ -112,6 +113,7 @@ namespace Francesco.Recipes.World.Migrations .HasColumnType("uniqueidentifier"); b.Property("Name") + .IsRequired() .HasColumnType("nvarchar(max)"); b.HasKey("Id"); @@ -147,9 +149,11 @@ namespace Francesco.Recipes.World.Migrations .HasColumnType("uniqueidentifier"); b.Property("Description") + .IsRequired() .HasColumnType("nvarchar(max)"); b.Property("Number") + .IsRequired() .HasColumnType("nvarchar(max)"); b.Property("RecipeId") @@ -219,7 +223,11 @@ namespace Francesco.Recipes.World.Migrations b.Property("IsFavorite") .HasColumnType("bit"); + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + b.Property("Name") + .IsRequired() .HasColumnType("nvarchar(max)"); b.Property("PreparationTime") @@ -275,6 +283,9 @@ namespace Francesco.Recipes.World.Migrations b.Property("CreatedAt") .HasColumnType("datetime2"); + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + b.HasKey("Id"); b.ToTable("ShoppingLists"); @@ -287,9 +298,11 @@ namespace Francesco.Recipes.World.Migrations .HasColumnType("uniqueidentifier"); b.Property("Name") + .IsRequired() .HasColumnType("nvarchar(max)"); b.Property("Symbol") + .IsRequired() .HasColumnType("nvarchar(max)"); b.HasKey("Id"); diff --git a/Francesco.Recipes.World/Migrations/20250318105823_InitialMigration.cs b/Francesco.Recipes.World/Migrations/20250318150949_InitialMIgration.cs similarity index 96% rename from Francesco.Recipes.World/Migrations/20250318105823_InitialMigration.cs rename to Francesco.Recipes.World/Migrations/20250318150949_InitialMIgration.cs index 1e8d90d..2b14663 100644 --- a/Francesco.Recipes.World/Migrations/20250318105823_InitialMigration.cs +++ b/Francesco.Recipes.World/Migrations/20250318150949_InitialMIgration.cs @@ -8,12 +8,12 @@ using Microsoft.EntityFrameworkCore.Migrations; namespace Francesco.Recipes.World.Migrations { /// - public partial class InitialMigration : Migration + public partial class InitialMIgration : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { - if (migrationBuilder == null) + if (migrationBuilder is null) { throw new ArgumentNullException(nameof(migrationBuilder)); } @@ -22,7 +22,7 @@ namespace Francesco.Recipes.World.Migrations columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(max)", nullable: true) + Name = table.Column(type: "nvarchar(max)", nullable: false) }, constraints: table => { @@ -46,7 +46,7 @@ namespace Francesco.Recipes.World.Migrations columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(max)", nullable: true) + Name = table.Column(type: "nvarchar(max)", nullable: false) }, constraints: table => { @@ -58,7 +58,8 @@ namespace Francesco.Recipes.World.Migrations columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), - CreatedAt = table.Column(type: "datetime2", nullable: false) + CreatedAt = table.Column(type: "datetime2", nullable: false), + ModifiedAt = table.Column(type: "datetime2", nullable: true) }, constraints: table => { @@ -70,8 +71,8 @@ namespace Francesco.Recipes.World.Migrations columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(max)", nullable: true), - Symbol = table.Column(type: "nvarchar(max)", nullable: true) + Name = table.Column(type: "nvarchar(max)", nullable: false), + Symbol = table.Column(type: "nvarchar(max)", nullable: false) }, constraints: table => { @@ -83,7 +84,7 @@ namespace Francesco.Recipes.World.Migrations columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(max)", nullable: true), + Name = table.Column(type: "nvarchar(max)", nullable: false), Description = table.Column(type: "nvarchar(max)", nullable: true), Difficulty = table.Column(type: "int", nullable: false), Servings = table.Column(type: "int", nullable: false), @@ -91,6 +92,7 @@ namespace Francesco.Recipes.World.Migrations CookingTime = table.Column(type: "time", nullable: false), IsFavorite = table.Column(type: "bit", nullable: false), CreatedAt = table.Column(type: "datetime2", nullable: false), + ModifiedAt = table.Column(type: "datetime2", nullable: true), FavoritId = table.Column(type: "uniqueidentifier", nullable: false), CategoryId = table.Column(type: "uniqueidentifier", nullable: true) }, @@ -140,8 +142,8 @@ namespace Francesco.Recipes.World.Migrations columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Number = table.Column(type: "nvarchar(max)", nullable: true), + Description = table.Column(type: "nvarchar(max)", nullable: false), + Number = table.Column(type: "nvarchar(max)", nullable: false), RecipeId = table.Column(type: "uniqueidentifier", nullable: false) }, constraints: table => @@ -304,7 +306,7 @@ namespace Francesco.Recipes.World.Migrations /// protected override void Down(MigrationBuilder migrationBuilder) { - if (migrationBuilder == null) + if (migrationBuilder is null) { throw new ArgumentNullException(nameof(migrationBuilder)); } diff --git a/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs b/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs index 06d6ff4..e24e960 100644 --- a/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs +++ b/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs @@ -29,6 +29,7 @@ namespace Francesco.Recipes.World.Migrations .HasColumnType("uniqueidentifier"); b.Property("Name") + .IsRequired() .HasColumnType("nvarchar(max)"); b.HasKey("Id"); @@ -109,6 +110,7 @@ namespace Francesco.Recipes.World.Migrations .HasColumnType("uniqueidentifier"); b.Property("Name") + .IsRequired() .HasColumnType("nvarchar(max)"); b.HasKey("Id"); @@ -144,9 +146,11 @@ namespace Francesco.Recipes.World.Migrations .HasColumnType("uniqueidentifier"); b.Property("Description") + .IsRequired() .HasColumnType("nvarchar(max)"); b.Property("Number") + .IsRequired() .HasColumnType("nvarchar(max)"); b.Property("RecipeId") @@ -216,7 +220,11 @@ namespace Francesco.Recipes.World.Migrations b.Property("IsFavorite") .HasColumnType("bit"); + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + b.Property("Name") + .IsRequired() .HasColumnType("nvarchar(max)"); b.Property("PreparationTime") @@ -272,6 +280,9 @@ namespace Francesco.Recipes.World.Migrations b.Property("CreatedAt") .HasColumnType("datetime2"); + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + b.HasKey("Id"); b.ToTable("ShoppingLists"); @@ -284,9 +295,11 @@ namespace Francesco.Recipes.World.Migrations .HasColumnType("uniqueidentifier"); b.Property("Name") + .IsRequired() .HasColumnType("nvarchar(max)"); b.Property("Symbol") + .IsRequired() .HasColumnType("nvarchar(max)"); b.HasKey("Id"); diff --git a/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs b/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs index 64934a2..5fa42d4 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs @@ -4,7 +4,7 @@ public class Category { public Guid Id { get; set; } - public string? Name { get; set; } + public string Name { get; set; } public virtual ICollection Recipes { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs b/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs index 902657a..e11bce3 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs @@ -7,8 +7,8 @@ namespace Francesco.Recipes.World.Models.BackendModels.Unit public class Unit { public Guid Id{ get; set; } - public string? Name { get; set; } - public string? Symbol { get; set; } + public string Name { get; set; } + public string Symbol { get; set; } public virtual ICollection RecipeIngredient { get; set; } = new List(); } } From d1b8c3c7e0c24fba8e90a91c4fc2f550b467f1de Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Wed, 19 Mar 2025 10:52:01 +0100 Subject: [PATCH 010/183] Fix Formatting --- .../Data/FrancescosRecipesWorldDbContext.cs | 4 ++-- .../Models/BackendModels/Favorit/Favorit.cs | 1 + .../Models/BackendModels/Ingredient/Ingredient.cs | 1 - .../Models/BackendModels/Recipe/Difficulty.cs | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs index 0f6c858..5146867 100644 --- a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs +++ b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs @@ -73,7 +73,7 @@ { return [ - new Category { Id = Guid.Parse("b248244f-f21c-4555-a14d-5dd49a2717cf"), Name = "Vorspeisen & Snacks" }, + new Category { Id = Guid.Parse("b248244f-f21c-4555-a14d-5dd49a2717cf"), Name = "Vorspeisen & Snacks" }, new Category { Id = Guid.Parse("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"), Name = "Erste Gänge" }, new Category { Id = Guid.Parse("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"), Name = "Hauptgerichte" }, new Category { Id = Guid.Parse("90deec39-dcd0-422d-9018-ac8389e332e1"), Name = "Desserts & Süßspeisen" }, @@ -90,7 +90,7 @@ { return [ - new Unit { Id = Guid.Parse("62eb2c11-8768-46fb-9db0-c77f940bb4aa"), Name = "liter", Symbol = "l" }, + new Unit { Id = Guid.Parse("62eb2c11-8768-46fb-9db0-c77f940bb4aa"), Name = "liter", Symbol = "l" }, new Unit { Id = Guid.Parse("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"), Name = "gramm", Symbol = "g" }, new Unit { Id = Guid.Parse("7e3d1b86-ac48-45d6-814e-d3492a86db1d"), Name = "kilogramm", Symbol = "kg" }, new Unit { Id = Guid.Parse("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"), Name = "stücke", Symbol = "stk" }, diff --git a/Francesco.Recipes.World/Models/BackendModels/Favorit/Favorit.cs b/Francesco.Recipes.World/Models/BackendModels/Favorit/Favorit.cs index 538dd99..65b9aa9 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Favorit/Favorit.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Favorit/Favorit.cs @@ -1,6 +1,7 @@ namespace Francesco.Recipes.World.Models.BackendModels.Favorit { using Francesco.Recipes.World.Models.BackendModels.Recipe; + public class Favorit { public Guid Id { get; set; } diff --git a/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs b/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs index f8bf8eb..2a3e4d4 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs @@ -2,7 +2,6 @@ { using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; - using Francesco.Recipes.World.Models.BackendModels.Unit; public class Ingredient { diff --git a/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs b/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs index 3c3036e..91d20a0 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs @@ -6,7 +6,6 @@ Easy = 2, Medium = 3, Hard = 4, - Expert = 5 + Expert = 5, } - } From 5622e554419148c66557bcb8eaae260d4ac2b0ed Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Wed, 19 Mar 2025 13:56:21 +0100 Subject: [PATCH 011/183] Add some configuration to Project --- .editorconfig | 8 ++++++++ .../Francesco.Recipes.World.csproj | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/.editorconfig b/.editorconfig index 367a88a..dd05a8a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -142,3 +142,11 @@ dotnet_diagnostic.SA1309.severity = none # SA1309: Make sure class members are allowed to call without "this" prefix dotnet_diagnostic.SA1101.severity = none +# parameter spans multiple lines +dotnet_diagnostic.SA1118.severity = none +# closing square bracket spacing +dotnet_diagnostic.SA1011.severity = none + # no blank lines at start of file +dotnet_diagnostic.SA1517.severity = none + # using directive should appear within a namespace +dotnet_diagnostic.SA1200.severity = none diff --git a/Francesco.Recipes.World/Francesco.Recipes.World.csproj b/Francesco.Recipes.World/Francesco.Recipes.World.csproj index f9e0493..984ea7a 100644 --- a/Francesco.Recipes.World/Francesco.Recipes.World.csproj +++ b/Francesco.Recipes.World/Francesco.Recipes.World.csproj @@ -6,6 +6,10 @@ enable be6a70eb-b657-40b2-b4a1-418e5c6ec131 + + true + $(SolutionDir)Francesco.Recipes.World.ruleset + @@ -38,4 +42,11 @@ + + + + + + + From 906d26c2ec7b85a622f415e68c54fa87da490b27 Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Wed, 19 Mar 2025 14:22:55 +0100 Subject: [PATCH 012/183] Edit .gitignore file --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ad9a362..5cd6a1e 100644 --- a/.gitignore +++ b/.gitignore @@ -397,4 +397,4 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml src/Recruitment.Tool.xml -/Francesco.Recipes.World/Migrations/20250304095839_AddFewNewBackendModels.Designer.cs + From ce2a5aceb2b93fa74e1052748312da118ae672fd Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Wed, 19 Mar 2025 14:32:33 +0100 Subject: [PATCH 013/183] update Microsoft.CodeAnalysis.NetAnalyzers to 9.0.0 --- Francesco.Recipes.World/Francesco.Recipes.World.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Francesco.Recipes.World.csproj b/Francesco.Recipes.World/Francesco.Recipes.World.csproj index 984ea7a..728f575 100644 --- a/Francesco.Recipes.World/Francesco.Recipes.World.csproj +++ b/Francesco.Recipes.World/Francesco.Recipes.World.csproj @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 29faf5a99ef2c2e7242b2d8b0504006ac39d4ace Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Wed, 19 Mar 2025 15:50:33 +0100 Subject: [PATCH 014/183] Changed xml file on .gitignore --- .editorconfig | 8 ++++---- .gitignore | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.editorconfig b/.editorconfig index dd05a8a..b0d8970 100644 --- a/.editorconfig +++ b/.editorconfig @@ -142,11 +142,11 @@ dotnet_diagnostic.SA1309.severity = none # SA1309: Make sure class members are allowed to call without "this" prefix dotnet_diagnostic.SA1101.severity = none -# parameter spans multiple lines +# SA1118: parameter spans multiple lines dotnet_diagnostic.SA1118.severity = none -# closing square bracket spacing +# SA1011: closing square bracket spacing dotnet_diagnostic.SA1011.severity = none - # no blank lines at start of file +# SA1517: no blank lines at start of file dotnet_diagnostic.SA1517.severity = none - # using directive should appear within a namespace +# SA1200: using directive should appear within a namespace dotnet_diagnostic.SA1200.severity = none diff --git a/.gitignore b/.gitignore index 5cd6a1e..ed90c2d 100644 --- a/.gitignore +++ b/.gitignore @@ -396,5 +396,5 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml -src/Recruitment.Tool.xml +src/Francesco.Recipes.World.xml From b9d0bf21704165e585dd52ea91c223fdcc3a2e22 Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Wed, 19 Mar 2025 16:28:12 +0100 Subject: [PATCH 015/183] Edit .editconfig file --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index b0d8970..6e06f07 100644 --- a/.editorconfig +++ b/.editorconfig @@ -126,7 +126,7 @@ dotnet_diagnostic.SA1601.severity = none dotnet_analyzer_diagnostic.category-StyleCop.CSharp.DocumentationRules.severity = none # SA1516: Elements should be separated by blank line -dotnet_diagnostic.SA1516.severity = none +dotnet_diagnostic.SA1516.severity = error # SA1649: File name should match first type name dotnet_diagnostic.SA1649.severity = none From 02533e9bfb10acf7e88230a104132e158a1965c8 Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Wed, 19 Mar 2025 16:33:40 +0100 Subject: [PATCH 016/183] Last files for merge --- .../20250318150949_InitialMIgration.cs | 34 ++++++++++--------- .../Models/BackendModels/Category/Category.cs | 2 +- .../BackendModels/Ingredient/Ingredient.cs | 2 +- .../IngredientsShoppingList.cs | 4 +-- .../BackendModels/Instruction/Instruction.cs | 6 ++-- .../BackendModels/MediaFile/MediaFile.cs | 4 +-- .../Models/BackendModels/Recipe/Recipe.cs | 4 +-- .../RecipeIngredient/RecipeIngredient.cs | 6 ++-- .../Shoppinglist/ShoppingList.cs | 6 ++-- .../Models/BackendModels/Unit/Unit.cs | 15 ++++---- 10 files changed, 41 insertions(+), 42 deletions(-) diff --git a/Francesco.Recipes.World/Migrations/20250318150949_InitialMIgration.cs b/Francesco.Recipes.World/Migrations/20250318150949_InitialMIgration.cs index 2b14663..18ff0ed 100644 --- a/Francesco.Recipes.World/Migrations/20250318150949_InitialMIgration.cs +++ b/Francesco.Recipes.World/Migrations/20250318150949_InitialMIgration.cs @@ -1,12 +1,12 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable +#nullable disable #pragma warning disable CA1814 // Prefer jagged arrays over multidimensional namespace Francesco.Recipes.World.Migrations { + using System; + using Microsoft.EntityFrameworkCore.Migrations; + /// public partial class InitialMIgration : Migration { @@ -17,12 +17,13 @@ namespace Francesco.Recipes.World.Migrations { throw new ArgumentNullException(nameof(migrationBuilder)); } + migrationBuilder.CreateTable( name: "Categories", columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(max)", nullable: false) + Name = table.Column(type: "nvarchar(max)", nullable: false), }, constraints: table => { @@ -34,7 +35,7 @@ namespace Francesco.Recipes.World.Migrations columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), - CreatedAt = table.Column(type: "datetime2", nullable: false) + CreatedAt = table.Column(type: "datetime2", nullable: false), }, constraints: table => { @@ -46,7 +47,7 @@ namespace Francesco.Recipes.World.Migrations columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(max)", nullable: false) + Name = table.Column(type: "nvarchar(max)", nullable: false), }, constraints: table => { @@ -59,7 +60,7 @@ namespace Francesco.Recipes.World.Migrations { Id = table.Column(type: "uniqueidentifier", nullable: false), CreatedAt = table.Column(type: "datetime2", nullable: false), - ModifiedAt = table.Column(type: "datetime2", nullable: true) + ModifiedAt = table.Column(type: "datetime2", nullable: true), }, constraints: table => { @@ -72,7 +73,7 @@ namespace Francesco.Recipes.World.Migrations { Id = table.Column(type: "uniqueidentifier", nullable: false), Name = table.Column(type: "nvarchar(max)", nullable: false), - Symbol = table.Column(type: "nvarchar(max)", nullable: false) + Symbol = table.Column(type: "nvarchar(max)", nullable: false), }, constraints: table => { @@ -94,7 +95,7 @@ namespace Francesco.Recipes.World.Migrations CreatedAt = table.Column(type: "datetime2", nullable: false), ModifiedAt = table.Column(type: "datetime2", nullable: true), FavoritId = table.Column(type: "uniqueidentifier", nullable: false), - CategoryId = table.Column(type: "uniqueidentifier", nullable: true) + CategoryId = table.Column(type: "uniqueidentifier", nullable: true), }, constraints: table => { @@ -118,7 +119,7 @@ namespace Francesco.Recipes.World.Migrations { Id = table.Column(type: "uniqueidentifier", nullable: false), ShoppinglistId = table.Column(type: "uniqueidentifier", nullable: false), - IngredientId = table.Column(type: "uniqueidentifier", nullable: false) + IngredientId = table.Column(type: "uniqueidentifier", nullable: false), }, constraints: table => { @@ -144,7 +145,7 @@ namespace Francesco.Recipes.World.Migrations Id = table.Column(type: "uniqueidentifier", nullable: false), Description = table.Column(type: "nvarchar(max)", nullable: false), Number = table.Column(type: "nvarchar(max)", nullable: false), - RecipeId = table.Column(type: "uniqueidentifier", nullable: false) + RecipeId = table.Column(type: "uniqueidentifier", nullable: false), }, constraints: table => { @@ -165,7 +166,7 @@ namespace Francesco.Recipes.World.Migrations RecipeId = table.Column(type: "uniqueidentifier", nullable: false), IngredientId = table.Column(type: "uniqueidentifier", nullable: false), UnitId = table.Column(type: "uniqueidentifier", nullable: false), - Quantity = table.Column(type: "int", nullable: false) + Quantity = table.Column(type: "int", nullable: false), }, constraints: table => { @@ -199,7 +200,7 @@ namespace Francesco.Recipes.World.Migrations MimeType = table.Column(type: "nvarchar(max)", nullable: true), Data = table.Column(type: "varbinary(max)", nullable: true), RecipeId = table.Column(type: "uniqueidentifier", nullable: true), - InstructionId = table.Column(type: "uniqueidentifier", nullable: false) + InstructionId = table.Column(type: "uniqueidentifier", nullable: false), }, constraints: table => { @@ -231,7 +232,7 @@ namespace Francesco.Recipes.World.Migrations { new Guid("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"), "Hauptgerichte" }, { new Guid("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"), "Hefegebäck & Brot" }, { new Guid("b248244f-f21c-4555-a14d-5dd49a2717cf"), "Vorspeisen & Snacks" }, - { new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"), "Soßen & Saucen" } + { new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"), "Soßen & Saucen" }, }); migrationBuilder.InsertData( @@ -249,7 +250,7 @@ namespace Francesco.Recipes.World.Migrations { new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"), "bund", "bund" }, { new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"), "blatt", "blatt" }, { new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"), "messerspitze", "msp" }, - { new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"), "stange", "stange" } + { new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"), "stange", "stange" }, }); migrationBuilder.CreateIndex( @@ -310,6 +311,7 @@ namespace Francesco.Recipes.World.Migrations { throw new ArgumentNullException(nameof(migrationBuilder)); } + migrationBuilder.DropTable( name: "IngredientsShoppingLists"); diff --git a/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs b/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs index 5fa42d4..23f13b6 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs @@ -4,7 +4,7 @@ public class Category { public Guid Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public virtual ICollection Recipes { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs b/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs index 2a3e4d4..87797d2 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs @@ -6,7 +6,7 @@ public class Ingredient { public Guid Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public virtual ICollection RecipeIngredients { get; set; } = new List(); public virtual ICollection IngredientShoppingLists { get; set; } = new List(); } diff --git a/Francesco.Recipes.World/Models/BackendModels/IngredientShoppingList/IngredientsShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/IngredientShoppingList/IngredientsShoppingList.cs index c527693..9e0fb38 100644 --- a/Francesco.Recipes.World/Models/BackendModels/IngredientShoppingList/IngredientsShoppingList.cs +++ b/Francesco.Recipes.World/Models/BackendModels/IngredientShoppingList/IngredientsShoppingList.cs @@ -6,7 +6,7 @@ public class IngredientsShoppingList { public Guid Id { get; set; } - public ShoppingList Shoppinglist { get; set; } = new(); - public Ingredient Ingredient { get; set; } = new(); + public ShoppingList Shoppinglist { get; set; } = new (); + public Ingredient Ingredient { get; set; } = new (); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs b/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs index 6f0919c..0c2e855 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs @@ -5,9 +5,9 @@ public class Instruction { public Guid Id { get; set; } - public string Description { get; set; } - public string Number { get; set; } - public virtual Recipe Recipe { get; set; } = new(); + public string Description { get; set; } = string.Empty; + public int Number { get; set; } + public virtual Recipe Recipe { get; set; } = new (); public virtual ICollection MediaFiles { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/MediaFile/MediaFile.cs b/Francesco.Recipes.World/Models/BackendModels/MediaFile/MediaFile.cs index 56c083a..bafcd48 100644 --- a/Francesco.Recipes.World/Models/BackendModels/MediaFile/MediaFile.cs +++ b/Francesco.Recipes.World/Models/BackendModels/MediaFile/MediaFile.cs @@ -11,7 +11,7 @@ public string? MimeType { get; set; } public byte[]? Data { get; set; } - public virtual Recipe? Recipe { get; set; } = new(); - public virtual Instruction Instruction { get; set; } = new(); + public virtual Recipe? Recipe { get; set; } = new (); + public virtual Instruction Instruction { get; set; } = new (); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs b/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs index 7d4e01e..0e46f85 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs @@ -8,7 +8,7 @@ using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; public class Recipe : ITimeStampedEntity { public Guid Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public string? Description { get; set; } public Difficulty Difficulty { get; set; } public int Servings { get; set; } @@ -20,5 +20,5 @@ public class Recipe : ITimeStampedEntity public virtual ICollection RecipeIngredients { get; set; } = new List(); public virtual ICollection Instructions { get; set; } = new List(); public virtual ICollection MediaFiles { get; set; } = new List(); - public virtual Favorit Favorit { get; set; } = new(); + public virtual Favorit Favorit { get; set; } = new (); } diff --git a/Francesco.Recipes.World/Models/BackendModels/RecipeIngredient/RecipeIngredient.cs b/Francesco.Recipes.World/Models/BackendModels/RecipeIngredient/RecipeIngredient.cs index 30456ad..876da46 100644 --- a/Francesco.Recipes.World/Models/BackendModels/RecipeIngredient/RecipeIngredient.cs +++ b/Francesco.Recipes.World/Models/BackendModels/RecipeIngredient/RecipeIngredient.cs @@ -6,9 +6,9 @@ public class RecipeIngredient { public Guid Id { get; set; } - public virtual Recipe Recipe { get; set; } = new(); - public virtual Ingredient Ingredient { get; set; } = new(); - public virtual Unit Unit { get; set; } = new(); + public virtual Recipe Recipe { get; set; } = new (); + public virtual Ingredient Ingredient { get; set; } = new (); + public virtual Unit Unit { get; set; } = new (); public int Quantity { get; set; } } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs index a6f764e..95ffcb7 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs @@ -1,7 +1,7 @@ -using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; - -namespace Francesco.Recipes.World.Models.BackendModels.Shoppinglist +namespace Francesco.Recipes.World.Models.BackendModels.Shoppinglist { + using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; + public class ShoppingList : ITimeStampedEntity { public Guid Id { get; set; } diff --git a/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs b/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs index e11bce3..94ab6a8 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs @@ -1,14 +1,11 @@ - +namespace Francesco.Recipes.World.Models.BackendModels.Unit; -namespace Francesco.Recipes.World.Models.BackendModels.Unit -{ - using Francesco.Recipes.World.Models.BackendModels.Ingredient; using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; + public class Unit { - public Guid Id{ get; set; } - public string Name { get; set; } - public string Symbol { get; set; } + public Guid Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Symbol { get; set; } = string.Empty; public virtual ICollection RecipeIngredient { get; set; } = new List(); - } -} + } From 41f41ce3fbe5e4ffa6f98f1bdc0351af63628d71 Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Mon, 24 Mar 2025 09:27:13 +0100 Subject: [PATCH 017/183] Fixed errors SA1516 --- .../Data/FrancescosRecipesWorldDbContext.cs | 15 +++++++++--- ...escosRecipesWorldDbContextModelSnapshot.cs | 5 ++-- .../Models/BackendModels/Category/Category.cs | 3 +++ .../Models/BackendModels/Favorit/Favorit.cs | 2 ++ .../BackendModels/Ingredient/Ingredient.cs | 3 +++ .../IngredientsShoppingList.cs | 2 ++ .../BackendModels/Instruction/Instruction.cs | 5 ++++ .../BackendModels/MediaFile/MediaFile.cs | 3 +++ .../Models/BackendModels/Recipe/Difficulty.cs | 4 ++++ .../Models/BackendModels/Recipe/Recipe.cs | 13 +++++++++++ .../RecipeIngredient/RecipeIngredient.cs | 5 ++++ .../Shoppinglist/ShoppingList.cs | 3 +++ .../Models/BackendModels/Unit/Unit.cs | 8 +++++-- Francesco.Recipes.World/Program.cs | 23 ++++++++++++++++--- 14 files changed, 83 insertions(+), 11 deletions(-) diff --git a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs index 5146867..67117c4 100644 --- a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs +++ b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs @@ -1,5 +1,5 @@ -namespace Francesco.Recipes.World.Data -{ +namespace Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Models.BackendModels; using Francesco.Recipes.World.Models.BackendModels.Category; using Francesco.Recipes.World.Models.BackendModels.Favorit; @@ -21,15 +21,25 @@ } public DbSet Categories => Set(); + public DbSet Ingredients => Set(); + public DbSet Instructions => Set(); + public DbSet Recipes => Set(); + public DbSet RecipeIngredients => Set(); + public DbSet Units => Set(); + public DbSet Favorits => Set(); + public DbSet IngredientsShoppingLists => Set(); + public DbSet ShoppingLists => Set(); + public DbSet MediaFiles => Set(); + public override Task SaveChangesAsync(CancellationToken cancellationToken = default) { foreach (var entry in ChangeTracker.Entries()) @@ -104,4 +114,3 @@ ]; } } -} diff --git a/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs b/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs index e24e960..130fbf5 100644 --- a/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs +++ b/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs @@ -1,10 +1,9 @@ // -using System; + using Francesco.Recipes.World.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + #nullable disable diff --git a/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs b/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs index 23f13b6..b088952 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Category/Category.cs @@ -1,10 +1,13 @@ namespace Francesco.Recipes.World.Models.BackendModels.Category { using Francesco.Recipes.World.Models.BackendModels.Recipe; + public class Category { public Guid Id { get; set; } + public string Name { get; set; } = string.Empty; + public virtual ICollection Recipes { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Favorit/Favorit.cs b/Francesco.Recipes.World/Models/BackendModels/Favorit/Favorit.cs index 65b9aa9..97e12ed 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Favorit/Favorit.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Favorit/Favorit.cs @@ -5,7 +5,9 @@ public class Favorit { public Guid Id { get; set; } + public DateTime CreatedAt { get; set; } + public virtual ICollection Recipe { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs b/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs index 87797d2..c4c43c3 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs @@ -6,8 +6,11 @@ public class Ingredient { public Guid Id { get; set; } + public string Name { get; set; } = string.Empty; + public virtual ICollection RecipeIngredients { get; set; } = new List(); + public virtual ICollection IngredientShoppingLists { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/IngredientShoppingList/IngredientsShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/IngredientShoppingList/IngredientsShoppingList.cs index 9e0fb38..78f92d7 100644 --- a/Francesco.Recipes.World/Models/BackendModels/IngredientShoppingList/IngredientsShoppingList.cs +++ b/Francesco.Recipes.World/Models/BackendModels/IngredientShoppingList/IngredientsShoppingList.cs @@ -6,7 +6,9 @@ public class IngredientsShoppingList { public Guid Id { get; set; } + public ShoppingList Shoppinglist { get; set; } = new (); + public Ingredient Ingredient { get; set; } = new (); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs b/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs index 0c2e855..77a16d1 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Instruction/Instruction.cs @@ -2,12 +2,17 @@ { using Francesco.Recipes.World.Models.BackendModels.MediaFile; using Francesco.Recipes.World.Models.BackendModels.Recipe; + public class Instruction { public Guid Id { get; set; } + public string Description { get; set; } = string.Empty; + public int Number { get; set; } + public virtual Recipe Recipe { get; set; } = new (); + public virtual ICollection MediaFiles { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/MediaFile/MediaFile.cs b/Francesco.Recipes.World/Models/BackendModels/MediaFile/MediaFile.cs index bafcd48..f8a8218 100644 --- a/Francesco.Recipes.World/Models/BackendModels/MediaFile/MediaFile.cs +++ b/Francesco.Recipes.World/Models/BackendModels/MediaFile/MediaFile.cs @@ -2,6 +2,7 @@ { using Francesco.Recipes.World.Models.BackendModels.Instruction; using Francesco.Recipes.World.Models.BackendModels.Recipe; + public class MediaFile { public Guid Id { get; set; } = Guid.NewGuid(); @@ -11,7 +12,9 @@ public string? MimeType { get; set; } public byte[]? Data { get; set; } + public virtual Recipe? Recipe { get; set; } = new (); + public virtual Instruction Instruction { get; set; } = new (); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs b/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs index 91d20a0..4456670 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs @@ -3,9 +3,13 @@ public enum Difficulty { VeryEasy = 1, + Easy = 2, + Medium = 3, + Hard = 4, + Expert = 5, } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs b/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs index 0e46f85..da673d7 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs @@ -8,17 +8,30 @@ using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; public class Recipe : ITimeStampedEntity { public Guid Id { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public Difficulty Difficulty { get; set; } + public int Servings { get; set; } + public TimeSpan PreparationTime { get; set; } + public TimeSpan CookingTime { get; set; } + public bool IsFavorite { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? ModifiedAt { get; set; } + public virtual ICollection RecipeIngredients { get; set; } = new List(); + public virtual ICollection Instructions { get; set; } = new List(); + public virtual ICollection MediaFiles { get; set; } = new List(); + public virtual Favorit Favorit { get; set; } = new (); } diff --git a/Francesco.Recipes.World/Models/BackendModels/RecipeIngredient/RecipeIngredient.cs b/Francesco.Recipes.World/Models/BackendModels/RecipeIngredient/RecipeIngredient.cs index 876da46..d64abb5 100644 --- a/Francesco.Recipes.World/Models/BackendModels/RecipeIngredient/RecipeIngredient.cs +++ b/Francesco.Recipes.World/Models/BackendModels/RecipeIngredient/RecipeIngredient.cs @@ -3,12 +3,17 @@ using Francesco.Recipes.World.Models.BackendModels.Ingredient; using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Models.BackendModels.Unit; + public class RecipeIngredient { public Guid Id { get; set; } + public virtual Recipe Recipe { get; set; } = new (); + public virtual Ingredient Ingredient { get; set; } = new (); + public virtual Unit Unit { get; set; } = new (); + public int Quantity { get; set; } } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs index 95ffcb7..71fc7e7 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs @@ -5,8 +5,11 @@ public class ShoppingList : ITimeStampedEntity { public Guid Id { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? ModifiedAt { get; set; } + public virtual ICollection IngredientsShoppingLists { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs b/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs index 94ab6a8..ba8a539 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Unit/Unit.cs @@ -1,11 +1,15 @@ -namespace Francesco.Recipes.World.Models.BackendModels.Unit; - +namespace Francesco.Recipes.World.Models.BackendModels.Unit +{ using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; public class Unit { public Guid Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Symbol { get; set; } = string.Empty; + public virtual ICollection RecipeIngredient { get; set; } = new List(); } +} diff --git a/Francesco.Recipes.World/Program.cs b/Francesco.Recipes.World/Program.cs index c36c4ac..6d773ec 100644 --- a/Francesco.Recipes.World/Program.cs +++ b/Francesco.Recipes.World/Program.cs @@ -1,15 +1,22 @@ using Francesco.Recipes.World.Data; -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; -using Serilog; +using Francesco.Recipes.World.Repositories; + +using FrancescoRecipesWorld.Repositories; + +using Microsoft.AspNetCore.Identity; + +using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); + var services = builder.Services; + var configuration = builder.Configuration; var connectionString = builder.Configuration.GetConnectionString("FrancescosRecipesWorldDbContextConnection") ?? throw new InvalidOperationException("Connection string 'FrancescosRecipesWorldDbContextConnection' not found."); + services.AddDbContext(options => options.UseSqlServer(connectionString)); @@ -19,6 +26,14 @@ services.AddDefaultIdentity(options => options.SignIn.RequireConfi // Add services to the container. builder.Services.AddControllersWithViews(); +builder.Services.AddScoped(); + +builder.Services.AddScoped(); + +builder.Services.AddScoped(); + +builder.Services.AddScoped(); + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -31,9 +46,11 @@ if (!app.Environment.IsDevelopment()) } app.UseHttpsRedirection(); + app.UseStaticFiles(); app.UseRouting(); + app.UseAuthentication(); app.UseAuthorization(); From a4c8edb572c9a047c68ad94ece13ac1d4f071f71 Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Sun, 30 Mar 2025 15:53:26 +0200 Subject: [PATCH 018/183] Write Controller for testing Data --- .../Controller/Category/CategoryController.cs | 43 +++++ .../Ingredient/IngredientController.cs | 6 + .../Instruction/InstructionController.cs | 6 + .../MediaFile/MediaFileController.cs | 6 + .../Controller/Recipe/RecipeController.cs | 156 ++++++++++++++++++ .../ShoppingList/ShoppingListController.cs | 6 + .../Controller/Unit/UnitController.cs | 6 + 7 files changed, 229 insertions(+) create mode 100644 Francesco.Recipes.World/Controller/Category/CategoryController.cs create mode 100644 Francesco.Recipes.World/Controller/Ingredient/IngredientController.cs create mode 100644 Francesco.Recipes.World/Controller/Instruction/InstructionController.cs create mode 100644 Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs create mode 100644 Francesco.Recipes.World/Controller/Recipe/RecipeController.cs create mode 100644 Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs create mode 100644 Francesco.Recipes.World/Controller/Unit/UnitController.cs diff --git a/Francesco.Recipes.World/Controller/Category/CategoryController.cs b/Francesco.Recipes.World/Controller/Category/CategoryController.cs new file mode 100644 index 0000000..0257ab4 --- /dev/null +++ b/Francesco.Recipes.World/Controller/Category/CategoryController.cs @@ -0,0 +1,43 @@ +namespace Francesco.Recipes.World.Controller.Category +{ + using Francesco.Recipes.World.Models.BackendModels.Category; + using Francesco.Recipes.World.Models.BackendModels.Recipe; + using Francesco.Recipes.World.Repositories.Category; + using Microsoft.AspNetCore.Mvc; + + [Route("Category")] + + public class CategoryController : Controller + { + private readonly ICategoryRepository _categoryRepository; + + public CategoryController(ICategoryRepository categoryRepository) + { + _categoryRepository = categoryRepository; + } + + // GET: /Category + [HttpGet] + public async Task>> Index() + { + var categories = await _categoryRepository.GetAllCategoriesAsync(); + return View(categories); + } + + // GET: /Category/{id} + [HttpGet("{id:guid}")] + public async Task Details(Guid id) + { + var category = await _categoryRepository.GetCategoryByIdAsync(id); + return View(category); + } + + // GET: /Category/{id}/recipes + [HttpGet("{id:guid}/recipes")] + public async Task>> GetRecipesByCategory(Guid id) + { + var recipes = await _categoryRepository.GetRecipesByCategoryAsync(id); + return Ok(recipes); + } + } +} diff --git a/Francesco.Recipes.World/Controller/Ingredient/IngredientController.cs b/Francesco.Recipes.World/Controller/Ingredient/IngredientController.cs new file mode 100644 index 0000000..d59fb2a --- /dev/null +++ b/Francesco.Recipes.World/Controller/Ingredient/IngredientController.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Controller.Ingredient +{ + public class IngredientController + { + } +} diff --git a/Francesco.Recipes.World/Controller/Instruction/InstructionController.cs b/Francesco.Recipes.World/Controller/Instruction/InstructionController.cs new file mode 100644 index 0000000..d86aadf --- /dev/null +++ b/Francesco.Recipes.World/Controller/Instruction/InstructionController.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Controller.Instruction +{ + public class InstructionController + { + } +} diff --git a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs new file mode 100644 index 0000000..6cec16d --- /dev/null +++ b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Controller.MediaFile +{ + public class MediaFileController + { + } +} diff --git a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs new file mode 100644 index 0000000..5b21475 --- /dev/null +++ b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs @@ -0,0 +1,156 @@ +namespace Francesco.Recipes.World.Controller.Recipe +{ + using Francesco.Recipes.World.Models.BackendModels.Recipe; + using Francesco.Recipes.World.Repositories.Category; + using Francesco.Recipes.World.Repositories.Ingredient; + using Francesco.Recipes.World.Repositories.Recipe; + using Francesco.Recipes.World.Repositories.Unit; + using Francesco.Recipes.World.Views.Recipe; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Rendering; + + [Route("categories/{categoryId}/Recipe")] + public class RecipeController : Controller + { + private readonly IRecipeRepository _recipeRepository; + private readonly IUnitRepository _unitRepository; + private readonly ICategoryRepository _categoryRepository; + private readonly IIngredientRepository _ingredientRepository; + + public RecipeController(IRecipeRepository recipeRepository, IUnitRepository unitRepository, ICategoryRepository categoryRepository, IIngredientRepository ingredientRepository) + { + _recipeRepository = recipeRepository; + _unitRepository = unitRepository; + _categoryRepository = categoryRepository; + _ingredientRepository = ingredientRepository; + } + + // GET: /Recipe/AddOrCreateIngredient + [HttpGet("{recipeId}/AddOrCreateIngredient")] + public async Task AddOrCreateIngredient() + { + var units = await _unitRepository.GetAllUnitsAsync(); + ViewBag.Units = new SelectList(units, "Id", "Name"); + return View(); + } + + // POST: /Recipe/AddOrCreateIngredient + [HttpPost("{recipeId}/AddOrCreateIngredient")] + [ValidateAntiForgeryToken] + public async Task AddOrCreateIngredient(Guid recipeId, string ingredientName, int quantity, Guid unitId) + { + if (quantity <= 0) + { + ModelState.AddModelError(nameof(quantity), "Die Menge muss größer als 0 sein."); + } + + if (!ModelState.IsValid) + { + var units = await _unitRepository.GetAllUnitsAsync(); + ViewBag.Units = new SelectList(units, "Id", "Name"); + return View(); + } + + await _recipeRepository.AddOrCreateIngredientToRecipeAsync(recipeId, ingredientName, quantity, unitId); + return RedirectToAction("Details", new { id = recipeId }); + } + + // GET: /categories/{categoryId}/Recipe/Create + [HttpGet("Create")] + public async Task Create(Guid categoryId) + { + var category = await _categoryRepository.GetCategoryByIdAsync(categoryId); + if (category == null) + { + return NotFound($"Kategorie mit ID {categoryId} wurde nicht gefunden."); + } + + ViewBag.CategoryName = category.Name; + return View(); + } + + // POST: /categories/{categoryId}/Recipe/Create + [HttpPost("Create")] + [ValidateAntiForgeryToken] + public async Task Create(Guid categoryId, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime) + { + if (string.IsNullOrWhiteSpace(name)) + { + ModelState.AddModelError(nameof(name), "Name darf nicht leer sein."); + } + + if (servings <= 0) + { + ModelState.AddModelError(nameof(servings), "Anzahl der Portionen muss größer als 0 sein."); + } + + if (!ModelState.IsValid) + { + var category = await _categoryRepository.GetCategoryByIdAsync(categoryId); + if (category == null) + { + return NotFound($"Kategorie mit ID {categoryId} wurde nicht gefunden."); + } + + ViewBag.CategoryName = category.Name; + return View(); + } + + var categoryEntity = await _categoryRepository.GetCategoryByIdAsync(categoryId); + if (categoryEntity == null) + { + return NotFound($"Kategorie mit ID {categoryId} wurde nicht gefunden."); + } + + await _recipeRepository.CreateRecipeForCategoryAsync(categoryEntity, name, description, difficulty, servings, preparationTime, cookingTime); + return RedirectToAction("Details", "Category", new { id = categoryId }); + } + + // GET: /categories/{categoryId}/Recipe/{recipeId}/RemoveIngredient/{ingredientId} + [HttpGet("{recipeId}/RemoveIngredient/{ingredientId}")] + public async Task RemoveIngredient(Guid categoryId, Guid recipeId, Guid ingredientId) + { + var recipe = await _recipeRepository.GetRecipeAsync(recipeId); + var ingredient = await _ingredientRepository.GetIngredientByIdAsync(ingredientId); + + if (recipe == null || ingredient == null) + { + return NotFound("Recipe or Ingredient not found."); + } + + ViewBag.RecipeId = recipeId; + ViewBag.IngredientId = ingredientId; + ViewBag.CategoryId = categoryId; + ViewBag.IngredientName = ingredient.Name; + + return View(); + } + + // POST: /categories/{categoryId}/Recipe/{recipeId}/RemoveIngredient/{ingredientId} + [HttpPost("{recipeId}/RemoveIngredient/{ingredientId}")] + [ValidateAntiForgeryToken] + public async Task RemoveIngredientConfirmed(Guid categoryId, Guid recipeId, Guid ingredientId) + { + await _recipeRepository.RemoveIngredientFromRecipeAsync(recipeId, ingredientId); + TempData["SuccessMessage"] = "Ingredient removed successfully."; + return RedirectToAction("Details", new { id = recipeId }); + } + + // GET: /categories/{categoryId}/Recipe/FilterByDifficulty + [HttpGet("FilterByDifficulty")] + public async Task FilterByDifficulty(Difficulty? difficulty) + { + var viewModel = new FilterByDifficultyViewModel + { + SelectedDifficulty = difficulty, + }; + + if (difficulty.HasValue) + { + viewModel.Recipes = (await _recipeRepository.GetRecipesByDifficultyAsync(difficulty.Value)).ToList(); + } + + return View(viewModel); + } + } +} diff --git a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs new file mode 100644 index 0000000..38fdf4b --- /dev/null +++ b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Controller.ShoppingList +{ + public class ShoppingListController + { + } +} diff --git a/Francesco.Recipes.World/Controller/Unit/UnitController.cs b/Francesco.Recipes.World/Controller/Unit/UnitController.cs new file mode 100644 index 0000000..c0efcda --- /dev/null +++ b/Francesco.Recipes.World/Controller/Unit/UnitController.cs @@ -0,0 +1,6 @@ +namespace Francesco.Recipes.World.Controller.Unit +{ + public class UnitController + { + } +} From afde272d8a9a0c3f1a4c665423128ac4d61c206d Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Sun, 30 Mar 2025 15:55:19 +0200 Subject: [PATCH 019/183] Make new Migration and change logic of ingredientshoppinglist --- .../Data/FrancescosRecipesWorldDbContext.cs | 2 +- ...leRecipeIngredientShoppinglist.Designer.cs | 521 ++++++++++++++++++ ...ateNewTableRecipeIngredientShoppinglist.cs | 131 +++++ ...escosRecipesWorldDbContextModelSnapshot.cs | 44 +- 4 files changed, 678 insertions(+), 20 deletions(-) create mode 100644 Francesco.Recipes.World/Migrations/20250324124459_CreateNewTableRecipeIngredientShoppinglist.Designer.cs create mode 100644 Francesco.Recipes.World/Migrations/20250324124459_CreateNewTableRecipeIngredientShoppinglist.cs diff --git a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs index 67117c4..4fd6bd8 100644 --- a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs +++ b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs @@ -34,7 +34,7 @@ public DbSet Favorits => Set(); - public DbSet IngredientsShoppingLists => Set(); + public DbSet RecipeIngredientsShoppingLists => Set(); public DbSet ShoppingLists => Set(); diff --git a/Francesco.Recipes.World/Migrations/20250324124459_CreateNewTableRecipeIngredientShoppinglist.Designer.cs b/Francesco.Recipes.World/Migrations/20250324124459_CreateNewTableRecipeIngredientShoppinglist.Designer.cs new file mode 100644 index 0000000..6807402 --- /dev/null +++ b/Francesco.Recipes.World/Migrations/20250324124459_CreateNewTableRecipeIngredientShoppinglist.Designer.cs @@ -0,0 +1,521 @@ +// +using System; +using Francesco.Recipes.World.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Francesco.Recipes.World.Migrations +{ + [DbContext(typeof(FrancescosRecipesWorldDbContext))] + [Migration("20250324124459_CreateNewTableRecipeIngredientShoppinglist")] + partial class CreateNewTableRecipeIngredientShoppinglist + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + + b.HasData( + new + { + Id = new Guid("b248244f-f21c-4555-a14d-5dd49a2717cf"), + Name = "Vorspeisen & Snacks" + }, + new + { + Id = new Guid("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"), + Name = "Erste Gänge" + }, + new + { + Id = new Guid("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"), + Name = "Hauptgerichte" + }, + new + { + Id = new Guid("90deec39-dcd0-422d-9018-ac8389e332e1"), + Name = "Desserts & Süßspeisen" + }, + new + { + Id = new Guid("28e39168-701a-4084-81da-d96c987c462f"), + Name = "Beilagen & Salate" + }, + new + { + Id = new Guid("20585b74-4805-4aff-a6df-aa6b7af04ff1"), + Name = "Kuchen" + }, + new + { + Id = new Guid("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"), + Name = "Hefegebäck & Brot" + }, + new + { + Id = new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"), + Name = "Soßen & Saucen" + }, + new + { + Id = new Guid("23b1c740-e427-44f9-a6ea-d33d3f30f05a"), + Name = "Marmeladen & Eingemachtes" + }, + new + { + Id = new Guid("0a91a200-dc76-4e00-b38c-b38cab5b69d7"), + Name = "Getränke" + }); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Favorits"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Ingredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("IngredientId") + .HasColumnType("uniqueidentifier"); + + b.Property("RecipeIngredientId") + .HasColumnType("uniqueidentifier"); + + b.Property("ShoppingListId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IngredientId"); + + b.HasIndex("RecipeIngredientId"); + + b.HasIndex("ShoppingListId"); + + b.ToTable("RecipeIngredientsShoppingLists"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Number") + .HasColumnType("int"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RecipeId"); + + b.ToTable("Instructions"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Data") + .HasColumnType("varbinary(max)"); + + b.Property("FileName") + .HasColumnType("nvarchar(max)"); + + b.Property("InstructionId") + .HasColumnType("uniqueidentifier"); + + b.Property("MimeType") + .HasColumnType("nvarchar(max)"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("InstructionId"); + + b.HasIndex("RecipeId"); + + b.ToTable("MediaFiles"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("CookingTime") + .HasColumnType("time"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Difficulty") + .HasColumnType("int"); + + b.Property("FavoritId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsFavorite") + .HasColumnType("bit"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PreparationTime") + .HasColumnType("time"); + + b.Property("Servings") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("FavoritId"); + + b.ToTable("Recipes"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("IngredientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.Property("UnitId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IngredientId"); + + b.HasIndex("RecipeId"); + + b.HasIndex("UnitId"); + + b.ToTable("RecipeIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("ShoppingLists"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Symbol") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Units"); + + b.HasData( + new + { + Id = new Guid("62eb2c11-8768-46fb-9db0-c77f940bb4aa"), + Name = "liter", + Symbol = "l" + }, + new + { + Id = new Guid("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"), + Name = "gramm", + Symbol = "g" + }, + new + { + Id = new Guid("7e3d1b86-ac48-45d6-814e-d3492a86db1d"), + Name = "kilogramm", + Symbol = "kg" + }, + new + { + Id = new Guid("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"), + Name = "stücke", + Symbol = "stk" + }, + new + { + Id = new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"), + Name = "blatt", + Symbol = "blatt" + }, + new + { + Id = new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"), + Name = "messerspitze", + Symbol = "msp" + }, + new + { + Id = new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"), + Name = "stange", + Symbol = "stange" + }, + new + { + Id = new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"), + Name = "bund", + Symbol = "bund" + }, + new + { + Id = new Guid("66556e0e-eb2b-4bc3-9a56-135dd508ed09"), + Name = "zehe", + Symbol = "zehe" + }, + new + { + Id = new Guid("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"), + Name = "teelöffel", + Symbol = "TL" + }, + new + { + Id = new Guid("24a91b89-3389-4465-89e4-2f70d2ea6fd7"), + Name = "esslöffel", + Symbol = "EL" + }); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", null) + .WithMany("IngredientShoppingLists") + .HasForeignKey("IngredientId"); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", "RecipeIngredient") + .WithMany() + .HasForeignKey("RecipeIngredientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList") + .WithMany("RecipeIngredientShoppingLists") + .HasForeignKey("ShoppingListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RecipeIngredient"); + + b.Navigation("ShoppingList"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany("Instructions") + .HasForeignKey("RecipeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Recipe"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", "Instruction") + .WithMany("MediaFiles") + .HasForeignKey("InstructionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany("MediaFiles") + .HasForeignKey("RecipeId"); + + b.Navigation("Instruction"); + + b.Navigation("Recipe"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", null) + .WithMany("Recipes") + .HasForeignKey("CategoryId"); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorit") + .WithMany("Recipe") + .HasForeignKey("FavoritId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Favorit"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient") + .WithMany("RecipeIngredients") + .HasForeignKey("IngredientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany("RecipeIngredients") + .HasForeignKey("RecipeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit") + .WithMany("RecipeIngredient") + .HasForeignKey("UnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ingredient"); + + b.Navigation("Recipe"); + + b.Navigation("Unit"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b => + { + b.Navigation("Recipes"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b => + { + b.Navigation("Recipe"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b => + { + b.Navigation("IngredientShoppingLists"); + + b.Navigation("RecipeIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => + { + b.Navigation("MediaFiles"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => + { + b.Navigation("Instructions"); + + b.Navigation("MediaFiles"); + + b.Navigation("RecipeIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => + { + b.Navigation("RecipeIngredientShoppingLists"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b => + { + b.Navigation("RecipeIngredient"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Francesco.Recipes.World/Migrations/20250324124459_CreateNewTableRecipeIngredientShoppinglist.cs b/Francesco.Recipes.World/Migrations/20250324124459_CreateNewTableRecipeIngredientShoppinglist.cs new file mode 100644 index 0000000..94df359 --- /dev/null +++ b/Francesco.Recipes.World/Migrations/20250324124459_CreateNewTableRecipeIngredientShoppinglist.cs @@ -0,0 +1,131 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Francesco.Recipes.World.Migrations +{ + /// + public partial class CreateNewTableRecipeIngredientShoppinglist : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + if (migrationBuilder is null) + { + throw new ArgumentNullException(nameof(migrationBuilder)); + } + + migrationBuilder.DropTable( + name: "IngredientsShoppingLists"); + + migrationBuilder.AlterColumn( + name: "Number", + table: "Instructions", + type: "int", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.CreateTable( + name: "RecipeIngredientsShoppingLists", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + ShoppingListId = table.Column(type: "uniqueidentifier", nullable: false), + RecipeIngredientId = table.Column(type: "uniqueidentifier", nullable: false), + IngredientId = table.Column(type: "uniqueidentifier", nullable: true), + }, + constraints: table => + { + table.PrimaryKey("PK_RecipeIngredientsShoppingLists", x => x.Id); + table.ForeignKey( + name: "FK_RecipeIngredientsShoppingLists_Ingredients_IngredientId", + column: x => x.IngredientId, + principalTable: "Ingredients", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_RecipeIngredientsShoppingLists_RecipeIngredients_RecipeIngredientId", + column: x => x.RecipeIngredientId, + principalTable: "RecipeIngredients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RecipeIngredientsShoppingLists_ShoppingLists_ShoppingListId", + column: x => x.ShoppingListId, + principalTable: "ShoppingLists", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_RecipeIngredientsShoppingLists_IngredientId", + table: "RecipeIngredientsShoppingLists", + column: "IngredientId"); + + migrationBuilder.CreateIndex( + name: "IX_RecipeIngredientsShoppingLists_RecipeIngredientId", + table: "RecipeIngredientsShoppingLists", + column: "RecipeIngredientId"); + + migrationBuilder.CreateIndex( + name: "IX_RecipeIngredientsShoppingLists_ShoppingListId", + table: "RecipeIngredientsShoppingLists", + column: "ShoppingListId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + if (migrationBuilder is null) + { + throw new ArgumentNullException(nameof(migrationBuilder)); + } + + migrationBuilder.DropTable( + name: "RecipeIngredientsShoppingLists"); + + migrationBuilder.AlterColumn( + name: "Number", + table: "Instructions", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(int), + oldType: "int"); + + migrationBuilder.CreateTable( + name: "IngredientsShoppingLists", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + IngredientId = table.Column(type: "uniqueidentifier", nullable: false), + ShoppinglistId = table.Column(type: "uniqueidentifier", nullable: false), + }, + constraints: table => + { + table.PrimaryKey("PK_IngredientsShoppingLists", x => x.Id); + table.ForeignKey( + name: "FK_IngredientsShoppingLists_Ingredients_IngredientId", + column: x => x.IngredientId, + principalTable: "Ingredients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_IngredientsShoppingLists_ShoppingLists_ShoppinglistId", + column: x => x.ShoppinglistId, + principalTable: "ShoppingLists", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_IngredientsShoppingLists_IngredientId", + table: "IngredientsShoppingLists", + column: "IngredientId"); + + migrationBuilder.CreateIndex( + name: "IX_IngredientsShoppingLists_ShoppinglistId", + table: "IngredientsShoppingLists", + column: "ShoppinglistId"); + } + } +} diff --git a/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs b/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs index 130fbf5..6b432d8 100644 --- a/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs +++ b/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs @@ -1,10 +1,8 @@ // - using Francesco.Recipes.World.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; - #nullable disable namespace Francesco.Recipes.World.Migrations @@ -117,25 +115,30 @@ namespace Francesco.Recipes.World.Migrations b.ToTable("Ingredients"); }); - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.IngredientsShoppingList", b => + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("IngredientId") + b.Property("IngredientId") .HasColumnType("uniqueidentifier"); - b.Property("ShoppinglistId") + b.Property("RecipeIngredientId") + .HasColumnType("uniqueidentifier"); + + b.Property("ShoppingListId") .HasColumnType("uniqueidentifier"); b.HasKey("Id"); b.HasIndex("IngredientId"); - b.HasIndex("ShoppinglistId"); + b.HasIndex("RecipeIngredientId"); - b.ToTable("IngredientsShoppingLists"); + b.HasIndex("ShoppingListId"); + + b.ToTable("RecipeIngredientsShoppingLists"); }); modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => @@ -148,9 +151,8 @@ namespace Francesco.Recipes.World.Migrations .IsRequired() .HasColumnType("nvarchar(max)"); - b.Property("Number") - .IsRequired() - .HasColumnType("nvarchar(max)"); + b.Property("Number") + .HasColumnType("int"); b.Property("RecipeId") .HasColumnType("uniqueidentifier"); @@ -374,23 +376,27 @@ namespace Francesco.Recipes.World.Migrations }); }); - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.IngredientsShoppingList", b => + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b => { - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient") + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", null) .WithMany("IngredientShoppingLists") - .HasForeignKey("IngredientId") + .HasForeignKey("IngredientId"); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", "RecipeIngredient") + .WithMany() + .HasForeignKey("RecipeIngredientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "Shoppinglist") - .WithMany("IngredientsShoppingLists") - .HasForeignKey("ShoppinglistId") + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList") + .WithMany("RecipeIngredientShoppingLists") + .HasForeignKey("ShoppingListId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("Ingredient"); + b.Navigation("RecipeIngredient"); - b.Navigation("Shoppinglist"); + b.Navigation("ShoppingList"); }); modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => @@ -496,7 +502,7 @@ namespace Francesco.Recipes.World.Migrations modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => { - b.Navigation("IngredientsShoppingLists"); + b.Navigation("RecipeIngredientShoppingLists"); }); modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b => From b2ba51d2970dacaadebfaf5cb0810e390b3b3825 Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Sun, 30 Mar 2025 15:56:01 +0200 Subject: [PATCH 020/183] update model --- .../Models/BackendModels/Ingredient/Ingredient.cs | 2 +- .../IngredientsShoppingList.cs | 14 -------------- .../Models/BackendModels/Recipe/Recipe.cs | 3 +++ .../RecipeIngredientShoppingList.cs | 14 ++++++++++++++ .../BackendModels/Shoppinglist/ShoppingList.cs | 2 +- 5 files changed, 19 insertions(+), 16 deletions(-) delete mode 100644 Francesco.Recipes.World/Models/BackendModels/IngredientShoppingList/IngredientsShoppingList.cs create mode 100644 Francesco.Recipes.World/Models/BackendModels/RecipeIngredientShoppingList/RecipeIngredientShoppingList.cs diff --git a/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs b/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs index c4c43c3..ccc9ba1 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Ingredient/Ingredient.cs @@ -11,6 +11,6 @@ public virtual ICollection RecipeIngredients { get; set; } = new List(); - public virtual ICollection IngredientShoppingLists { get; set; } = new List(); + public virtual ICollection IngredientShoppingLists { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Models/BackendModels/IngredientShoppingList/IngredientsShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/IngredientShoppingList/IngredientsShoppingList.cs deleted file mode 100644 index 78f92d7..0000000 --- a/Francesco.Recipes.World/Models/BackendModels/IngredientShoppingList/IngredientsShoppingList.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList -{ - using Francesco.Recipes.World.Models.BackendModels.Ingredient; - using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; - - public class IngredientsShoppingList - { - public Guid Id { get; set; } - - public ShoppingList Shoppinglist { get; set; } = new (); - - public Ingredient Ingredient { get; set; } = new (); - } -} diff --git a/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs b/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs index da673d7..977b9ad 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs @@ -1,5 +1,6 @@ namespace Francesco.Recipes.World.Models.BackendModels.Recipe; +using Francesco.Recipes.World.Models.BackendModels.Category; using Francesco.Recipes.World.Models.BackendModels.Favorit; using Francesco.Recipes.World.Models.BackendModels.Instruction; using Francesco.Recipes.World.Models.BackendModels.MediaFile; @@ -34,4 +35,6 @@ public class Recipe : ITimeStampedEntity public virtual ICollection MediaFiles { get; set; } = new List(); public virtual Favorit Favorit { get; set; } = new (); + + public virtual Category Category { get; set; } = new (); } diff --git a/Francesco.Recipes.World/Models/BackendModels/RecipeIngredientShoppingList/RecipeIngredientShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/RecipeIngredientShoppingList/RecipeIngredientShoppingList.cs new file mode 100644 index 0000000..1878caf --- /dev/null +++ b/Francesco.Recipes.World/Models/BackendModels/RecipeIngredientShoppingList/RecipeIngredientShoppingList.cs @@ -0,0 +1,14 @@ +namespace Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList +{ + using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; + using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; + + public class RecipeIngredientShoppingList + { + public Guid Id { get; set; } + + public virtual ShoppingList ShoppingList { get; set; } = new (); + + public virtual RecipeIngredient RecipeIngredient { get; set; } = new (); + } +} diff --git a/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs index 71fc7e7..c8fd12f 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs @@ -10,6 +10,6 @@ public DateTime? ModifiedAt { get; set; } - public virtual ICollection IngredientsShoppingLists { get; set; } = new List(); + public virtual ICollection RecipeIngredientShoppingLists { get; set; } = new List(); } } From c48d1bb5b23410fe778e494e01b2ea63273b4ffd Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Sun, 30 Mar 2025 15:56:17 +0200 Subject: [PATCH 021/183] make views for testing data --- .../Views/Category/Details.cshtml | 23 +++++++++ .../Views/Category/Index.cshtml | 28 +++++++++++ .../Views/Home/Index.cshtml | 8 --- .../Views/Recipe/AddOrCreateIngredient.cshtml | 29 +++++++++++ .../Views/Recipe/Create.cshtml | 50 +++++++++++++++++++ .../Views/Recipe/FilterByDifficulty.cshtml | 36 +++++++++++++ .../Recipe/FilterByDifficultyViewModel.cs | 12 +++++ .../Views/Recipe/RemoveIngredient.cshtml | 23 +++++++++ 8 files changed, 201 insertions(+), 8 deletions(-) create mode 100644 Francesco.Recipes.World/Views/Category/Details.cshtml create mode 100644 Francesco.Recipes.World/Views/Category/Index.cshtml delete mode 100644 Francesco.Recipes.World/Views/Home/Index.cshtml create mode 100644 Francesco.Recipes.World/Views/Recipe/AddOrCreateIngredient.cshtml create mode 100644 Francesco.Recipes.World/Views/Recipe/Create.cshtml create mode 100644 Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml create mode 100644 Francesco.Recipes.World/Views/Recipe/FilterByDifficultyViewModel.cs create mode 100644 Francesco.Recipes.World/Views/Recipe/RemoveIngredient.cshtml diff --git a/Francesco.Recipes.World/Views/Category/Details.cshtml b/Francesco.Recipes.World/Views/Category/Details.cshtml new file mode 100644 index 0000000..ba13edc --- /dev/null +++ b/Francesco.Recipes.World/Views/Category/Details.cshtml @@ -0,0 +1,23 @@ +@model Francesco.Recipes.World.Models.BackendModels.Category.Category + +@{ + ViewData["Title"] = "Category Details"; +} + +

Category Details

+ +
+

Category

+
+
+
+ Name +
+
+ @Model.Name +
+
+
+ diff --git a/Francesco.Recipes.World/Views/Category/Index.cshtml b/Francesco.Recipes.World/Views/Category/Index.cshtml new file mode 100644 index 0000000..9aa30c6 --- /dev/null +++ b/Francesco.Recipes.World/Views/Category/Index.cshtml @@ -0,0 +1,28 @@ +@model IEnumerable + +@{ + ViewData["Title"] = "Categories"; +} + +

Categories

+ + + + + + + + + + @foreach (var category in Model) + { + + + + + } + +
NameActions
@category.Name + Details + Recipes +
diff --git a/Francesco.Recipes.World/Views/Home/Index.cshtml b/Francesco.Recipes.World/Views/Home/Index.cshtml deleted file mode 100644 index bcfd79a..0000000 --- a/Francesco.Recipes.World/Views/Home/Index.cshtml +++ /dev/null @@ -1,8 +0,0 @@ -@{ - ViewData["Title"] = "Home Page"; -} - -
-

Welcome

-

Learn about building Web apps with ASP.NET Core.

-
diff --git a/Francesco.Recipes.World/Views/Recipe/AddOrCreateIngredient.cshtml b/Francesco.Recipes.World/Views/Recipe/AddOrCreateIngredient.cshtml new file mode 100644 index 0000000..c72fdcf --- /dev/null +++ b/Francesco.Recipes.World/Views/Recipe/AddOrCreateIngredient.cshtml @@ -0,0 +1,29 @@ +@{ + ViewData["Title"] = "Add or Create Ingredient to Recipe"; +} + +

@ViewData["Title"]

+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +@section Scripts { + +} diff --git a/Francesco.Recipes.World/Views/Recipe/Create.cshtml b/Francesco.Recipes.World/Views/Recipe/Create.cshtml new file mode 100644 index 0000000..8b1e201 --- /dev/null +++ b/Francesco.Recipes.World/Views/Recipe/Create.cshtml @@ -0,0 +1,50 @@ +@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe +@using Francesco.Recipes.World.Models.BackendModels.Recipe +@{ + ViewData["Title"] = "Create Recipe"; +} + +

Create Recipe

+ +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ +
+
+ +@section Scripts { + @{ + await Html.RenderPartialAsync("_ValidationScriptsPartial"); + } +} + diff --git a/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml b/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml new file mode 100644 index 0000000..6b9ff3b --- /dev/null +++ b/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml @@ -0,0 +1,36 @@ + @model Francesco.Recipes.World.Views.Recipe.FilterByDifficultyViewModel + @using Francesco.Recipes.World.Models.BackendModels.Recipe + +@{ + ViewData["Title"] = "Filter Recipes by Difficulty"; +} + +

Filter Recipes by Difficulty

+ +
+
+ + + +
+ +
+ +@if (Model.Recipes != null && Model.Recipes.Any()) +{ +

Filtered Recipes

+
    + @foreach (var recipe in Model.Recipes) + { +
  • @recipe.Name - @recipe.Difficulty
  • + } +
+} +else +{ +

No recipes found for the selected difficulty.

+} + +@section Scripts { + @await Html.PartialAsync("_ValidationScriptsPartial") + } diff --git a/Francesco.Recipes.World/Views/Recipe/FilterByDifficultyViewModel.cs b/Francesco.Recipes.World/Views/Recipe/FilterByDifficultyViewModel.cs new file mode 100644 index 0000000..94f71f6 --- /dev/null +++ b/Francesco.Recipes.World/Views/Recipe/FilterByDifficultyViewModel.cs @@ -0,0 +1,12 @@ +namespace Francesco.Recipes.World.Views.Recipe +{ + using System.Collections.Generic; + using Francesco.Recipes.World.Models.BackendModels.Recipe; + + public class FilterByDifficultyViewModel + { + public Difficulty? SelectedDifficulty { get; set; } + + public List Recipes { get; set; } = new List(); + } +} diff --git a/Francesco.Recipes.World/Views/Recipe/RemoveIngredient.cshtml b/Francesco.Recipes.World/Views/Recipe/RemoveIngredient.cshtml new file mode 100644 index 0000000..ca32396 --- /dev/null +++ b/Francesco.Recipes.World/Views/Recipe/RemoveIngredient.cshtml @@ -0,0 +1,23 @@ +@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe + +@{ + ViewData["Title"] = "Remove Ingredient"; +} + +

Remove Ingredient

+ +

Are you sure you want to remove the ingredient '@ViewBag.IngredientName' from this recipe?

+ +
+ + + +
+ + Cancel +
+
+ +@section Scripts { + @await Html.PartialAsync("_ValidationScriptsPartial") +} From 2acb4b4117254126be303af44abf22a796c18c94 Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Sun, 30 Mar 2025 15:56:39 +0200 Subject: [PATCH 022/183] change some logic to the repositories --- .../Category/CategoryRepository.cs | 45 +++++ .../Category/ICategoryRepository.cs | 17 ++ .../Ingredient/IIngredientRepository.cs | 21 +++ .../Ingredient/IngredientRepository.cs | 109 +++++++++++ .../Instruction/IInstructionRepository.cs | 9 + .../Instruction/InstructionRepository.cs | 21 +++ .../MediaFile/IMediaFileRepository.cs | 13 ++ .../MediaFile/MediaFileRepository.cs | 101 ++++++++++ .../Repositories/Recipe/IRecipeRepository.cs | 23 +++ .../Repositories/Recipe/RecipeRepository.cs | 175 ++++++++++++++++++ .../ShoppingList/IShoppingListRepository.cs | 19 ++ .../ShoppingList/ShoppingListRepository.cs | 106 +++++++++++ .../Repositories/Unit/IUnitRepository.cs | 13 ++ .../Repositories/Unit/UnitRepository.cs | 43 +++++ 14 files changed, 715 insertions(+) create mode 100644 Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs create mode 100644 Francesco.Recipes.World/Repositories/Category/ICategoryRepository.cs create mode 100644 Francesco.Recipes.World/Repositories/Ingredient/IIngredientRepository.cs create mode 100644 Francesco.Recipes.World/Repositories/Ingredient/IngredientRepository.cs create mode 100644 Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs create mode 100644 Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs create mode 100644 Francesco.Recipes.World/Repositories/MediaFile/IMediaFileRepository.cs create mode 100644 Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs create mode 100644 Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs create mode 100644 Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs create mode 100644 Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs create mode 100644 Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs create mode 100644 Francesco.Recipes.World/Repositories/Unit/IUnitRepository.cs create mode 100644 Francesco.Recipes.World/Repositories/Unit/UnitRepository.cs diff --git a/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs b/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs new file mode 100644 index 0000000..6e372f8 --- /dev/null +++ b/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs @@ -0,0 +1,45 @@ +namespace Francesco.Recipes.World.Repositories.Category +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Models.BackendModels.Category; + using Francesco.Recipes.World.Models.BackendModels.Recipe; + using Microsoft.EntityFrameworkCore; + + public class CategoryRepository : ICategoryRepository + { + private readonly FrancescosRecipesWorldDbContext _context; + + public CategoryRepository(FrancescosRecipesWorldDbContext context) + { + _context = context; + } + + public async Task GetCategoryByIdAsync(Guid categoryId) + { + var category = await _context.Categories.FindAsync(categoryId); + return category ?? throw new InvalidDataException($"Category {categoryId} not found."); + } + + public async Task> GetAllCategoriesAsync() + { + return await _context.Categories.ToListAsync(); + } + + public async Task> GetRecipesByCategoryAsync(Guid categoryId) + { + var category = await _context.Categories + .Include(c => c.Recipes) + .FirstOrDefaultAsync(c => c.Id == categoryId); + + if (category == null) + { + throw new InvalidDataException($"Category {categoryId} not found."); + } + + return category.Recipes; + } + } +} diff --git a/Francesco.Recipes.World/Repositories/Category/ICategoryRepository.cs b/Francesco.Recipes.World/Repositories/Category/ICategoryRepository.cs new file mode 100644 index 0000000..04491aa --- /dev/null +++ b/Francesco.Recipes.World/Repositories/Category/ICategoryRepository.cs @@ -0,0 +1,17 @@ +namespace Francesco.Recipes.World.Repositories.Category +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Francesco.Recipes.World.Models.BackendModels.Category; + using Francesco.Recipes.World.Models.BackendModels.Recipe; + + public interface ICategoryRepository + { + Task GetCategoryByIdAsync(Guid categoryId); + + Task> GetAllCategoriesAsync(); + + Task> GetRecipesByCategoryAsync(Guid categoryId); + } +} diff --git a/Francesco.Recipes.World/Repositories/Ingredient/IIngredientRepository.cs b/Francesco.Recipes.World/Repositories/Ingredient/IIngredientRepository.cs new file mode 100644 index 0000000..013e2d1 --- /dev/null +++ b/Francesco.Recipes.World/Repositories/Ingredient/IIngredientRepository.cs @@ -0,0 +1,21 @@ +namespace Francesco.Recipes.World.Repositories.Ingredient +{ + using Francesco.Recipes.World.Models.BackendModels.Ingredient; + using Francesco.Recipes.World.Models.BackendModels.Recipe; + using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; + + public interface IIngredientRepository + { + Task CreateIngredientToRecipeAsync(Recipe recipe, string ingredientName); + + Task UpdateIngredientAsync(Ingredient ingredient); + + Task> GetIngredientsByRecipeIdAsync(Guid recipeId); + + Task> GetIngredientsByNameAsync(string name); + + Task RemoveIngredientFromRecipeAsync(Recipe recipe, Guid ingredientId); + + Task GetIngredientByIdAsync(Guid ingredientId); + } +} diff --git a/Francesco.Recipes.World/Repositories/Ingredient/IngredientRepository.cs b/Francesco.Recipes.World/Repositories/Ingredient/IngredientRepository.cs new file mode 100644 index 0000000..f573a81 --- /dev/null +++ b/Francesco.Recipes.World/Repositories/Ingredient/IngredientRepository.cs @@ -0,0 +1,109 @@ +namespace Francesco.Recipes.World.Repositories.Ingredient +{ + using Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Models.BackendModels.Ingredient; + using Francesco.Recipes.World.Models.BackendModels.Recipe; + using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; + using Microsoft.EntityFrameworkCore; + + public class IngredientRepository : IIngredientRepository + { + private readonly FrancescosRecipesWorldDbContext _context; + + public IngredientRepository( + FrancescosRecipesWorldDbContext context) + { + _context = context; + } + + public async Task CreateIngredientToRecipeAsync(Recipe recipe, string ingredientName) + { + if (recipe is null) + { + throw new ArgumentException("Recipe not found", nameof(recipe)); + } + + var newIngredient = new Ingredient + { + Id = Guid.NewGuid(), + Name = ingredientName, + }; + + var recipeIngredient = new RecipeIngredient + { + Id = Guid.NewGuid(), + Recipe = recipe, + Ingredient = newIngredient, + Quantity = 1, + }; + + _context.Ingredients.Add(newIngredient); + _context.RecipeIngredients.Add(recipeIngredient); + await _context.SaveChangesAsync(); + + return newIngredient; + } + + public async Task RemoveIngredientFromRecipeAsync(Recipe recipe, Guid ingredientId) + { + if (recipe == null) + { + throw new ArgumentNullException(nameof(recipe)); + } + + var ingredientToRemove = recipe.RecipeIngredients.FirstOrDefault(i => i.Id == ingredientId); + + if (ingredientToRemove != null) + { + recipe.RecipeIngredients.Remove(ingredientToRemove); + await _context.SaveChangesAsync(); + } + } + + public async Task> GetIngredientsByNameAsync(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + return await _context.Ingredients.ToListAsync(); + } + + return await _context.Ingredients + .Where(i => i.Name.ToLower().Contains(name.ToLower())) + .ToListAsync(); + } + + public async Task> GetIngredientsByRecipeIdAsync(Guid recipeId) + { + return await _context.RecipeIngredients + .Include(ri => ri.Ingredient) + .Include(ri => ri.Unit) + .Where(ri => ri.Recipe.Id == recipeId) + .ToListAsync(); + } + + public async Task UpdateIngredientAsync(Ingredient ingredient) + { + if (ingredient == null) + { + throw new ArgumentNullException(nameof(ingredient)); + } + + var existingIngredient = await _context.Ingredients.FindAsync(ingredient.Id); + if (existingIngredient == null) + { + throw new InvalidOperationException($"Ingredient with ID {ingredient.Id} not found."); + } + + existingIngredient.Name = ingredient.Name; + + _context.Ingredients.Update(existingIngredient); + await _context.SaveChangesAsync(); + } + + public async Task GetIngredientByIdAsync(Guid ingredientId) + { + var ingredient = await _context.Ingredients.FindAsync(ingredientId); + return ingredient ?? throw new InvalidDataException($"Address {ingredientId} not found."); + } + } +} diff --git a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs new file mode 100644 index 0000000..7969570 --- /dev/null +++ b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs @@ -0,0 +1,9 @@ +namespace Francesco.Recipes.World.Repositories.Instruction +{ + using Francesco.Recipes.World.Models.BackendModels.Instruction; + + public interface IInstructionRepository + { + Task GetInstructionAsync(Guid instructionId); + } +} diff --git a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs new file mode 100644 index 0000000..b772813 --- /dev/null +++ b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs @@ -0,0 +1,21 @@ +namespace Francesco.Recipes.World.Repositories.Instruction +{ + using Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Models.BackendModels.Instruction; + + public class InstructionRepository : IInstructionRepository + { + private readonly FrancescosRecipesWorldDbContext _context; + + public InstructionRepository(FrancescosRecipesWorldDbContext context) + { + _context = context; + } + + public async Task GetInstructionAsync(Guid instructionId) + { + var instruction = await _context.Instructions.FindAsync(instructionId); + return instruction ?? throw new InvalidDataException($"Instruction {instructionId} not found."); + } + } +} diff --git a/Francesco.Recipes.World/Repositories/MediaFile/IMediaFileRepository.cs b/Francesco.Recipes.World/Repositories/MediaFile/IMediaFileRepository.cs new file mode 100644 index 0000000..0fc5d91 --- /dev/null +++ b/Francesco.Recipes.World/Repositories/MediaFile/IMediaFileRepository.cs @@ -0,0 +1,13 @@ +namespace Francesco.Recipes.World.Repositories.MediaFile +{ + public interface IMediaFileRepository + { + Task ReplaceInstructionImageAsync(Guid instructionId, IFormFile? newPhoto); + + Task UploadInstructionImageAsync(Guid instructionId, IFormFile? photo); + + Task ReplaceRecipeImageAsync(Guid recipeId, IFormFile? newPhoto); + + Task UploadRecipeImageAsync(Guid recipeId, IFormFile? photo); + } +} diff --git a/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs b/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs new file mode 100644 index 0000000..742e4c4 --- /dev/null +++ b/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs @@ -0,0 +1,101 @@ +namespace Francesco.Recipes.World.Repositories.MediaFile +{ + using Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Models.BackendModels.MediaFile; + using Francesco.Recipes.World.Repositories.Instruction; + using Francesco.Recipes.World.Repositories.Recipe; + + public class MediaFileRepository : IMediaFileRepository + { + private readonly IInstructionRepository _instructionRepository; + private readonly FrancescosRecipesWorldDbContext _context; + private readonly IRecipeRepository _recipeRepository; + + public MediaFileRepository(IInstructionRepository instructionRepository, FrancescosRecipesWorldDbContext context, IRecipeRepository recipeRepository) + { + _instructionRepository = instructionRepository; + _context = context; + _recipeRepository = recipeRepository; + } + + public async Task ReplaceInstructionImageAsync(Guid instructionId, IFormFile? newPhoto) + { + if (newPhoto is null) + { + throw new ArgumentNullException(nameof(newPhoto)); + } + + var instruction = await _instructionRepository.GetInstructionAsync(instructionId); + _context.RemoveRange(instruction.MediaFiles); + await _context.SaveChangesAsync(); + + await UploadInstructionImageAsync(instructionId, newPhoto); + } + + public async Task ReplaceRecipeImageAsync(Guid recipeId, IFormFile? newPhoto) + { + if (newPhoto is null) + { + throw new ArgumentNullException(nameof(newPhoto)); + } + + var recipe = await _recipeRepository.GetRecipeAsync(recipeId); + _context.RemoveRange(recipe.MediaFiles); + await _context.SaveChangesAsync(); + + await UploadInstructionImageAsync(recipeId, newPhoto); + } + + public async Task UploadInstructionImageAsync(Guid instructionId, IFormFile? photo) + { + if (photo is null) + { + throw new ArgumentNullException(nameof(photo)); + } + + var instruction = await _instructionRepository.GetInstructionAsync(instructionId); + + using (var memoryStream = new MemoryStream()) + { + await photo.CopyToAsync(memoryStream); + + var instructionImage = new MediaFile + { + FileName = photo.FileName, + MimeType = photo.ContentType, + Data = memoryStream.ToArray(), + Instruction = instruction, + }; + + _context.Add(instructionImage); + await _context.SaveChangesAsync(); + } + } + + public async Task UploadRecipeImageAsync(Guid recipeId, IFormFile? photo) + { + if (photo is null) + { + throw new ArgumentNullException(nameof(photo)); + } + + var recipe = await _recipeRepository.GetRecipeAsync(recipeId); + + using (var memoryStream = new MemoryStream()) + { + await photo.CopyToAsync(memoryStream); + + var recipeImage = new MediaFile + { + FileName = photo.FileName, + MimeType = photo.ContentType, + Data = memoryStream.ToArray(), + Recipe = recipe, + }; + + _context.Add(recipeImage); + await _context.SaveChangesAsync(); + } + } + } +} diff --git a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs new file mode 100644 index 0000000..43a652d --- /dev/null +++ b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs @@ -0,0 +1,23 @@ +namespace Francesco.Recipes.World.Repositories.Recipe +{ + using Francesco.Recipes.World.Models.BackendModels.Category; + using Francesco.Recipes.World.Models.BackendModels.Recipe; + using Francesco.Recipes.World.Models.BackendModels.Unit; + + public interface IRecipeRepository + { + Task GetRecipeAsync(Guid recipeId); + + Task AddOrCreateIngredientToRecipeAsync(Guid recipeId, string ingredientName, int quantity, Guid unitId); + + Task RemoveIngredientFromRecipeAsync(Guid recipeId, Guid ingredientId); + + Task> GetRecipesByNameOrIngredientAsync(string name, string ingredient); + + Task> GetRecipesByDifficultyAsync(Difficulty difficulty); + + Task AddUnitToRecipeAsync(string name, string symbol); + + Task CreateRecipeForCategoryAsync(Category category, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime); + } +} diff --git a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs new file mode 100644 index 0000000..300c467 --- /dev/null +++ b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs @@ -0,0 +1,175 @@ +namespace Francesco.Recipes.World.Repositories.Recipe +{ + using Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Models.BackendModels.Category; + using Francesco.Recipes.World.Models.BackendModels.Ingredient; + using Francesco.Recipes.World.Models.BackendModels.Recipe; + using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; + using Francesco.Recipes.World.Models.BackendModels.Unit; + using Francesco.Recipes.World.Repositories.Ingredient; + using Francesco.Recipes.World.Repositories.Unit; + using Microsoft.EntityFrameworkCore; + + public class RecipeRepository : IRecipeRepository + { + private readonly FrancescosRecipesWorldDbContext _context; + private readonly IIngredientRepository _ingredientRepository; + private readonly IUnitRepository _unitRepository; + + public RecipeRepository( + FrancescosRecipesWorldDbContext context, IIngredientRepository ingredientRepository, IUnitRepository unitRepository) + { + _context = context; + _ingredientRepository = ingredientRepository; + _unitRepository = unitRepository; + } + + public async Task GetRecipeAsync(Guid recipeId) + { + var recipe = await _context.Recipes.FindAsync(recipeId); + return recipe ?? throw new InvalidDataException($"Address {recipeId} not found."); + } + + public async Task AddOrCreateIngredientToRecipeAsync(Guid recipeId, string ingredientName, int quantity, Guid unitId) + { + var recipe = await GetRecipeAsync(recipeId); + var unit = await _unitRepository.GetUnitByIdAsync(unitId); + + if (quantity <= 0) + { + throw new ArgumentOutOfRangeException(nameof(quantity), "Die Menge muss größer als 0 sein."); + } + + var ingredients = await _ingredientRepository.GetIngredientsByNameAsync(ingredientName); + Ingredient ingredient; + + if (ingredients == null || !ingredients.Any()) + { + ingredient = new Ingredient + { + Id = Guid.NewGuid(), + Name = ingredientName, + }; + _context.Ingredients.Add(ingredient); + await _context.SaveChangesAsync(); + } + else + { + ingredient = ingredients.First(); + } + + var existingEntry = await _context.RecipeIngredients + .FirstOrDefaultAsync(ri => ri.Recipe.Id == recipeId && ri.Ingredient.Id == ingredient.Id); + if (existingEntry != null) + { + throw new InvalidOperationException("Das Rezept enthält diese Zutat bereits."); + } + + var recipeIngredient = new RecipeIngredient + { + Recipe = recipe, + Ingredient = ingredient, + Unit = unit, + Quantity = quantity, + }; + _context.Add(recipeIngredient); + await _context.SaveChangesAsync(); + } + + public async Task CreateRecipeForCategoryAsync( + Category category, + string name, + string description, + Difficulty difficulty, + int servings, + TimeSpan preparationTime, + TimeSpan cookingTime) + { + if (category == null) + { + throw new ArgumentNullException(nameof(category), "Category cannot be null."); + } + + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentNullException(nameof(name), "Name cannot be empty."); + } + + if (servings <= 0) + { + throw new ArgumentOutOfRangeException(nameof(servings), "Servings must be greater than 0."); + } + + var recipe = new Recipe + { + Id = Guid.NewGuid(), + Name = name, + Description = description, + Difficulty = difficulty, + Servings = servings, + PreparationTime = preparationTime, + CookingTime = cookingTime, + CreatedAt = DateTime.UtcNow, + Category = category, + }; + + _context.Recipes.Add(recipe); + await _context.SaveChangesAsync(); + + return recipe; + } + + public async Task RemoveIngredientFromRecipeAsync(Guid recipeId, Guid ingredientId) + { + var recipe = await GetRecipeAsync(recipeId); + var recipeIngredient = await _context.RecipeIngredients + .FirstOrDefaultAsync(ri => ri.Recipe.Id == recipeId && ri.Ingredient.Id == ingredientId); + + if (recipeIngredient == null) + { + throw new ArgumentException("Diese Zutat ist nicht mit dem Rezept verknüpft."); + } + + _context.RecipeIngredients.Remove(recipeIngredient); + await _context.SaveChangesAsync(); + } + + public async Task> GetRecipesByNameOrIngredientAsync(string name, string ingredient) + { + var query = _context.Recipes + .Include(r => r.RecipeIngredients) + .ThenInclude(ri => ri.Ingredient) + .AsQueryable(); + + if (!string.IsNullOrWhiteSpace(name)) + { + query = query.Where(r => r.Name.Contains(name, StringComparison.OrdinalIgnoreCase)); + } + + if (!string.IsNullOrWhiteSpace(ingredient)) + { + var ingredientMatches = await _ingredientRepository.GetIngredientsByNameAsync(ingredient); + var ingredientIds = ingredientMatches.Select(i => i.Id).ToList(); + + if (ingredientIds.Any()) + { + query = query.Where(r => r.RecipeIngredients.Any(ri => ingredientIds.Contains(ri.Ingredient.Id))); + } + } + + return await query.ToListAsync(); + } + + public async Task> GetRecipesByDifficultyAsync(Difficulty difficulty) + { + return await _context.Recipes + .Where(r => r.Difficulty == difficulty) + .ToListAsync(); + } + + public async Task AddUnitToRecipeAsync(string name, string symbol) + { + return await _unitRepository.AddUnitAsync(name, symbol); + } + } +} diff --git a/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs b/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs new file mode 100644 index 0000000..2205dbc --- /dev/null +++ b/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs @@ -0,0 +1,19 @@ + +namespace Francesco.Recipes.World.Repositories.ShoppingList +{ + using Francesco.Recipes.World.Models.BackendModels.Recipe; + using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; + + public interface IShoppingListRepository + { + Task AddIngredientToShoppingListAsync(Guid recipeIngredientId, Guid shoppingListId); + + Task RemoveRecipeIngredientFromShoppingListAsync(Guid recipeIngredientId); + + Task RemoveRecipeFromShoppingListIfEmptyAsync(Guid recipeId, Guid shoppingListId); + + Task> GetShoppingListsByIngredientOfRecipeAsync(Guid ingredientId, Guid recipeId); + + Task GetRecipeByNameAndImageAsync(string recipeName, string imageFileName); + } +} diff --git a/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs b/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs new file mode 100644 index 0000000..5609143 --- /dev/null +++ b/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs @@ -0,0 +1,106 @@ +namespace Francesco.Recipes.World.Repositories.ShoppingList +{ + using Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; + using Francesco.Recipes.World.Models.BackendModels.Recipe; + using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; + using Microsoft.EntityFrameworkCore; + + public class ShoppingListRepository : IShoppingListRepository + { + private readonly FrancescosRecipesWorldDbContext _context; + + public ShoppingListRepository(FrancescosRecipesWorldDbContext context) + { + _context = context; + } + + public async Task AddIngredientToShoppingListAsync(Guid recipeIngredientId, Guid shoppingListId) + { + var existingEntry = await _context.RecipeIngredientsShoppingLists + .FirstOrDefaultAsync(risl => risl.RecipeIngredient.Id == recipeIngredientId && risl.ShoppingList.Id == shoppingListId); + + if (existingEntry != null) + { + return; + } + + var recipeIngredient = await _context.RecipeIngredients + .FirstOrDefaultAsync(ri => ri.Id == recipeIngredientId); + + if (recipeIngredient == null) + { + throw new InvalidOperationException($"Recipe ingredient with ID {recipeIngredientId} not found."); + } + + var shoppingList = await _context.ShoppingLists + .FirstOrDefaultAsync(sl => sl.Id == shoppingListId); + + if (shoppingList == null) + { + throw new InvalidOperationException($"Shopping list with ID {shoppingListId} not found."); + } + + var newEntry = new RecipeIngredientShoppingList + { + RecipeIngredient = recipeIngredient, + ShoppingList = shoppingList, + }; + + _context.RecipeIngredientsShoppingLists.Add(newEntry); + await _context.SaveChangesAsync(); + } + + public async Task RemoveRecipeIngredientFromShoppingListAsync(Guid recipeIngredientId) + { + var entry = await _context.RecipeIngredientsShoppingLists + .FirstOrDefaultAsync(risl => risl.RecipeIngredient.Id == recipeIngredientId); + + if (entry != null) + { + _context.RecipeIngredientsShoppingLists.Remove(entry); + await _context.SaveChangesAsync(); + } + } + + public async Task RemoveRecipeFromShoppingListIfEmptyAsync(Guid recipeId, Guid shoppingListId) + { + var hasIngredients = await _context.RecipeIngredientsShoppingLists + .AnyAsync(risl => risl.RecipeIngredient.Recipe.Id == recipeId && risl.ShoppingList.Id == shoppingListId); + + if (!hasIngredients) + { + var shoppingList = await _context.ShoppingLists + .Include(sl => sl.RecipeIngredientShoppingLists) + .FirstOrDefaultAsync(sl => sl.Id == shoppingListId); + + if (shoppingList != null) + { + var recipeToRemove = shoppingList.RecipeIngredientShoppingLists + .FirstOrDefault(risl => risl.RecipeIngredient.Recipe.Id == recipeId); + + if (recipeToRemove != null) + { + _context.RecipeIngredientsShoppingLists.Remove(recipeToRemove); + await _context.SaveChangesAsync(); + } + } + } + } + + public async Task> GetShoppingListsByIngredientOfRecipeAsync(Guid ingredientId, Guid recipeId) + { + return await _context.ShoppingLists + .Where(sl => sl.RecipeIngredientShoppingLists.Any(risl => risl.RecipeIngredient.Ingredient.Id == ingredientId && risl.RecipeIngredient.Recipe.Id == recipeId)) + .ToListAsync(); + } + + public async Task GetRecipeByNameAndImageAsync(string recipeName, string imageFileName) + { + return await _context.Recipes + .Where(r => r.Name == recipeName && r.MediaFiles.Any(mf => mf.FileName == imageFileName)) + .Include(r => r.MediaFiles.Where(mf => mf.FileName == imageFileName)) + .FirstOrDefaultAsync(); + } + } +} diff --git a/Francesco.Recipes.World/Repositories/Unit/IUnitRepository.cs b/Francesco.Recipes.World/Repositories/Unit/IUnitRepository.cs new file mode 100644 index 0000000..a8244cd --- /dev/null +++ b/Francesco.Recipes.World/Repositories/Unit/IUnitRepository.cs @@ -0,0 +1,13 @@ +namespace Francesco.Recipes.World.Repositories.Unit +{ + using Francesco.Recipes.World.Models.BackendModels.Unit; + + public interface IUnitRepository + { + Task GetUnitByIdAsync(Guid unitId); + + Task AddUnitAsync(string name, string symbol); + + Task> GetAllUnitsAsync(); + } +} diff --git a/Francesco.Recipes.World/Repositories/Unit/UnitRepository.cs b/Francesco.Recipes.World/Repositories/Unit/UnitRepository.cs new file mode 100644 index 0000000..04297cd --- /dev/null +++ b/Francesco.Recipes.World/Repositories/Unit/UnitRepository.cs @@ -0,0 +1,43 @@ +namespace Francesco.Recipes.World.Repositories.Unit +{ + using Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Models.BackendModels.Unit; + using Microsoft.EntityFrameworkCore; + + public class UnitRepository : IUnitRepository + { + private readonly FrancescosRecipesWorldDbContext _context; + + public UnitRepository( + FrancescosRecipesWorldDbContext context) + { + _context = context; + } + + public async Task GetUnitByIdAsync(Guid unitId) + { + var unit = await _context.Units.FindAsync(unitId); + return unit ?? throw new InvalidDataException($"Address {unitId} not found."); + } + + public async Task AddUnitAsync(string name, string symbol) + { + var unit = new Unit + { + Id = Guid.NewGuid(), + Name = name, + Symbol = symbol, + }; + + _context.Units.Add(unit); + await _context.SaveChangesAsync(); + + return unit; + } + + public async Task> GetAllUnitsAsync() + { + return await _context.Units.ToListAsync(); + } + } +} From 1fcf1a1c26845c25e053147f388c6ed3dd8b3028 Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Tue, 1 Apr 2025 17:02:47 +0200 Subject: [PATCH 023/183] FilterByDifficulty testing --- .../Controller/Recipe/RecipeController.cs | 16 ++++------------ .../Models/BackendModels/Recipe/Difficulty.cs | 10 +++++----- .../Repositories/Recipe/IRecipeRepository.cs | 2 +- .../Repositories/Recipe/RecipeRepository.cs | 2 +- .../Views/Recipe/FilterByDifficulty.cshtml | 16 +++++++++++----- 5 files changed, 22 insertions(+), 24 deletions(-) diff --git a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs index 5b21475..9b4cd9e 100644 --- a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs +++ b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs @@ -5,7 +5,6 @@ using Francesco.Recipes.World.Repositories.Ingredient; using Francesco.Recipes.World.Repositories.Recipe; using Francesco.Recipes.World.Repositories.Unit; - using Francesco.Recipes.World.Views.Recipe; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; @@ -25,6 +24,8 @@ _ingredientRepository = ingredientRepository; } + public IReadOnlyCollection Recipes { get; set; } + // GET: /Recipe/AddOrCreateIngredient [HttpGet("{recipeId}/AddOrCreateIngredient")] public async Task AddOrCreateIngredient() @@ -140,17 +141,8 @@ [HttpGet("FilterByDifficulty")] public async Task FilterByDifficulty(Difficulty? difficulty) { - var viewModel = new FilterByDifficultyViewModel - { - SelectedDifficulty = difficulty, - }; - - if (difficulty.HasValue) - { - viewModel.Recipes = (await _recipeRepository.GetRecipesByDifficultyAsync(difficulty.Value)).ToList(); - } - - return View(viewModel); + Recipes = await _recipeRepository.GetRecipesByDifficultyAsync(difficulty); + return View(Recipes); } } } diff --git a/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs b/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs index 4456670..ee81fd2 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs @@ -2,14 +2,14 @@ { public enum Difficulty { - VeryEasy = 1, + VeryEasy = 0, - Easy = 2, + Easy = 1, - Medium = 3, + Medium = 2, - Hard = 4, + Hard = 3, - Expert = 5, + Expert = 4, } } diff --git a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs index 43a652d..bc6ae01 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs @@ -14,7 +14,7 @@ Task> GetRecipesByNameOrIngredientAsync(string name, string ingredient); - Task> GetRecipesByDifficultyAsync(Difficulty difficulty); + Task> GetRecipesByDifficultyAsync(Difficulty? difficulty); Task AddUnitToRecipeAsync(string name, string symbol); diff --git a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs index 300c467..7b2c780 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs @@ -160,7 +160,7 @@ return await query.ToListAsync(); } - public async Task> GetRecipesByDifficultyAsync(Difficulty difficulty) + public async Task> GetRecipesByDifficultyAsync(Difficulty? difficulty) { return await _context.Recipes .Where(r => r.Difficulty == difficulty) diff --git a/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml b/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml index 6b9ff3b..01a77cb 100644 --- a/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml +++ b/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml @@ -7,13 +7,14 @@

Filter Recipes by Difficulty

-
+
- +
-
@if (Model.Recipes != null && Model.Recipes.Any()) @@ -22,7 +23,7 @@
    @foreach (var recipe in Model.Recipes) { -
  • @recipe.Name - @recipe.Difficulty
  • +
  • @recipe.Name - @recipe.Difficulty.
  • }
} @@ -32,5 +33,10 @@ else } @section Scripts { + @await Html.PartialAsync("_ValidationScriptsPartial") - } +} From c7543f8a49c5677893488b03718bb6174aa61707 Mon Sep 17 00:00:00 2001 From: Francesco D'Amico Date: Wed, 2 Apr 2025 08:46:37 +0200 Subject: [PATCH 024/183] Edit Program and csproj file --- .../Francesco.Recipes.World.csproj | 5 +---- Francesco.Recipes.World/Program.cs | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Francesco.Recipes.World/Francesco.Recipes.World.csproj b/Francesco.Recipes.World/Francesco.Recipes.World.csproj index 728f575..549a91d 100644 --- a/Francesco.Recipes.World/Francesco.Recipes.World.csproj +++ b/Francesco.Recipes.World/Francesco.Recipes.World.csproj @@ -29,6 +29,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive
+ all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -41,10 +42,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive
- - - - diff --git a/Francesco.Recipes.World/Program.cs b/Francesco.Recipes.World/Program.cs index 6d773ec..719acda 100644 --- a/Francesco.Recipes.World/Program.cs +++ b/Francesco.Recipes.World/Program.cs @@ -1,8 +1,11 @@ using Francesco.Recipes.World.Data; - -using Francesco.Recipes.World.Repositories; - -using FrancescoRecipesWorld.Repositories; +using Francesco.Recipes.World.Repositories.Category; +using Francesco.Recipes.World.Repositories.Ingredient; +using Francesco.Recipes.World.Repositories.Instruction; +using Francesco.Recipes.World.Repositories.MediaFile; +using Francesco.Recipes.World.Repositories.Recipe; +using Francesco.Recipes.World.Repositories.ShoppingList; +using Francesco.Recipes.World.Repositories.Unit; using Microsoft.AspNetCore.Identity; @@ -34,6 +37,12 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); + +builder.Services.AddScoped(); + +builder.Services.AddScoped(); + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -45,6 +54,8 @@ if (!app.Environment.IsDevelopment()) app.UseHsts(); } +Console.WriteLine("Standard Numeric Format Specifiers"); + app.UseHttpsRedirection(); app.UseStaticFiles(); From e438e8a114b06963f95e5eefa64dcbc423ec3717 Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 8 Apr 2025 14:25:21 +0200 Subject: [PATCH 025/183] implement FavoritRepository for managing favorite recipes --- .../Repositories/Favorit/FavoritRepository.cs | 49 +++++++++++++++++++ .../Favorit/IFavoritRepository.cs | 15 ++++++ 2 files changed, 64 insertions(+) create mode 100644 Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs create mode 100644 Francesco.Recipes.World/Repositories/Favorit/IFavoritRepository.cs diff --git a/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs b/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs new file mode 100644 index 0000000..b4eddb5 --- /dev/null +++ b/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs @@ -0,0 +1,49 @@ +namespace Francesco.Recipes.World.Repositories.Favorit +{ + using Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Models.BackendModels.Recipe; + using Microsoft.EntityFrameworkCore; + + public class FavoritRepository : IFavoritRepository + { + private readonly FrancescosRecipesWorldDbContext _context; + + public FavoritRepository(FrancescosRecipesWorldDbContext context) + { + _context = context; + } + + public async Task> GetFavoriteRecipesAsync() + { + return await _context.Recipes + .Where(r => r.IsFavorite) + .ToListAsync(); + } + + public async Task IsFavoriteAsync(Guid recipeId) + { + return await _context.Recipes + .AnyAsync(r => r.Id == recipeId && r.IsFavorite); + } + + public async Task AddFavoriteAsync(Guid recipeId) + { + var recipe = await _context.Recipes.FindAsync(recipeId); + if (recipe != null && !recipe.IsFavorite) + { + recipe.IsFavorite = true; + await _context.SaveChangesAsync(); + } + } + + public async Task RemoveFavoriteAsync(Guid recipeId) + { + var recipe = await _context.Recipes.FindAsync(recipeId); + if (recipe != null && recipe.IsFavorite) + { + recipe.IsFavorite = false; + await _context.SaveChangesAsync(); + } + } + } +} diff --git a/Francesco.Recipes.World/Repositories/Favorit/IFavoritRepository.cs b/Francesco.Recipes.World/Repositories/Favorit/IFavoritRepository.cs new file mode 100644 index 0000000..8f47e0a --- /dev/null +++ b/Francesco.Recipes.World/Repositories/Favorit/IFavoritRepository.cs @@ -0,0 +1,15 @@ +namespace Francesco.Recipes.World.Repositories.Favorit +{ + using Francesco.Recipes.World.Models.BackendModels.Recipe; + + public interface IFavoritRepository + { + Task> GetFavoriteRecipesAsync(); + + Task IsFavoriteAsync(Guid recipeId); + + Task AddFavoriteAsync(Guid recipeId); + + Task RemoveFavoriteAsync(Guid recipeId); + } +} From ffebc376e1321dc577c5cf7d77b01a3a8bef9b48 Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 8 Apr 2025 14:30:12 +0200 Subject: [PATCH 026/183] implement ingredient management methods --- .../Ingredient/IIngredientRepository.cs | 5 -- .../Ingredient/IngredientRepository.cs | 53 ++++++------------- 2 files changed, 17 insertions(+), 41 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/Ingredient/IIngredientRepository.cs b/Francesco.Recipes.World/Repositories/Ingredient/IIngredientRepository.cs index 013e2d1..702f174 100644 --- a/Francesco.Recipes.World/Repositories/Ingredient/IIngredientRepository.cs +++ b/Francesco.Recipes.World/Repositories/Ingredient/IIngredientRepository.cs @@ -1,21 +1,16 @@ namespace Francesco.Recipes.World.Repositories.Ingredient { using Francesco.Recipes.World.Models.BackendModels.Ingredient; - using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; public interface IIngredientRepository { - Task CreateIngredientToRecipeAsync(Recipe recipe, string ingredientName); - Task UpdateIngredientAsync(Ingredient ingredient); Task> GetIngredientsByRecipeIdAsync(Guid recipeId); Task> GetIngredientsByNameAsync(string name); - Task RemoveIngredientFromRecipeAsync(Recipe recipe, Guid ingredientId); - Task GetIngredientByIdAsync(Guid ingredientId); } } diff --git a/Francesco.Recipes.World/Repositories/Ingredient/IngredientRepository.cs b/Francesco.Recipes.World/Repositories/Ingredient/IngredientRepository.cs index f573a81..69a21ed 100644 --- a/Francesco.Recipes.World/Repositories/Ingredient/IngredientRepository.cs +++ b/Francesco.Recipes.World/Repositories/Ingredient/IngredientRepository.cs @@ -1,8 +1,8 @@ -namespace Francesco.Recipes.World.Repositories.Ingredient + +namespace Francesco.Recipes.World.Repositories.Ingredient { using Francesco.Recipes.World.Data; using Francesco.Recipes.World.Models.BackendModels.Ingredient; - using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; using Microsoft.EntityFrameworkCore; @@ -16,48 +16,29 @@ _context = context; } - public async Task CreateIngredientToRecipeAsync(Recipe recipe, string ingredientName) + public async Task UpdateRecipeIngredientAsync(RecipeIngredient recipeIngredient) { - if (recipe is null) + if (recipeIngredient == null) { - throw new ArgumentException("Recipe not found", nameof(recipe)); + throw new ArgumentNullException(nameof(recipeIngredient)); } - var newIngredient = new Ingredient - { - Id = Guid.NewGuid(), - Name = ingredientName, - }; + var existingRecipeIngredient = await _context.RecipeIngredients + .Include(ri => ri.Ingredient) + .Include(ri => ri.Unit) + .FirstOrDefaultAsync(ri => ri.Id == recipeIngredient.Id); - var recipeIngredient = new RecipeIngredient + if (existingRecipeIngredient == null) { - Id = Guid.NewGuid(), - Recipe = recipe, - Ingredient = newIngredient, - Quantity = 1, - }; + throw new InvalidOperationException($"RecipeIngredient with ID {recipeIngredient.Id} not found."); + } - _context.Ingredients.Add(newIngredient); - _context.RecipeIngredients.Add(recipeIngredient); + existingRecipeIngredient.Quantity = recipeIngredient.Quantity; + existingRecipeIngredient.Ingredient = recipeIngredient.Ingredient; + existingRecipeIngredient.Unit = recipeIngredient.Unit; + + _context.RecipeIngredients.Update(existingRecipeIngredient); await _context.SaveChangesAsync(); - - return newIngredient; - } - - public async Task RemoveIngredientFromRecipeAsync(Recipe recipe, Guid ingredientId) - { - if (recipe == null) - { - throw new ArgumentNullException(nameof(recipe)); - } - - var ingredientToRemove = recipe.RecipeIngredients.FirstOrDefault(i => i.Id == ingredientId); - - if (ingredientToRemove != null) - { - recipe.RecipeIngredients.Remove(ingredientToRemove); - await _context.SaveChangesAsync(); - } } public async Task> GetIngredientsByNameAsync(string name) From d4bcf7980bce8e8a3333a261e836721ae46d6d85 Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 8 Apr 2025 15:05:15 +0200 Subject: [PATCH 027/183] add instruction management for recipes --- .../Instruction/IInstructionRepository.cs | 7 +++ .../Instruction/InstructionRepository.cs | 59 ++++++++++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs index 7969570..1d22238 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs @@ -1,9 +1,16 @@ namespace Francesco.Recipes.World.Repositories.Instruction { using Francesco.Recipes.World.Models.BackendModels.Instruction; + using Francesco.Recipes.World.Models.BackendModels.Recipe; public interface IInstructionRepository { Task GetInstructionAsync(Guid instructionId); + + Task CreateInstructionToRecipeAsync(Guid recipeId, string description, int number); + + Task> GetInstructionsByRecipeIdAsync(Guid recipeId); + + Task RemoveInstructionFromRecipeAsync(Recipe recipe, Guid instructionId); } } diff --git a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs index b772813..09561c5 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs @@ -2,13 +2,18 @@ { using Francesco.Recipes.World.Data; using Francesco.Recipes.World.Models.BackendModels.Instruction; + using Francesco.Recipes.World.Models.BackendModels.Recipe; + using Francesco.Recipes.World.Repositories.Recipe; + using Microsoft.EntityFrameworkCore; public class InstructionRepository : IInstructionRepository { private readonly FrancescosRecipesWorldDbContext _context; + private readonly IRecipeRepository _recipeRepository; - public InstructionRepository(FrancescosRecipesWorldDbContext context) + public InstructionRepository(FrancescosRecipesWorldDbContext context, IRecipeRepository recipeRepository) { + _recipeRepository = recipeRepository; _context = context; } @@ -17,5 +22,57 @@ var instruction = await _context.Instructions.FindAsync(instructionId); return instruction ?? throw new InvalidDataException($"Instruction {instructionId} not found."); } + + public async Task CreateInstructionToRecipeAsync(Guid recipeId, string description, int number) + { + var recipe = await _recipeRepository.GetRecipeAsync(recipeId); + + if (string.IsNullOrWhiteSpace(description)) + { + throw new ArgumentException("Description cannot be empty", nameof(description)); + } + + if (number <= 0) + { + throw new ArgumentOutOfRangeException(nameof(number), "Number must be greater than 0."); + } + + var newInstruction = new Instruction + { + Id = Guid.NewGuid(), + Description = description, + Number = number, + Recipe = recipe, + }; + + _context.Instructions.Add(newInstruction); + await _context.SaveChangesAsync(); + + return newInstruction; + } + + public async Task RemoveInstructionFromRecipeAsync(Recipe recipe, Guid instructionId) + { + if (recipe == null) + { + throw new ArgumentNullException(nameof(recipe)); + } + + var instructionToRemove = recipe.Instructions.FirstOrDefault(i => i.Id == instructionId); + + if (instructionToRemove != null) + { + recipe.Instructions.Remove(instructionToRemove); + await _context.SaveChangesAsync(); + } + } + + public async Task> GetInstructionsByRecipeIdAsync(Guid recipeId) + { + return await _context.Instructions + .Include(i => i.Recipe) + .Where(i => i.Recipe.Id == recipeId) + .ToListAsync(); + } } } From 31fbafe806d607dd7098d10cc4ec6e0a2b0d7104 Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 8 Apr 2025 15:05:49 +0200 Subject: [PATCH 028/183] add repository logic for mediafile --- .../MediaFile/IMediaFileRepository.cs | 6 +- .../MediaFile/MediaFileRepository.cs | 97 +++++++++++++------ 2 files changed, 70 insertions(+), 33 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/MediaFile/IMediaFileRepository.cs b/Francesco.Recipes.World/Repositories/MediaFile/IMediaFileRepository.cs index 0fc5d91..879a1b2 100644 --- a/Francesco.Recipes.World/Repositories/MediaFile/IMediaFileRepository.cs +++ b/Francesco.Recipes.World/Repositories/MediaFile/IMediaFileRepository.cs @@ -2,12 +2,10 @@ { public interface IMediaFileRepository { - Task ReplaceInstructionImageAsync(Guid instructionId, IFormFile? newPhoto); + Task ReplaceInstructionImageAsync(Guid instructionId, Guid mediafileId, IFormFile? newPhoto); Task UploadInstructionImageAsync(Guid instructionId, IFormFile? photo); - Task ReplaceRecipeImageAsync(Guid recipeId, IFormFile? newPhoto); - - Task UploadRecipeImageAsync(Guid recipeId, IFormFile? photo); + Task UploadRecipeMediaAsync(Guid recipeId, IFormFile mediaFile); } } diff --git a/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs b/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs index 742e4c4..b4beb26 100644 --- a/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs +++ b/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs @@ -18,7 +18,7 @@ _recipeRepository = recipeRepository; } - public async Task ReplaceInstructionImageAsync(Guid instructionId, IFormFile? newPhoto) + public async Task ReplaceInstructionImageAsync(Guid instructionId, Guid mediaFileIdToReplace, IFormFile? newPhoto) { if (newPhoto is null) { @@ -26,24 +26,32 @@ } var instruction = await _instructionRepository.GetInstructionAsync(instructionId); - _context.RemoveRange(instruction.MediaFiles); - await _context.SaveChangesAsync(); - await UploadInstructionImageAsync(instructionId, newPhoto); - } - - public async Task ReplaceRecipeImageAsync(Guid recipeId, IFormFile? newPhoto) - { - if (newPhoto is null) + var mediaToReplace = instruction.MediaFiles.FirstOrDefault(m => m.Id == mediaFileIdToReplace); + if (mediaToReplace == null) { - throw new ArgumentNullException(nameof(newPhoto)); + throw new InvalidOperationException("The specified media file does not exist."); } - var recipe = await _recipeRepository.GetRecipeAsync(recipeId); - _context.RemoveRange(recipe.MediaFiles); + _context.MediaFiles.Remove(mediaToReplace); await _context.SaveChangesAsync(); - await UploadInstructionImageAsync(recipeId, newPhoto); + using (var memoryStream = new MemoryStream()) + { + await newPhoto.CopyToAsync(memoryStream); + + var newMedia = new MediaFile + { + Id = Guid.NewGuid(), + FileName = newPhoto.FileName, + MimeType = newPhoto.ContentType, + Data = memoryStream.ToArray(), + Instruction = instruction, + }; + + _context.MediaFiles.Add(newMedia); + await _context.SaveChangesAsync(); + } } public async Task UploadInstructionImageAsync(Guid instructionId, IFormFile? photo) @@ -72,30 +80,61 @@ } } - public async Task UploadRecipeImageAsync(Guid recipeId, IFormFile? photo) + public async Task UploadRecipeMediaAsync(Guid recipeId, IFormFile mediaFile) { - if (photo is null) + if (mediaFile == null) { - throw new ArgumentNullException(nameof(photo)); + throw new ArgumentNullException(nameof(mediaFile)); } var recipe = await _recipeRepository.GetRecipeAsync(recipeId); - using (var memoryStream = new MemoryStream()) + if (recipe == null) { - await photo.CopyToAsync(memoryStream); - - var recipeImage = new MediaFile - { - FileName = photo.FileName, - MimeType = photo.ContentType, - Data = memoryStream.ToArray(), - Recipe = recipe, - }; - - _context.Add(recipeImage); - await _context.SaveChangesAsync(); + throw new InvalidOperationException("The specified recipe does not exist."); } + + var isImage = mediaFile.ContentType.StartsWith("image/"); + var isVideo = mediaFile.ContentType.StartsWith("video/"); + + if (!isImage && !isVideo) + { + throw new InvalidOperationException("Only image or video files are allowed."); + } + + if (isImage) + { + var existingImage = recipe.MediaFiles?.FirstOrDefault(m => m.MimeType?.StartsWith("image/") == true); + if (existingImage != null) + { + _context.MediaFiles.Remove(existingImage); + await _context.SaveChangesAsync(); + } + } + else if (isVideo) + { + var existingVideo = recipe.MediaFiles?.FirstOrDefault(m => m.MimeType?.StartsWith("video/") == true); + if (existingVideo != null) + { + _context.MediaFiles.Remove(existingVideo); + await _context.SaveChangesAsync(); + } + } + + using var memoryStream = new MemoryStream(); + await mediaFile.CopyToAsync(memoryStream); + + var newMedia = new MediaFile + { + Id = Guid.NewGuid(), + FileName = mediaFile.FileName, + MimeType = mediaFile.ContentType, + Data = memoryStream.ToArray(), + Recipe = recipe, + }; + + _context.MediaFiles.Add(newMedia); + await _context.SaveChangesAsync(); } } } From ddd0cad36b7b033f3283441816affbd29f309c97 Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 8 Apr 2025 15:06:23 +0200 Subject: [PATCH 029/183] add recipe managment --- .../Repositories/Recipe/IRecipeRepository.cs | 5 +- .../Repositories/Recipe/RecipeRepository.cs | 47 +++++++++++++------ 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs index bc6ae01..c5d46a0 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs @@ -2,12 +2,13 @@ { using Francesco.Recipes.World.Models.BackendModels.Category; using Francesco.Recipes.World.Models.BackendModels.Recipe; - using Francesco.Recipes.World.Models.BackendModels.Unit; public interface IRecipeRepository { Task GetRecipeAsync(Guid recipeId); + Task GetRecipeByIdAsync(Guid id); + Task AddOrCreateIngredientToRecipeAsync(Guid recipeId, string ingredientName, int quantity, Guid unitId); Task RemoveIngredientFromRecipeAsync(Guid recipeId, Guid ingredientId); @@ -16,8 +17,6 @@ Task> GetRecipesByDifficultyAsync(Difficulty? difficulty); - Task AddUnitToRecipeAsync(string name, string symbol); - Task CreateRecipeForCategoryAsync(Category category, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime); } } diff --git a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs index 7b2c780..ae33b43 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs @@ -5,7 +5,6 @@ using Francesco.Recipes.World.Models.BackendModels.Ingredient; using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; - using Francesco.Recipes.World.Models.BackendModels.Unit; using Francesco.Recipes.World.Repositories.Ingredient; using Francesco.Recipes.World.Repositories.Unit; using Microsoft.EntityFrameworkCore; @@ -30,6 +29,17 @@ return recipe ?? throw new InvalidDataException($"Address {recipeId} not found."); } + public async Task GetRecipeByIdAsync(Guid recipeId) + { + return await _context.Recipes + .Include(r => r.RecipeIngredients) + .ThenInclude(ri => ri.Ingredient) + .Include(r => r.RecipeIngredients) + .ThenInclude(ri => ri.Unit) + .Include(r => r.MediaFiles) + .FirstOrDefaultAsync(r => r.Id == recipeId); + } + public async Task AddOrCreateIngredientToRecipeAsync(Guid recipeId, string ingredientName, int quantity, Guid unitId) { var recipe = await GetRecipeAsync(recipeId); @@ -77,13 +87,13 @@ } public async Task CreateRecipeForCategoryAsync( - Category category, - string name, - string description, - Difficulty difficulty, - int servings, - TimeSpan preparationTime, - TimeSpan cookingTime) + Category category, + string name, + string description, + Difficulty difficulty, + int servings, + TimeSpan preparationTime, + TimeSpan cookingTime) { if (category == null) { @@ -162,14 +172,21 @@ public async Task> GetRecipesByDifficultyAsync(Difficulty? difficulty) { - return await _context.Recipes - .Where(r => r.Difficulty == difficulty) - .ToListAsync(); - } + if (!difficulty.HasValue) + { + return await _context.Recipes + .Include(r => r.Category) + .Include(r => r.RecipeIngredients) + .ThenInclude(ri => ri.Ingredient) + .ToListAsync(); + } - public async Task AddUnitToRecipeAsync(string name, string symbol) - { - return await _unitRepository.AddUnitAsync(name, symbol); + return await _context.Recipes + .Where(r => r.Difficulty == difficulty.Value) + .Include(r => r.Category) + .Include(r => r.RecipeIngredients) + .ThenInclude(ri => ri.Ingredient) + .ToListAsync(); } } } From 712b561d12da1c1f745507229f94ff7102bd2ddf Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 8 Apr 2025 15:06:50 +0200 Subject: [PATCH 030/183] add shoppinglist managment for ingredient --- .../ShoppingList/IShoppingListRepository.cs | 15 +- .../ShoppingList/ShoppingListRepository.cs | 177 ++++++++++++------ 2 files changed, 132 insertions(+), 60 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs b/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs index 2205dbc..5411e89 100644 --- a/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs +++ b/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs @@ -1,18 +1,19 @@ - -namespace Francesco.Recipes.World.Repositories.ShoppingList +namespace Francesco.Recipes.World.Repositories.ShoppingList { + using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; using Francesco.Recipes.World.Models.BackendModels.Recipe; - using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; public interface IShoppingListRepository { - Task AddIngredientToShoppingListAsync(Guid recipeIngredientId, Guid shoppingListId); + Task AddIngredientsToShoppingListAsync(Guid shoppingListId, List ingredientIds); - Task RemoveRecipeIngredientFromShoppingListAsync(Guid recipeIngredientId); + Task> GetIngredientsOfRecipeInListAsync(Guid shoppingListRecipeId); - Task RemoveRecipeFromShoppingListIfEmptyAsync(Guid recipeId, Guid shoppingListId); + Task UpdateIngredientCheckedAsync(Guid shoppingListRecipeId, Guid recipeIngredientId, bool isChecked); - Task> GetShoppingListsByIngredientOfRecipeAsync(Guid ingredientId, Guid recipeId); + Task RemoveRecipeIfEmptyAsync(Guid shoppingListRecipeId); + + Task DeleteShoppingListAsync(Guid shoppingListId); Task GetRecipeByNameAndImageAsync(string recipeName, string imageFileName); } diff --git a/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs b/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs index 5609143..45793f0 100644 --- a/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs +++ b/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs @@ -3,6 +3,7 @@ using Francesco.Recipes.World.Data; using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; using Francesco.Recipes.World.Models.BackendModels.Recipe; + using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList; using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; using Microsoft.EntityFrameworkCore; @@ -15,92 +16,162 @@ _context = context; } - public async Task AddIngredientToShoppingListAsync(Guid recipeIngredientId, Guid shoppingListId) + public async Task AddIngredientsToShoppingListAsync(Guid recipeId, List ingredientIds) { - var existingEntry = await _context.RecipeIngredientsShoppingLists - .FirstOrDefaultAsync(risl => risl.RecipeIngredient.Id == recipeIngredientId && risl.ShoppingList.Id == shoppingListId); - - if (existingEntry != null) + if (ingredientIds == null || !ingredientIds.Any()) { - return; + throw new ArgumentNullException(nameof(ingredientIds)); } - var recipeIngredient = await _context.RecipeIngredients - .FirstOrDefaultAsync(ri => ri.Id == recipeIngredientId); + var recipe = await _context.Recipes + .Include(r => r.RecipeIngredients) + .FirstOrDefaultAsync(r => r.Id == recipeId); - if (recipeIngredient == null) + if (recipe == null) { - throw new InvalidOperationException($"Recipe ingredient with ID {recipeIngredientId} not found."); + throw new Exception("Rezept nicht gefunden."); } var shoppingList = await _context.ShoppingLists - .FirstOrDefaultAsync(sl => sl.Id == shoppingListId); + .Include(sl => sl.RecipeShoppingList) + .ThenInclude(rsl => rsl.SelectedIngredients) + .ThenInclude(si => si.RecipeIngredient) + .Include(sl => sl.RecipeShoppingList) + .ThenInclude(rsl => rsl.Recipe) + .FirstOrDefaultAsync(sl => sl.RecipeShoppingList.Any(rsl => rsl.Recipe.Id == recipeId)); + + var recipeIngredients = await _context.RecipeIngredients + .Where(ri => ingredientIds.Contains(ri.Id) && ri.Recipe.Id == recipeId) + .ToListAsync(); + + if (!recipeIngredients.Any()) + { + throw new Exception("Keine gültigen Zutaten gefunden."); + } if (shoppingList == null) { - throw new InvalidOperationException($"Shopping list with ID {shoppingListId} not found."); + shoppingList = new ShoppingList + { + Id = Guid.NewGuid(), + CreatedAt = DateTime.UtcNow, + ModifiedAt = null, + RecipeShoppingList = new List(), + }; + + var newRecipeList = new RecipeShoppingList + { + Id = Guid.NewGuid(), + Recipe = recipe, + SelectedIngredients = recipeIngredients.Select(ri => new RecipeIngredientShoppingList + { + Id = Guid.NewGuid(), + RecipeIngredient = ri, + IsChecked = false, + }).ToList(), + }; + + shoppingList.RecipeShoppingList.Add(newRecipeList); + _context.ShoppingLists.Add(shoppingList); + } + else + { + var existingRecipeList = shoppingList.RecipeShoppingList + .FirstOrDefault(rsl => rsl.Recipe.Id == recipeId); + + if (existingRecipeList == null) + { + existingRecipeList = new RecipeShoppingList + { + Id = Guid.NewGuid(), + Recipe = recipe, + SelectedIngredients = new List(), + }; + + shoppingList.RecipeShoppingList.Add(existingRecipeList); + } + + foreach (var ri in recipeIngredients) + { + var alreadyExists = existingRecipeList.SelectedIngredients + .Any(si => si.RecipeIngredient.Id == ri.Id); + + if (!alreadyExists) + { + existingRecipeList.SelectedIngredients.Add(new RecipeIngredientShoppingList + { + Id = Guid.NewGuid(), + RecipeIngredient = ri, + IsChecked = false, + }); + } + } + + shoppingList.ModifiedAt = DateTime.UtcNow; + } + } + + public async Task> GetIngredientsOfRecipeInListAsync(Guid shoppingListRecipeId) + { + return await _context.RecipeIngredientsShoppingLists + .AsNoTracking() + .Include(i => i.RecipeIngredient) + .ThenInclude(ri => ri.Ingredient) + .Include(i => i.RecipeIngredient.Unit) + .Where(i => i.RecipeShoppingList.Id == shoppingListRecipeId) + .ToListAsync(); + } + + public async Task UpdateIngredientCheckedAsync(Guid shoppingListRecipeId, Guid recipeIngredientId, bool isChecked) + { + var item = await _context.RecipeIngredientsShoppingLists + .FirstOrDefaultAsync(i => + i.RecipeShoppingList.Id == shoppingListRecipeId && + i.RecipeIngredient.Id == recipeIngredientId); + + if (item == null) + { + throw new Exception("Zutat nicht gefunden."); } - var newEntry = new RecipeIngredientShoppingList - { - RecipeIngredient = recipeIngredient, - ShoppingList = shoppingList, - }; - - _context.RecipeIngredientsShoppingLists.Add(newEntry); + item.IsChecked = isChecked; await _context.SaveChangesAsync(); } - public async Task RemoveRecipeIngredientFromShoppingListAsync(Guid recipeIngredientId) + public async Task RemoveRecipeIfEmptyAsync(Guid shoppingListRecipeId) { - var entry = await _context.RecipeIngredientsShoppingLists - .FirstOrDefaultAsync(risl => risl.RecipeIngredient.Id == recipeIngredientId); + var recipeEntry = await _context.RecipeShoppingLists + .Include(r => r.SelectedIngredients) + .FirstOrDefaultAsync(r => r.Id == shoppingListRecipeId); - if (entry != null) + if (recipeEntry != null && !recipeEntry.SelectedIngredients.Any()) { - _context.RecipeIngredientsShoppingLists.Remove(entry); + _context.RecipeShoppingLists.Remove(recipeEntry); await _context.SaveChangesAsync(); } } - public async Task RemoveRecipeFromShoppingListIfEmptyAsync(Guid recipeId, Guid shoppingListId) + public async Task DeleteShoppingListAsync(Guid shoppingListId) { - var hasIngredients = await _context.RecipeIngredientsShoppingLists - .AnyAsync(risl => risl.RecipeIngredient.Recipe.Id == recipeId && risl.ShoppingList.Id == shoppingListId); + var list = await _context.ShoppingLists + .Include(sl => sl.RecipeShoppingList) + .ThenInclude(r => r.SelectedIngredients) + .FirstOrDefaultAsync(sl => sl.Id == shoppingListId); - if (!hasIngredients) + if (list != null) { - var shoppingList = await _context.ShoppingLists - .Include(sl => sl.RecipeIngredientShoppingLists) - .FirstOrDefaultAsync(sl => sl.Id == shoppingListId); - - if (shoppingList != null) - { - var recipeToRemove = shoppingList.RecipeIngredientShoppingLists - .FirstOrDefault(risl => risl.RecipeIngredient.Recipe.Id == recipeId); - - if (recipeToRemove != null) - { - _context.RecipeIngredientsShoppingLists.Remove(recipeToRemove); - await _context.SaveChangesAsync(); - } - } + _context.ShoppingLists.Remove(list); + await _context.SaveChangesAsync(); } } - public async Task> GetShoppingListsByIngredientOfRecipeAsync(Guid ingredientId, Guid recipeId) - { - return await _context.ShoppingLists - .Where(sl => sl.RecipeIngredientShoppingLists.Any(risl => risl.RecipeIngredient.Ingredient.Id == ingredientId && risl.RecipeIngredient.Recipe.Id == recipeId)) - .ToListAsync(); - } - public async Task GetRecipeByNameAndImageAsync(string recipeName, string imageFileName) { return await _context.Recipes - .Where(r => r.Name == recipeName && r.MediaFiles.Any(mf => mf.FileName == imageFileName)) - .Include(r => r.MediaFiles.Where(mf => mf.FileName == imageFileName)) - .FirstOrDefaultAsync(); + .AsNoTracking() + .Where(r => r.Name == recipeName && r.MediaFiles.Any(mf => mf.FileName == imageFileName)) + .Include(r => r.MediaFiles.Where(mf => mf.FileName == imageFileName)) + .FirstOrDefaultAsync(); } } } From 36bca4d6584d644a81c2fd755d53ae6bea018419 Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 8 Apr 2025 15:22:28 +0200 Subject: [PATCH 031/183] add CategoryController with endpoints --- .../Controller/Category/CategoryController.cs | 3 +- .../MediaFile/MediaFileController.cs | 44 +- .../Controller/Recipe/RecipeController.cs | 160 ++++- .../ShoppingList/ShoppingListController.cs | 48 +- .../Data/FrancescosRecipesWorldDbContext.cs | 5 +- .../Francesco.Recipes.World.csproj | 10 +- ...120842_UpdateShoppingLIstLogic.Designer.cs | 572 ++++++++++++++++++ .../20250402120842_UpdateShoppingLIstLogic.cs | 162 +++++ ...escosRecipesWorldDbContextModelSnapshot.cs | 73 ++- .../Models/BackendModels/Recipe/Difficulty.cs | 9 +- .../RecipeIngredientShoppingList.cs | 6 +- .../RecipeShoppingList/RecipeShoppingList.cs | 17 + .../Shoppinglist/ShoppingList.cs | 4 +- Francesco.Recipes.World/Program.cs | 12 +- .../Category/CategoryRecipesViewModel.cs | 12 + .../Views/Recipe/AddInstruction.cshtml | 59 ++ .../Views/Recipe/AddOrCreateIngredient.cshtml | 36 +- .../Views/Recipe/CategoryRecipes.cshtml | 98 +++ .../Views/Recipe/Create.cshtml | 78 +-- .../Views/Recipe/Details.cshtml | 68 +++ .../Views/Recipe/FilterByDifficulty.cshtml | 100 +-- .../Recipe/FilterByDifficultyViewModel.cs | 2 +- ...AddIngredientsToShoppingListPartial.cshtml | 43 ++ .../Views/ShoppingList/Details.cshtml | 56 ++ 24 files changed, 1535 insertions(+), 142 deletions(-) create mode 100644 Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.Designer.cs create mode 100644 Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.cs create mode 100644 Francesco.Recipes.World/Models/BackendModels/RecipeShoppingList/RecipeShoppingList.cs create mode 100644 Francesco.Recipes.World/Views/Category/CategoryRecipesViewModel.cs create mode 100644 Francesco.Recipes.World/Views/Recipe/AddInstruction.cshtml create mode 100644 Francesco.Recipes.World/Views/Recipe/CategoryRecipes.cshtml create mode 100644 Francesco.Recipes.World/Views/Recipe/Details.cshtml create mode 100644 Francesco.Recipes.World/Views/Shared/_AddIngredientsToShoppingListPartial.cshtml create mode 100644 Francesco.Recipes.World/Views/ShoppingList/Details.cshtml diff --git a/Francesco.Recipes.World/Controller/Category/CategoryController.cs b/Francesco.Recipes.World/Controller/Category/CategoryController.cs index 0257ab4..29b06b5 100644 --- a/Francesco.Recipes.World/Controller/Category/CategoryController.cs +++ b/Francesco.Recipes.World/Controller/Category/CategoryController.cs @@ -5,6 +5,7 @@ using Francesco.Recipes.World.Repositories.Category; using Microsoft.AspNetCore.Mvc; + [ValidateAntiForgeryToken] [Route("Category")] public class CategoryController : Controller @@ -37,7 +38,7 @@ public async Task>> GetRecipesByCategory(Guid id) { var recipes = await _categoryRepository.GetRecipesByCategoryAsync(id); - return Ok(recipes); + return View(recipes); } } } diff --git a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs index 6cec16d..14f59fe 100644 --- a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs +++ b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs @@ -1,6 +1,48 @@ namespace Francesco.Recipes.World.Controller.MediaFile { - public class MediaFileController + using Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Repositories.MediaFile; + using Microsoft.AspNetCore.Mvc; + + [ValidateAntiForgeryToken] + + [Route("categories/{categoryId}/Recipe")] + public class MediaFileController : Controller { + private readonly IMediaFileRepository _mediaFileRepository; + private readonly FrancescosRecipesWorldDbContext _context; + + public MediaFileController(IMediaFileRepository mediaFileRepository, FrancescosRecipesWorldDbContext context) + { + _mediaFileRepository = mediaFileRepository; + _context = context; + } + + // POST: /UploadImage + [HttpPost("UploadImage")] + public async Task UploadImage(Guid recipeId, IFormFile? mediaFile) + { + if (mediaFile is null) + { + return BadRequest("Photo is required."); + } + + try + { + await _mediaFileRepository.UploadRecipeMediaAsync(recipeId, mediaFile); + return Ok("Image uploaded successfully."); + } + catch (Exception ex) + { + return StatusCode(500, $"Internal server error: {ex.Message}"); + } + } + + // GET: /UploadImage + [HttpGet("UploadImage")] + public IActionResult UploadImageView() + { + return View(); + } } } diff --git a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs index 9b4cd9e..05d99aa 100644 --- a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs +++ b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs @@ -1,43 +1,94 @@ namespace Francesco.Recipes.World.Controller.Recipe { + using System.ComponentModel.DataAnnotations; using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Repositories.Category; + using Francesco.Recipes.World.Repositories.Favorit; using Francesco.Recipes.World.Repositories.Ingredient; + using Francesco.Recipes.World.Repositories.Instruction; + using Francesco.Recipes.World.Repositories.MediaFile; using Francesco.Recipes.World.Repositories.Recipe; using Francesco.Recipes.World.Repositories.Unit; + using Francesco.Recipes.World.Views.Category; + using Francesco.Recipes.World.Views.Recipe; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; - [Route("categories/{categoryId}/Recipe")] + [ValidateAntiForgeryToken] + [Route("Recipe")] public class RecipeController : Controller { private readonly IRecipeRepository _recipeRepository; private readonly IUnitRepository _unitRepository; private readonly ICategoryRepository _categoryRepository; private readonly IIngredientRepository _ingredientRepository; + private readonly IMediaFileRepository _mediaFileRepository; + private readonly IInstructionRepository _instructionRepository; + private readonly IFavoritRepository _favoritRepository; - public RecipeController(IRecipeRepository recipeRepository, IUnitRepository unitRepository, ICategoryRepository categoryRepository, IIngredientRepository ingredientRepository) + [Display(Name = "Schwierigkeitsgrad")] + [BindProperty(SupportsGet = true)] + public Difficulty? SelectedDifficulty { get; set; } + + public RecipeController(IRecipeRepository recipeRepository, IUnitRepository unitRepository, ICategoryRepository categoryRepository, IIngredientRepository ingredientRepository, IMediaFileRepository mediaFileRepository, IInstructionRepository instructionRepository, IFavoritRepository favoritRepository) { _recipeRepository = recipeRepository; _unitRepository = unitRepository; _categoryRepository = categoryRepository; _ingredientRepository = ingredientRepository; + Recipes = new List(); + _mediaFileRepository = mediaFileRepository; + _instructionRepository = instructionRepository; + _favoritRepository = favoritRepository; } public IReadOnlyCollection Recipes { get; set; } - // GET: /Recipe/AddOrCreateIngredient + // GET: /Recipe/{recipeId}/AddOrCreateIngredient [HttpGet("{recipeId}/AddOrCreateIngredient")] - public async Task AddOrCreateIngredient() + public async Task AddOrCreateIngredient(Guid recipeId) { var units = await _unitRepository.GetAllUnitsAsync(); ViewBag.Units = new SelectList(units, "Id", "Name"); + ViewBag.RecipeId = recipeId; return View(); } - // POST: /Recipe/AddOrCreateIngredient + // GET: /Recipe/Details/{recipeId} + [HttpGet("Details/{recipeId}")] + public async Task Details(Guid recipeId) + { + var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId); + if (recipe == null) + { + return NotFound("Recipe not found."); + } + + return View(recipe); + } + + // GET: /Recipe/CategoryRecipes + [HttpGet("CategoryRecipes")] + public async Task CategoryRecipes() + { + var categories = await _categoryRepository.GetAllCategoriesAsync(); + var viewModel = new List(); + + foreach (var category in categories) + { + var recipes = await _categoryRepository.GetRecipesByCategoryAsync(category.Id); + viewModel.Add(new CategoryRecipesViewModel + { + Category = category, + Recipes = recipes, + }); + } + + return View(viewModel); + } + + // POST: /Recipe/{recipeId}/AddOrCreateIngredient [HttpPost("{recipeId}/AddOrCreateIngredient")] - [ValidateAntiForgeryToken] public async Task AddOrCreateIngredient(Guid recipeId, string ingredientName, int quantity, Guid unitId) { if (quantity <= 0) @@ -56,8 +107,8 @@ return RedirectToAction("Details", new { id = recipeId }); } - // GET: /categories/{categoryId}/Recipe/Create - [HttpGet("Create")] + // GET: /Recipe/Create/{categoryId} + [HttpGet("Create/{categoryId}")] public async Task Create(Guid categoryId) { var category = await _categoryRepository.GetCategoryByIdAsync(categoryId); @@ -70,10 +121,9 @@ return View(); } - // POST: /categories/{categoryId}/Recipe/Create - [HttpPost("Create")] - [ValidateAntiForgeryToken] - public async Task Create(Guid categoryId, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime) + // POST: /Recipe/Create/{categoryId} + [HttpPost("Create/{categoryId}")] + public async Task Create(Guid categoryId, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime, IFormFile? photo) { if (string.IsNullOrWhiteSpace(name)) { @@ -103,13 +153,18 @@ return NotFound($"Kategorie mit ID {categoryId} wurde nicht gefunden."); } - await _recipeRepository.CreateRecipeForCategoryAsync(categoryEntity, name, description, difficulty, servings, preparationTime, cookingTime); + var recipe = await _recipeRepository.CreateRecipeForCategoryAsync(categoryEntity, name, description, difficulty, servings, preparationTime, cookingTime); + if (photo != null) + { + await _mediaFileRepository.UploadRecipeMediaAsync(recipe.Id, photo); + } + return RedirectToAction("Details", "Category", new { id = categoryId }); } - // GET: /categories/{categoryId}/Recipe/{recipeId}/RemoveIngredient/{ingredientId} + // GET: /Recipe/{recipeId}/RemoveIngredient/{ingredientId} [HttpGet("{recipeId}/RemoveIngredient/{ingredientId}")] - public async Task RemoveIngredient(Guid categoryId, Guid recipeId, Guid ingredientId) + public async Task RemoveIngredient(Guid recipeId, Guid ingredientId) { var recipe = await _recipeRepository.GetRecipeAsync(recipeId); var ingredient = await _ingredientRepository.GetIngredientByIdAsync(ingredientId); @@ -121,28 +176,87 @@ ViewBag.RecipeId = recipeId; ViewBag.IngredientId = ingredientId; - ViewBag.CategoryId = categoryId; ViewBag.IngredientName = ingredient.Name; return View(); } - // POST: /categories/{categoryId}/Recipe/{recipeId}/RemoveIngredient/{ingredientId} + // POST: /Recipe/{recipeId}/RemoveIngredient/{ingredientId} [HttpPost("{recipeId}/RemoveIngredient/{ingredientId}")] - [ValidateAntiForgeryToken] - public async Task RemoveIngredientConfirmed(Guid categoryId, Guid recipeId, Guid ingredientId) + public async Task RemoveIngredientConfirmed(Guid recipeId, Guid ingredientId) { await _recipeRepository.RemoveIngredientFromRecipeAsync(recipeId, ingredientId); TempData["SuccessMessage"] = "Ingredient removed successfully."; return RedirectToAction("Details", new { id = recipeId }); } - // GET: /categories/{categoryId}/Recipe/FilterByDifficulty + // GET: /Recipe/FilterByDifficulty [HttpGet("FilterByDifficulty")] - public async Task FilterByDifficulty(Difficulty? difficulty) + public async Task FilterByDifficulty(Difficulty? selectedDifficulty) { - Recipes = await _recipeRepository.GetRecipesByDifficultyAsync(difficulty); - return View(Recipes); + var recipes = await _recipeRepository.GetRecipesByDifficultyAsync(selectedDifficulty); + var viewModel = new FilterByDifficultyViewModel + { + SelectedDifficulty = selectedDifficulty, + Recipes = recipes, + }; + return View(viewModel); + } + + // GET: /Recipe/{recipeId}/AddInstruction + [HttpGet("{recipeId}/AddInstruction")] + public async Task AddInstruction(Guid recipeId) + { + var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId); + if (recipe == null) + { + return NotFound("Recipe not found."); + } + + ViewBag.RecipeId = recipeId; + return View(recipe); + } + + // POST: /Recipe/{recipeId}/AddInstruction + [HttpPost("{recipeId}/AddInstruction")] + public async Task AddInstruction(Guid recipeId, string description, int number) + { + try + { + await _instructionRepository.CreateInstructionToRecipeAsync(recipeId, description, number); + return RedirectToAction("AddInstruction", new { recipeId }); + } + catch (Exception ex) + { + ModelState.AddModelError(string.Empty, ex.Message); + var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId); + ViewBag.RecipeId = recipeId; + return View(recipe); + } + } + + // GET: /Recipe/Favorites + [HttpGet("Favorites")] + public async Task Favorites() + { + var favoriteRecipes = await _favoritRepository.GetFavoriteRecipesAsync(); + return View(favoriteRecipes); + } + + // POST: /Recipe/AddFavorite + [HttpPost("AddFavorite")] + public async Task AddFavorite(Guid recipeId) + { + await _favoritRepository.AddFavoriteAsync(recipeId); + return RedirectToAction("Details", new { recipeId }); + } + + // POST: /Recipe/RemoveFavorite + [HttpPost("RemoveFavorite")] + public async Task RemoveFavorite(Guid recipeId) + { + await _favoritRepository.RemoveFavoriteAsync(recipeId); + return RedirectToAction("Details", new { recipeId }); } } } diff --git a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs index 38fdf4b..7f6a434 100644 --- a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs +++ b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs @@ -1,6 +1,50 @@ -namespace Francesco.Recipes.World.Controller.ShoppingList +namespace Francesco.Recipes.World.Controllers { - public class ShoppingListController + using Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Repositories.ShoppingList; + using Microsoft.AspNetCore.Mvc; + using Microsoft.EntityFrameworkCore; + + [Route("ShoppingList")] + public class ShoppingListController : Controller { + private readonly IShoppingListRepository _shoppingListRepository; + private readonly FrancescosRecipesWorldDbContext _context; + + public ShoppingListController(IShoppingListRepository shoppingListRepository, FrancescosRecipesWorldDbContext context) + { + _shoppingListRepository = shoppingListRepository; + _context = context; + } + + [HttpPost("CreateOrAddIngredients")] + public async Task CreateOrAddIngredients([FromBody] CreateOrAddIngredientsRequest request) + { + if (request == null || request.IngredientIds == null || !request.IngredientIds.Any()) + { + return BadRequest("No ingredients provided."); + } + + await _shoppingListRepository.AddIngredientsToShoppingListAsync(request.RecipeId, request.IngredientIds); + + var shoppingList = await _context.ShoppingLists + .Include(sl => sl.RecipeShoppingList) + .ThenInclude(rsl => rsl.Recipe) + .FirstOrDefaultAsync(sl => sl.RecipeShoppingList.Any(rsl => rsl.Recipe.Id == request.RecipeId)); + + if (shoppingList == null) + { + return BadRequest("Error creating shopping list."); + } + + return Json(new { shoppingListId = shoppingList.Id }); + } + + public class CreateOrAddIngredientsRequest + { + public Guid RecipeId { get; set; } + + public List IngredientIds { get; set; } = new (); + } } } diff --git a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs index 4fd6bd8..7a8a5e7 100644 --- a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs +++ b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs @@ -9,7 +9,8 @@ using Francesco.Recipes.World.Models.BackendModels.MediaFile; using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; - using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; +using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList; +using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; using Francesco.Recipes.World.Models.BackendModels.Unit; using Microsoft.EntityFrameworkCore; @@ -36,6 +37,8 @@ public DbSet RecipeIngredientsShoppingLists => Set(); + public DbSet RecipeShoppingLists => Set(); + public DbSet ShoppingLists => Set(); public DbSet MediaFiles => Set(); diff --git a/Francesco.Recipes.World/Francesco.Recipes.World.csproj b/Francesco.Recipes.World/Francesco.Recipes.World.csproj index 549a91d..7d430a2 100644 --- a/Francesco.Recipes.World/Francesco.Recipes.World.csproj +++ b/Francesco.Recipes.World/Francesco.Recipes.World.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -45,5 +45,13 @@ + + + + + + + + diff --git a/Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.Designer.cs b/Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.Designer.cs new file mode 100644 index 0000000..d99ae26 --- /dev/null +++ b/Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.Designer.cs @@ -0,0 +1,572 @@ +// + +using Francesco.Recipes.World.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + + +#nullable disable + +namespace Francesco.Recipes.World.Migrations +{ + [DbContext(typeof(FrancescosRecipesWorldDbContext))] + [Migration("20250402120842_UpdateShoppingLIstLogic")] + partial class UpdateShoppingLIstLogic + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + + b.HasData( + new + { + Id = new Guid("b248244f-f21c-4555-a14d-5dd49a2717cf"), + Name = "Vorspeisen & Snacks" + }, + new + { + Id = new Guid("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"), + Name = "Erste Gänge" + }, + new + { + Id = new Guid("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"), + Name = "Hauptgerichte" + }, + new + { + Id = new Guid("90deec39-dcd0-422d-9018-ac8389e332e1"), + Name = "Desserts & Süßspeisen" + }, + new + { + Id = new Guid("28e39168-701a-4084-81da-d96c987c462f"), + Name = "Beilagen & Salate" + }, + new + { + Id = new Guid("20585b74-4805-4aff-a6df-aa6b7af04ff1"), + Name = "Kuchen" + }, + new + { + Id = new Guid("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"), + Name = "Hefegebäck & Brot" + }, + new + { + Id = new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"), + Name = "Soßen & Saucen" + }, + new + { + Id = new Guid("23b1c740-e427-44f9-a6ea-d33d3f30f05a"), + Name = "Marmeladen & Eingemachtes" + }, + new + { + Id = new Guid("0a91a200-dc76-4e00-b38c-b38cab5b69d7"), + Name = "Getränke" + }); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Favorits"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Ingredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("IngredientId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsChecked") + .HasColumnType("bit"); + + b.Property("RecipeIngredientId") + .HasColumnType("uniqueidentifier"); + + b.Property("RecipeShoppingListId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IngredientId"); + + b.HasIndex("RecipeIngredientId"); + + b.HasIndex("RecipeShoppingListId"); + + b.ToTable("RecipeIngredientsShoppingLists"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Number") + .HasColumnType("int"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RecipeId"); + + b.ToTable("Instructions"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Data") + .HasColumnType("varbinary(max)"); + + b.Property("FileName") + .HasColumnType("nvarchar(max)"); + + b.Property("InstructionId") + .HasColumnType("uniqueidentifier"); + + b.Property("MimeType") + .HasColumnType("nvarchar(max)"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("InstructionId"); + + b.HasIndex("RecipeId"); + + b.ToTable("MediaFiles"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("CookingTime") + .HasColumnType("time"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Difficulty") + .HasColumnType("int"); + + b.Property("FavoritId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsFavorite") + .HasColumnType("bit"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PreparationTime") + .HasColumnType("time"); + + b.Property("Servings") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("FavoritId"); + + b.ToTable("Recipes"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("IngredientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.Property("UnitId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IngredientId"); + + b.HasIndex("RecipeId"); + + b.HasIndex("UnitId"); + + b.ToTable("RecipeIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.Property("ShoppingListId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RecipeId"); + + b.HasIndex("ShoppingListId"); + + b.ToTable("RecipeShoppingLists"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("ShoppingLists"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Symbol") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Units"); + + b.HasData( + new + { + Id = new Guid("62eb2c11-8768-46fb-9db0-c77f940bb4aa"), + Name = "liter", + Symbol = "l" + }, + new + { + Id = new Guid("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"), + Name = "gramm", + Symbol = "g" + }, + new + { + Id = new Guid("7e3d1b86-ac48-45d6-814e-d3492a86db1d"), + Name = "kilogramm", + Symbol = "kg" + }, + new + { + Id = new Guid("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"), + Name = "stücke", + Symbol = "stk" + }, + new + { + Id = new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"), + Name = "blatt", + Symbol = "blatt" + }, + new + { + Id = new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"), + Name = "messerspitze", + Symbol = "msp" + }, + new + { + Id = new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"), + Name = "stange", + Symbol = "stange" + }, + new + { + Id = new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"), + Name = "bund", + Symbol = "bund" + }, + new + { + Id = new Guid("66556e0e-eb2b-4bc3-9a56-135dd508ed09"), + Name = "zehe", + Symbol = "zehe" + }, + new + { + Id = new Guid("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"), + Name = "teelöffel", + Symbol = "TL" + }, + new + { + Id = new Guid("24a91b89-3389-4465-89e4-2f70d2ea6fd7"), + Name = "esslöffel", + Symbol = "EL" + }); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", null) + .WithMany("IngredientShoppingLists") + .HasForeignKey("IngredientId"); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", "RecipeIngredient") + .WithMany() + .HasForeignKey("RecipeIngredientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", "RecipeShoppingList") + .WithMany("SelectedIngredients") + .HasForeignKey("RecipeShoppingListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RecipeIngredient"); + + b.Navigation("RecipeShoppingList"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany("Instructions") + .HasForeignKey("RecipeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Recipe"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", "Instruction") + .WithMany("MediaFiles") + .HasForeignKey("InstructionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany("MediaFiles") + .HasForeignKey("RecipeId"); + + b.Navigation("Instruction"); + + b.Navigation("Recipe"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", "Category") + .WithMany("Recipes") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorit") + .WithMany("Recipe") + .HasForeignKey("FavoritId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Favorit"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient") + .WithMany("RecipeIngredients") + .HasForeignKey("IngredientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany("RecipeIngredients") + .HasForeignKey("RecipeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit") + .WithMany("RecipeIngredient") + .HasForeignKey("UnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ingredient"); + + b.Navigation("Recipe"); + + b.Navigation("Unit"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany() + .HasForeignKey("RecipeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList") + .WithMany("RecipeIngredientShoppingLists") + .HasForeignKey("ShoppingListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Recipe"); + + b.Navigation("ShoppingList"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b => + { + b.Navigation("Recipes"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b => + { + b.Navigation("Recipe"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b => + { + b.Navigation("IngredientShoppingLists"); + + b.Navigation("RecipeIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => + { + b.Navigation("MediaFiles"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => + { + b.Navigation("Instructions"); + + b.Navigation("MediaFiles"); + + b.Navigation("RecipeIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => + { + b.Navigation("SelectedIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => + { + b.Navigation("RecipeIngredientShoppingLists"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b => + { + b.Navigation("RecipeIngredient"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.cs b/Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.cs new file mode 100644 index 0000000..5caf69f --- /dev/null +++ b/Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.cs @@ -0,0 +1,162 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Francesco.Recipes.World.Migrations +{ + /// + public partial class UpdateShoppingLIstLogic : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + if (migrationBuilder is null) + { + throw new ArgumentNullException(nameof(migrationBuilder)); + } + + migrationBuilder.DropForeignKey( + name: "FK_RecipeIngredientsShoppingLists_ShoppingLists_ShoppingListId", + table: "RecipeIngredientsShoppingLists"); + + migrationBuilder.DropForeignKey( + name: "FK_Recipes_Categories_CategoryId", + table: "Recipes"); + + migrationBuilder.RenameColumn( + name: "ShoppingListId", + table: "RecipeIngredientsShoppingLists", + newName: "RecipeShoppingListId"); + + migrationBuilder.RenameIndex( + name: "IX_RecipeIngredientsShoppingLists_ShoppingListId", + table: "RecipeIngredientsShoppingLists", + newName: "IX_RecipeIngredientsShoppingLists_RecipeShoppingListId"); + + migrationBuilder.AlterColumn( + name: "CategoryId", + table: "Recipes", + type: "uniqueidentifier", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uniqueidentifier", + oldNullable: true); + + migrationBuilder.AddColumn( + name: "IsChecked", + table: "RecipeIngredientsShoppingLists", + type: "bit", + nullable: false, + defaultValue: false); + + migrationBuilder.CreateTable( + name: "RecipeShoppingLists", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + ShoppingListId = table.Column(type: "uniqueidentifier", nullable: false), + RecipeId = table.Column(type: "uniqueidentifier", nullable: false), + }, + constraints: table => + { + table.PrimaryKey("PK_RecipeShoppingLists", x => x.Id); + table.ForeignKey( + name: "FK_RecipeShoppingLists_Recipes_RecipeId", + column: x => x.RecipeId, + principalTable: "Recipes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RecipeShoppingLists_ShoppingLists_ShoppingListId", + column: x => x.ShoppingListId, + principalTable: "ShoppingLists", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_RecipeShoppingLists_RecipeId", + table: "RecipeShoppingLists", + column: "RecipeId"); + + migrationBuilder.CreateIndex( + name: "IX_RecipeShoppingLists_ShoppingListId", + table: "RecipeShoppingLists", + column: "ShoppingListId"); + + migrationBuilder.AddForeignKey( + name: "FK_RecipeIngredientsShoppingLists_RecipeShoppingLists_RecipeShoppingListId", + table: "RecipeIngredientsShoppingLists", + column: "RecipeShoppingListId", + principalTable: "RecipeShoppingLists", + principalColumn: "Id", + onDelete: ReferentialAction.NoAction); + + migrationBuilder.AddForeignKey( + name: "FK_Recipes_Categories_CategoryId", + table: "Recipes", + column: "CategoryId", + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + if (migrationBuilder is null) + { + throw new ArgumentNullException(nameof(migrationBuilder)); + } + + migrationBuilder.DropForeignKey( + name: "FK_RecipeIngredientsShoppingLists_RecipeShoppingLists_RecipeShoppingListId", + table: "RecipeIngredientsShoppingLists"); + + migrationBuilder.DropForeignKey( + name: "FK_Recipes_Categories_CategoryId", + table: "Recipes"); + + migrationBuilder.DropTable( + name: "RecipeShoppingLists"); + + migrationBuilder.DropColumn( + name: "IsChecked", + table: "RecipeIngredientsShoppingLists"); + + migrationBuilder.RenameColumn( + name: "RecipeShoppingListId", + table: "RecipeIngredientsShoppingLists", + newName: "ShoppingListId"); + + migrationBuilder.RenameIndex( + name: "IX_RecipeIngredientsShoppingLists_RecipeShoppingListId", + table: "RecipeIngredientsShoppingLists", + newName: "IX_RecipeIngredientsShoppingLists_ShoppingListId"); + + migrationBuilder.AlterColumn( + name: "CategoryId", + table: "Recipes", + type: "uniqueidentifier", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uniqueidentifier"); + + migrationBuilder.AddForeignKey( + name: "FK_RecipeIngredientsShoppingLists_ShoppingLists_ShoppingListId", + table: "RecipeIngredientsShoppingLists", + column: "ShoppingListId", + principalTable: "ShoppingLists", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Recipes_Categories_CategoryId", + table: "Recipes", + column: "CategoryId", + principalTable: "Categories", + principalColumn: "Id"); + } + } +} diff --git a/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs b/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs index 6b432d8..c27540a 100644 --- a/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs +++ b/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs @@ -1,7 +1,10 @@ // +using System; using Francesco.Recipes.World.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable @@ -124,10 +127,13 @@ namespace Francesco.Recipes.World.Migrations b.Property("IngredientId") .HasColumnType("uniqueidentifier"); + b.Property("IsChecked") + .HasColumnType("bit"); + b.Property("RecipeIngredientId") .HasColumnType("uniqueidentifier"); - b.Property("ShoppingListId") + b.Property("RecipeShoppingListId") .HasColumnType("uniqueidentifier"); b.HasKey("Id"); @@ -136,7 +142,7 @@ namespace Francesco.Recipes.World.Migrations b.HasIndex("RecipeIngredientId"); - b.HasIndex("ShoppingListId"); + b.HasIndex("RecipeShoppingListId"); b.ToTable("RecipeIngredientsShoppingLists"); }); @@ -200,7 +206,7 @@ namespace Francesco.Recipes.World.Migrations .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("CategoryId") + b.Property("CategoryId") .HasColumnType("uniqueidentifier"); b.Property("CookingTime") @@ -272,6 +278,27 @@ namespace Francesco.Recipes.World.Migrations b.ToTable("RecipeIngredients"); }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.Property("ShoppingListId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RecipeId"); + + b.HasIndex("ShoppingListId"); + + b.ToTable("RecipeShoppingLists"); + }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => { b.Property("Id") @@ -388,15 +415,15 @@ namespace Francesco.Recipes.World.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList") - .WithMany("RecipeIngredientShoppingLists") - .HasForeignKey("ShoppingListId") + b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", "RecipeShoppingList") + .WithMany("SelectedIngredients") + .HasForeignKey("RecipeShoppingListId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.Navigation("RecipeIngredient"); - b.Navigation("ShoppingList"); + b.Navigation("RecipeShoppingList"); }); modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => @@ -429,9 +456,11 @@ namespace Francesco.Recipes.World.Migrations modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => { - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", null) + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", "Category") .WithMany("Recipes") - .HasForeignKey("CategoryId"); + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorit") .WithMany("Recipe") @@ -439,6 +468,8 @@ namespace Francesco.Recipes.World.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.Navigation("Category"); + b.Navigation("Favorit"); }); @@ -469,6 +500,25 @@ namespace Francesco.Recipes.World.Migrations b.Navigation("Unit"); }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany() + .HasForeignKey("RecipeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList") + .WithMany("RecipeIngredientShoppingLists") + .HasForeignKey("ShoppingListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Recipe"); + + b.Navigation("ShoppingList"); + }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b => { b.Navigation("Recipes"); @@ -500,6 +550,11 @@ namespace Francesco.Recipes.World.Migrations b.Navigation("RecipeIngredients"); }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => + { + b.Navigation("SelectedIngredients"); + }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => { b.Navigation("RecipeIngredientShoppingLists"); diff --git a/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs b/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs index ee81fd2..5dc055b 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs @@ -1,15 +1,22 @@ -namespace Francesco.Recipes.World.Models.BackendModels.Recipe +using System.ComponentModel.DataAnnotations; + +namespace Francesco.Recipes.World.Models.BackendModels.Recipe { public enum Difficulty { + [Display(Name = "Sehr einfach")] VeryEasy = 0, + [Display(Name = "Einfach")] Easy = 1, + [Display(Name = "Mittel")] Medium = 2, + [Display(Name = "Schwer")] Hard = 3, + [Display(Name = "Experte")] Expert = 4, } } diff --git a/Francesco.Recipes.World/Models/BackendModels/RecipeIngredientShoppingList/RecipeIngredientShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/RecipeIngredientShoppingList/RecipeIngredientShoppingList.cs index 1878caf..fa691b8 100644 --- a/Francesco.Recipes.World/Models/BackendModels/RecipeIngredientShoppingList/RecipeIngredientShoppingList.cs +++ b/Francesco.Recipes.World/Models/BackendModels/RecipeIngredientShoppingList/RecipeIngredientShoppingList.cs @@ -1,14 +1,16 @@ namespace Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList { using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; - using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; + using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList; public class RecipeIngredientShoppingList { public Guid Id { get; set; } - public virtual ShoppingList ShoppingList { get; set; } = new (); + public virtual RecipeShoppingList RecipeShoppingList { get; set; } = new (); public virtual RecipeIngredient RecipeIngredient { get; set; } = new (); + + public bool IsChecked { get; set; } = false; } } diff --git a/Francesco.Recipes.World/Models/BackendModels/RecipeShoppingList/RecipeShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/RecipeShoppingList/RecipeShoppingList.cs new file mode 100644 index 0000000..1173425 --- /dev/null +++ b/Francesco.Recipes.World/Models/BackendModels/RecipeShoppingList/RecipeShoppingList.cs @@ -0,0 +1,17 @@ +namespace Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList +{ + using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; + using Francesco.Recipes.World.Models.BackendModels.Recipe; + using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; + + public class RecipeShoppingList + { + public Guid Id { get; set; } + + public virtual ShoppingList ShoppingList { get; set; } = new ShoppingList(); + + public virtual Recipe Recipe { get; set; } = new (); + + public virtual ICollection SelectedIngredients { get; set; } = new List(); + } +} diff --git a/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs index c8fd12f..1a06a05 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs @@ -1,6 +1,6 @@ namespace Francesco.Recipes.World.Models.BackendModels.Shoppinglist { - using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; + using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList; public class ShoppingList : ITimeStampedEntity { @@ -10,6 +10,6 @@ public DateTime? ModifiedAt { get; set; } - public virtual ICollection RecipeIngredientShoppingLists { get; set; } = new List(); + public virtual ICollection RecipeShoppingList { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Program.cs b/Francesco.Recipes.World/Program.cs index 719acda..6f98ff1 100644 --- a/Francesco.Recipes.World/Program.cs +++ b/Francesco.Recipes.World/Program.cs @@ -1,5 +1,6 @@ using Francesco.Recipes.World.Data; using Francesco.Recipes.World.Repositories.Category; +using Francesco.Recipes.World.Repositories.Favorit; using Francesco.Recipes.World.Repositories.Ingredient; using Francesco.Recipes.World.Repositories.Instruction; using Francesco.Recipes.World.Repositories.MediaFile; @@ -7,8 +8,6 @@ using Francesco.Recipes.World.Repositories.Recipe; using Francesco.Recipes.World.Repositories.ShoppingList; using Francesco.Recipes.World.Repositories.Unit; -using Microsoft.AspNetCore.Identity; - using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); @@ -23,9 +22,6 @@ var connectionString = builder.Configuration.GetConnectionString("FrancescosReci services.AddDbContext(options => options.UseSqlServer(connectionString)); -services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) - .AddEntityFrameworkStores(); - // Add services to the container. builder.Services.AddControllersWithViews(); @@ -43,6 +39,8 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -62,10 +60,6 @@ app.UseStaticFiles(); app.UseRouting(); -app.UseAuthentication(); - -app.UseAuthorization(); - app.MapDefaultControllerRoute(); app.MapControllerRoute( diff --git a/Francesco.Recipes.World/Views/Category/CategoryRecipesViewModel.cs b/Francesco.Recipes.World/Views/Category/CategoryRecipesViewModel.cs new file mode 100644 index 0000000..1559dd8 --- /dev/null +++ b/Francesco.Recipes.World/Views/Category/CategoryRecipesViewModel.cs @@ -0,0 +1,12 @@ +namespace Francesco.Recipes.World.Views.Category +{ + using Francesco.Recipes.World.Models.BackendModels.Category; + using Francesco.Recipes.World.Models.BackendModels.Recipe; + + public class CategoryRecipesViewModel + { + public Category Category { get; set; } = new (); + + public IEnumerable Recipes { get; set; } = new List(); + } +} diff --git a/Francesco.Recipes.World/Views/Recipe/AddInstruction.cshtml b/Francesco.Recipes.World/Views/Recipe/AddInstruction.cshtml new file mode 100644 index 0000000..d6d5f79 --- /dev/null +++ b/Francesco.Recipes.World/Views/Recipe/AddInstruction.cshtml @@ -0,0 +1,59 @@ +@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe + +@{ + ViewData["Title"] = "Add Instructions"; +} + +

Add Instructions to @Model.Name

+ +
+

Existing Instructions

+
    + @foreach (var instruction in Model.Instructions.OrderBy(i => i.Number)) + { +
  • @instruction.Number. @instruction.Description
  • + } +
+
+ +
+

Add New Instruction

+
+ +
+ + +
+
+ + +
+ +
+
+ +@section Scripts { + +} + diff --git a/Francesco.Recipes.World/Views/Recipe/AddOrCreateIngredient.cshtml b/Francesco.Recipes.World/Views/Recipe/AddOrCreateIngredient.cshtml index c72fdcf..1232469 100644 --- a/Francesco.Recipes.World/Views/Recipe/AddOrCreateIngredient.cshtml +++ b/Francesco.Recipes.World/Views/Recipe/AddOrCreateIngredient.cshtml @@ -1,29 +1,27 @@ @{ - ViewData["Title"] = "Add or Create Ingredient to Recipe"; + ViewData["Title"] = "Add or Create Ingredient to Recipe"; }

@ViewData["Title"]

-
- - -
-
- - -
-
- - -
-
- - -
- + +
+ + +
+
+ + +
+
+ + +
+
@section Scripts { - + } + diff --git a/Francesco.Recipes.World/Views/Recipe/CategoryRecipes.cshtml b/Francesco.Recipes.World/Views/Recipe/CategoryRecipes.cshtml new file mode 100644 index 0000000..df06c6e --- /dev/null +++ b/Francesco.Recipes.World/Views/Recipe/CategoryRecipes.cshtml @@ -0,0 +1,98 @@ +@model IEnumerable + +@{ + ViewData["Title"] = "Category Recipes"; +} + +

Category Recipes

+ +@foreach (var categoryRecipes in Model) +{ +
+

@categoryRecipes.Category.Name

+ Rezept erstellen +
+ @foreach (var recipe in categoryRecipes.Recipes) + { +
+
+ @if (recipe.MediaFiles.Any() && recipe.MediaFiles.First().Data != null) + { + var mediaFile = recipe.MediaFiles.First(); + if (mediaFile.Data != null) + { + @recipe.Name + } + } +
+
+

@recipe.Name

+

@recipe.Description

+

Difficulty: @recipe.Difficulty

+

Servings: @recipe.Servings

+

Preparation Time: @recipe.PreparationTime

+

Cooking Time: @recipe.CookingTime

+
+ @if (recipe.IsFavorite) + { +
+ + +
+ } + else + { +
+ + +
+ } +
+
+
+ } + +
+
+} + + + diff --git a/Francesco.Recipes.World/Views/Recipe/Create.cshtml b/Francesco.Recipes.World/Views/Recipe/Create.cshtml index 8b1e201..e8c3769 100644 --- a/Francesco.Recipes.World/Views/Recipe/Create.cshtml +++ b/Francesco.Recipes.World/Views/Recipe/Create.cshtml @@ -1,45 +1,53 @@ @model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe @using Francesco.Recipes.World.Models.BackendModels.Recipe @{ - ViewData["Title"] = "Create Recipe"; + ViewData["Title"] = "Create Recipe"; }

Create Recipe

-
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- -
+ +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
+
@section Scripts { diff --git a/Francesco.Recipes.World/Views/Recipe/Details.cshtml b/Francesco.Recipes.World/Views/Recipe/Details.cshtml new file mode 100644 index 0000000..4ea25c7 --- /dev/null +++ b/Francesco.Recipes.World/Views/Recipe/Details.cshtml @@ -0,0 +1,68 @@ +@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe + +@{ + ViewData["Title"] = "Recipe Details"; +} + +

@Model.Name

+ +
+
+ @if (Model.MediaFiles.Any() && Model.MediaFiles.First().Data != null) + { + var mediaFile = Model.MediaFiles.First(); + if (mediaFile.Data != null) + { + @Model.Name + } + } +
+
+

Description: @Model.Description

+

Difficulty: @Model.Difficulty

+

Servings: @Model.Servings

+

Preparation Time: @Model.PreparationTime

+

Cooking Time: @Model.CookingTime

+
+
+

Ingredients

+
+
    + @foreach (var ingredient in Model.RecipeIngredients) + { +
  • + + @ingredient.Ingredient.Name - @ingredient.Quantity @(ingredient.Unit != null ? ingredient.Unit.Symbol : string.Empty) +
  • + } +
+ +
+
+
+ +@section Scripts { + +} diff --git a/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml b/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml index 01a77cb..b0e499e 100644 --- a/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml +++ b/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml @@ -1,42 +1,72 @@ - @model Francesco.Recipes.World.Views.Recipe.FilterByDifficultyViewModel - @using Francesco.Recipes.World.Models.BackendModels.Recipe +@using Francesco.Recipes.World.Models.BackendModels.Recipe +@model Francesco.Recipes.World.Views.Recipe.FilterByDifficultyViewModel -@{ - ViewData["Title"] = "Filter Recipes by Difficulty"; -} +

Rezepte nach Schwierigkeitsgrad

-

Filter Recipes by Difficulty

+ -
-
- - - -
-
+
+
+
+
+ + +
+
-@if (Model.Recipes != null && Model.Recipes.Any()) -{ -

Filtered Recipes

-
    - @foreach (var recipe in Model.Recipes) - { -
  • @recipe.Name - @recipe.Difficulty.
  • - } -
-} -else -{ -

No recipes found for the selected difficulty.

-} +
+
+ +
+ @if (Model?.Recipes != null && Model.Recipes.Any()) + { +
+ + + + + + + + + + + + + @foreach (var recipe in Model.Recipes) + { + + + + + + + + + } + +
NameBeschreibungSchwierigkeitsgradPortionenZubereitungszeitAktionen
@recipe.Name@(recipe.Description?.Length > 100 ? recipe.Description.Substring(0, 100) + "..." : recipe.Description)@recipe.?Difficulty@recipe.Servings@($"{recipe.PreparationTime.TotalMinutes} Min.") + Details + Bearbeiten +
+
+ } + else + { +
+

Keine Rezepte gefunden.

+
+ } +
@section Scripts { - - @await Html.PartialAsync("_ValidationScriptsPartial") + } diff --git a/Francesco.Recipes.World/Views/Recipe/FilterByDifficultyViewModel.cs b/Francesco.Recipes.World/Views/Recipe/FilterByDifficultyViewModel.cs index 94f71f6..b0543f3 100644 --- a/Francesco.Recipes.World/Views/Recipe/FilterByDifficultyViewModel.cs +++ b/Francesco.Recipes.World/Views/Recipe/FilterByDifficultyViewModel.cs @@ -7,6 +7,6 @@ { public Difficulty? SelectedDifficulty { get; set; } - public List Recipes { get; set; } = new List(); + public IReadOnlyCollection Recipes { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Views/Shared/_AddIngredientsToShoppingListPartial.cshtml b/Francesco.Recipes.World/Views/Shared/_AddIngredientsToShoppingListPartial.cshtml new file mode 100644 index 0000000..565c6f6 --- /dev/null +++ b/Francesco.Recipes.World/Views/Shared/_AddIngredientsToShoppingListPartial.cshtml @@ -0,0 +1,43 @@ +@model IEnumerable + +
+

Zutaten

+
+
    + @foreach (var ingredient in Model) + { +
  • + + @ingredient.Ingredient.Name - @ingredient.Quantity @ingredient.Unit.Symbol +
  • + } +
+ +
+
+ +@section Scripts { + +} diff --git a/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml b/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml new file mode 100644 index 0000000..1efa149 --- /dev/null +++ b/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml @@ -0,0 +1,56 @@ +@model Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList +@using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList +@using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient +@using Francesco.Recipes.World.Models.BackendModels.Recipe + +@{ + ViewData["Title"] = "Einkaufsliste Details"; +} + +

Einkaufsliste Details

+ +@if (TempData["SuccessMessage"] != null) +{ +
+ @TempData["SuccessMessage"] +
+} + +
+

Einkaufsliste

+
+
+
+ ID +
+
+ @Model.Id +
+
+
+ +

Rezepte

+ + + + + + + + + @foreach (var recipeShoppingList in Model.RecipeShoppingList) + { + + + + + } + +
RezeptnameZutaten
@recipeShoppingList.Recipe.Name +
    + @foreach (var ingredient in recipeShoppingList.SelectedIngredients) + { +
  • @ingredient.RecipeIngredient.Ingredient.Name - @ingredient.RecipeIngredient.Quantity @ingredient.RecipeIngredient.Unit.Name
  • + } +
+
From 928743303c13a39ee1120cec3b27f7ad51055ce5 Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 8 Apr 2025 15:23:57 +0200 Subject: [PATCH 032/183] Revert "add CategoryController with endpoints" This reverts commit 36bca4d6584d644a81c2fd755d53ae6bea018419. --- .../Controller/Category/CategoryController.cs | 3 +- .../MediaFile/MediaFileController.cs | 44 +- .../Controller/Recipe/RecipeController.cs | 160 +---- .../ShoppingList/ShoppingListController.cs | 48 +- .../Data/FrancescosRecipesWorldDbContext.cs | 5 +- .../Francesco.Recipes.World.csproj | 10 +- ...120842_UpdateShoppingLIstLogic.Designer.cs | 572 ------------------ .../20250402120842_UpdateShoppingLIstLogic.cs | 162 ----- ...escosRecipesWorldDbContextModelSnapshot.cs | 73 +-- .../Models/BackendModels/Recipe/Difficulty.cs | 9 +- .../RecipeIngredientShoppingList.cs | 6 +- .../RecipeShoppingList/RecipeShoppingList.cs | 17 - .../Shoppinglist/ShoppingList.cs | 4 +- Francesco.Recipes.World/Program.cs | 12 +- .../Category/CategoryRecipesViewModel.cs | 12 - .../Views/Recipe/AddInstruction.cshtml | 59 -- .../Views/Recipe/AddOrCreateIngredient.cshtml | 36 +- .../Views/Recipe/CategoryRecipes.cshtml | 98 --- .../Views/Recipe/Create.cshtml | 78 ++- .../Views/Recipe/Details.cshtml | 68 --- .../Views/Recipe/FilterByDifficulty.cshtml | 100 ++- .../Recipe/FilterByDifficultyViewModel.cs | 2 +- ...AddIngredientsToShoppingListPartial.cshtml | 43 -- .../Views/ShoppingList/Details.cshtml | 56 -- 24 files changed, 142 insertions(+), 1535 deletions(-) delete mode 100644 Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.Designer.cs delete mode 100644 Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.cs delete mode 100644 Francesco.Recipes.World/Models/BackendModels/RecipeShoppingList/RecipeShoppingList.cs delete mode 100644 Francesco.Recipes.World/Views/Category/CategoryRecipesViewModel.cs delete mode 100644 Francesco.Recipes.World/Views/Recipe/AddInstruction.cshtml delete mode 100644 Francesco.Recipes.World/Views/Recipe/CategoryRecipes.cshtml delete mode 100644 Francesco.Recipes.World/Views/Recipe/Details.cshtml delete mode 100644 Francesco.Recipes.World/Views/Shared/_AddIngredientsToShoppingListPartial.cshtml delete mode 100644 Francesco.Recipes.World/Views/ShoppingList/Details.cshtml diff --git a/Francesco.Recipes.World/Controller/Category/CategoryController.cs b/Francesco.Recipes.World/Controller/Category/CategoryController.cs index 29b06b5..0257ab4 100644 --- a/Francesco.Recipes.World/Controller/Category/CategoryController.cs +++ b/Francesco.Recipes.World/Controller/Category/CategoryController.cs @@ -5,7 +5,6 @@ using Francesco.Recipes.World.Repositories.Category; using Microsoft.AspNetCore.Mvc; - [ValidateAntiForgeryToken] [Route("Category")] public class CategoryController : Controller @@ -38,7 +37,7 @@ public async Task>> GetRecipesByCategory(Guid id) { var recipes = await _categoryRepository.GetRecipesByCategoryAsync(id); - return View(recipes); + return Ok(recipes); } } } diff --git a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs index 14f59fe..6cec16d 100644 --- a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs +++ b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs @@ -1,48 +1,6 @@ namespace Francesco.Recipes.World.Controller.MediaFile { - using Francesco.Recipes.World.Data; - using Francesco.Recipes.World.Repositories.MediaFile; - using Microsoft.AspNetCore.Mvc; - - [ValidateAntiForgeryToken] - - [Route("categories/{categoryId}/Recipe")] - public class MediaFileController : Controller + public class MediaFileController { - private readonly IMediaFileRepository _mediaFileRepository; - private readonly FrancescosRecipesWorldDbContext _context; - - public MediaFileController(IMediaFileRepository mediaFileRepository, FrancescosRecipesWorldDbContext context) - { - _mediaFileRepository = mediaFileRepository; - _context = context; - } - - // POST: /UploadImage - [HttpPost("UploadImage")] - public async Task UploadImage(Guid recipeId, IFormFile? mediaFile) - { - if (mediaFile is null) - { - return BadRequest("Photo is required."); - } - - try - { - await _mediaFileRepository.UploadRecipeMediaAsync(recipeId, mediaFile); - return Ok("Image uploaded successfully."); - } - catch (Exception ex) - { - return StatusCode(500, $"Internal server error: {ex.Message}"); - } - } - - // GET: /UploadImage - [HttpGet("UploadImage")] - public IActionResult UploadImageView() - { - return View(); - } } } diff --git a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs index 05d99aa..9b4cd9e 100644 --- a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs +++ b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs @@ -1,94 +1,43 @@ namespace Francesco.Recipes.World.Controller.Recipe { - using System.ComponentModel.DataAnnotations; using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Repositories.Category; - using Francesco.Recipes.World.Repositories.Favorit; using Francesco.Recipes.World.Repositories.Ingredient; - using Francesco.Recipes.World.Repositories.Instruction; - using Francesco.Recipes.World.Repositories.MediaFile; using Francesco.Recipes.World.Repositories.Recipe; using Francesco.Recipes.World.Repositories.Unit; - using Francesco.Recipes.World.Views.Category; - using Francesco.Recipes.World.Views.Recipe; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; - [ValidateAntiForgeryToken] - [Route("Recipe")] + [Route("categories/{categoryId}/Recipe")] public class RecipeController : Controller { private readonly IRecipeRepository _recipeRepository; private readonly IUnitRepository _unitRepository; private readonly ICategoryRepository _categoryRepository; private readonly IIngredientRepository _ingredientRepository; - private readonly IMediaFileRepository _mediaFileRepository; - private readonly IInstructionRepository _instructionRepository; - private readonly IFavoritRepository _favoritRepository; - [Display(Name = "Schwierigkeitsgrad")] - [BindProperty(SupportsGet = true)] - public Difficulty? SelectedDifficulty { get; set; } - - public RecipeController(IRecipeRepository recipeRepository, IUnitRepository unitRepository, ICategoryRepository categoryRepository, IIngredientRepository ingredientRepository, IMediaFileRepository mediaFileRepository, IInstructionRepository instructionRepository, IFavoritRepository favoritRepository) + public RecipeController(IRecipeRepository recipeRepository, IUnitRepository unitRepository, ICategoryRepository categoryRepository, IIngredientRepository ingredientRepository) { _recipeRepository = recipeRepository; _unitRepository = unitRepository; _categoryRepository = categoryRepository; _ingredientRepository = ingredientRepository; - Recipes = new List(); - _mediaFileRepository = mediaFileRepository; - _instructionRepository = instructionRepository; - _favoritRepository = favoritRepository; } public IReadOnlyCollection Recipes { get; set; } - // GET: /Recipe/{recipeId}/AddOrCreateIngredient + // GET: /Recipe/AddOrCreateIngredient [HttpGet("{recipeId}/AddOrCreateIngredient")] - public async Task AddOrCreateIngredient(Guid recipeId) + public async Task AddOrCreateIngredient() { var units = await _unitRepository.GetAllUnitsAsync(); ViewBag.Units = new SelectList(units, "Id", "Name"); - ViewBag.RecipeId = recipeId; return View(); } - // GET: /Recipe/Details/{recipeId} - [HttpGet("Details/{recipeId}")] - public async Task Details(Guid recipeId) - { - var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId); - if (recipe == null) - { - return NotFound("Recipe not found."); - } - - return View(recipe); - } - - // GET: /Recipe/CategoryRecipes - [HttpGet("CategoryRecipes")] - public async Task CategoryRecipes() - { - var categories = await _categoryRepository.GetAllCategoriesAsync(); - var viewModel = new List(); - - foreach (var category in categories) - { - var recipes = await _categoryRepository.GetRecipesByCategoryAsync(category.Id); - viewModel.Add(new CategoryRecipesViewModel - { - Category = category, - Recipes = recipes, - }); - } - - return View(viewModel); - } - - // POST: /Recipe/{recipeId}/AddOrCreateIngredient + // POST: /Recipe/AddOrCreateIngredient [HttpPost("{recipeId}/AddOrCreateIngredient")] + [ValidateAntiForgeryToken] public async Task AddOrCreateIngredient(Guid recipeId, string ingredientName, int quantity, Guid unitId) { if (quantity <= 0) @@ -107,8 +56,8 @@ return RedirectToAction("Details", new { id = recipeId }); } - // GET: /Recipe/Create/{categoryId} - [HttpGet("Create/{categoryId}")] + // GET: /categories/{categoryId}/Recipe/Create + [HttpGet("Create")] public async Task Create(Guid categoryId) { var category = await _categoryRepository.GetCategoryByIdAsync(categoryId); @@ -121,9 +70,10 @@ return View(); } - // POST: /Recipe/Create/{categoryId} - [HttpPost("Create/{categoryId}")] - public async Task Create(Guid categoryId, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime, IFormFile? photo) + // POST: /categories/{categoryId}/Recipe/Create + [HttpPost("Create")] + [ValidateAntiForgeryToken] + public async Task Create(Guid categoryId, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime) { if (string.IsNullOrWhiteSpace(name)) { @@ -153,18 +103,13 @@ return NotFound($"Kategorie mit ID {categoryId} wurde nicht gefunden."); } - var recipe = await _recipeRepository.CreateRecipeForCategoryAsync(categoryEntity, name, description, difficulty, servings, preparationTime, cookingTime); - if (photo != null) - { - await _mediaFileRepository.UploadRecipeMediaAsync(recipe.Id, photo); - } - + await _recipeRepository.CreateRecipeForCategoryAsync(categoryEntity, name, description, difficulty, servings, preparationTime, cookingTime); return RedirectToAction("Details", "Category", new { id = categoryId }); } - // GET: /Recipe/{recipeId}/RemoveIngredient/{ingredientId} + // GET: /categories/{categoryId}/Recipe/{recipeId}/RemoveIngredient/{ingredientId} [HttpGet("{recipeId}/RemoveIngredient/{ingredientId}")] - public async Task RemoveIngredient(Guid recipeId, Guid ingredientId) + public async Task RemoveIngredient(Guid categoryId, Guid recipeId, Guid ingredientId) { var recipe = await _recipeRepository.GetRecipeAsync(recipeId); var ingredient = await _ingredientRepository.GetIngredientByIdAsync(ingredientId); @@ -176,87 +121,28 @@ ViewBag.RecipeId = recipeId; ViewBag.IngredientId = ingredientId; + ViewBag.CategoryId = categoryId; ViewBag.IngredientName = ingredient.Name; return View(); } - // POST: /Recipe/{recipeId}/RemoveIngredient/{ingredientId} + // POST: /categories/{categoryId}/Recipe/{recipeId}/RemoveIngredient/{ingredientId} [HttpPost("{recipeId}/RemoveIngredient/{ingredientId}")] - public async Task RemoveIngredientConfirmed(Guid recipeId, Guid ingredientId) + [ValidateAntiForgeryToken] + public async Task RemoveIngredientConfirmed(Guid categoryId, Guid recipeId, Guid ingredientId) { await _recipeRepository.RemoveIngredientFromRecipeAsync(recipeId, ingredientId); TempData["SuccessMessage"] = "Ingredient removed successfully."; return RedirectToAction("Details", new { id = recipeId }); } - // GET: /Recipe/FilterByDifficulty + // GET: /categories/{categoryId}/Recipe/FilterByDifficulty [HttpGet("FilterByDifficulty")] - public async Task FilterByDifficulty(Difficulty? selectedDifficulty) + public async Task FilterByDifficulty(Difficulty? difficulty) { - var recipes = await _recipeRepository.GetRecipesByDifficultyAsync(selectedDifficulty); - var viewModel = new FilterByDifficultyViewModel - { - SelectedDifficulty = selectedDifficulty, - Recipes = recipes, - }; - return View(viewModel); - } - - // GET: /Recipe/{recipeId}/AddInstruction - [HttpGet("{recipeId}/AddInstruction")] - public async Task AddInstruction(Guid recipeId) - { - var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId); - if (recipe == null) - { - return NotFound("Recipe not found."); - } - - ViewBag.RecipeId = recipeId; - return View(recipe); - } - - // POST: /Recipe/{recipeId}/AddInstruction - [HttpPost("{recipeId}/AddInstruction")] - public async Task AddInstruction(Guid recipeId, string description, int number) - { - try - { - await _instructionRepository.CreateInstructionToRecipeAsync(recipeId, description, number); - return RedirectToAction("AddInstruction", new { recipeId }); - } - catch (Exception ex) - { - ModelState.AddModelError(string.Empty, ex.Message); - var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId); - ViewBag.RecipeId = recipeId; - return View(recipe); - } - } - - // GET: /Recipe/Favorites - [HttpGet("Favorites")] - public async Task Favorites() - { - var favoriteRecipes = await _favoritRepository.GetFavoriteRecipesAsync(); - return View(favoriteRecipes); - } - - // POST: /Recipe/AddFavorite - [HttpPost("AddFavorite")] - public async Task AddFavorite(Guid recipeId) - { - await _favoritRepository.AddFavoriteAsync(recipeId); - return RedirectToAction("Details", new { recipeId }); - } - - // POST: /Recipe/RemoveFavorite - [HttpPost("RemoveFavorite")] - public async Task RemoveFavorite(Guid recipeId) - { - await _favoritRepository.RemoveFavoriteAsync(recipeId); - return RedirectToAction("Details", new { recipeId }); + Recipes = await _recipeRepository.GetRecipesByDifficultyAsync(difficulty); + return View(Recipes); } } } diff --git a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs index 7f6a434..38fdf4b 100644 --- a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs +++ b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs @@ -1,50 +1,6 @@ -namespace Francesco.Recipes.World.Controllers +namespace Francesco.Recipes.World.Controller.ShoppingList { - using Francesco.Recipes.World.Data; - using Francesco.Recipes.World.Repositories.ShoppingList; - using Microsoft.AspNetCore.Mvc; - using Microsoft.EntityFrameworkCore; - - [Route("ShoppingList")] - public class ShoppingListController : Controller + public class ShoppingListController { - private readonly IShoppingListRepository _shoppingListRepository; - private readonly FrancescosRecipesWorldDbContext _context; - - public ShoppingListController(IShoppingListRepository shoppingListRepository, FrancescosRecipesWorldDbContext context) - { - _shoppingListRepository = shoppingListRepository; - _context = context; - } - - [HttpPost("CreateOrAddIngredients")] - public async Task CreateOrAddIngredients([FromBody] CreateOrAddIngredientsRequest request) - { - if (request == null || request.IngredientIds == null || !request.IngredientIds.Any()) - { - return BadRequest("No ingredients provided."); - } - - await _shoppingListRepository.AddIngredientsToShoppingListAsync(request.RecipeId, request.IngredientIds); - - var shoppingList = await _context.ShoppingLists - .Include(sl => sl.RecipeShoppingList) - .ThenInclude(rsl => rsl.Recipe) - .FirstOrDefaultAsync(sl => sl.RecipeShoppingList.Any(rsl => rsl.Recipe.Id == request.RecipeId)); - - if (shoppingList == null) - { - return BadRequest("Error creating shopping list."); - } - - return Json(new { shoppingListId = shoppingList.Id }); - } - - public class CreateOrAddIngredientsRequest - { - public Guid RecipeId { get; set; } - - public List IngredientIds { get; set; } = new (); - } } } diff --git a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs index 7a8a5e7..4fd6bd8 100644 --- a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs +++ b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs @@ -9,8 +9,7 @@ using Francesco.Recipes.World.Models.BackendModels.MediaFile; using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; -using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList; -using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; + using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; using Francesco.Recipes.World.Models.BackendModels.Unit; using Microsoft.EntityFrameworkCore; @@ -37,8 +36,6 @@ using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; public DbSet RecipeIngredientsShoppingLists => Set(); - public DbSet RecipeShoppingLists => Set(); - public DbSet ShoppingLists => Set(); public DbSet MediaFiles => Set(); diff --git a/Francesco.Recipes.World/Francesco.Recipes.World.csproj b/Francesco.Recipes.World/Francesco.Recipes.World.csproj index 7d430a2..549a91d 100644 --- a/Francesco.Recipes.World/Francesco.Recipes.World.csproj +++ b/Francesco.Recipes.World/Francesco.Recipes.World.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -45,13 +45,5 @@ - - - - - - - - diff --git a/Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.Designer.cs b/Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.Designer.cs deleted file mode 100644 index d99ae26..0000000 --- a/Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.Designer.cs +++ /dev/null @@ -1,572 +0,0 @@ -// - -using Francesco.Recipes.World.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; - - -#nullable disable - -namespace Francesco.Recipes.World.Migrations -{ - [DbContext(typeof(FrancescosRecipesWorldDbContext))] - [Migration("20250402120842_UpdateShoppingLIstLogic")] - partial class UpdateShoppingLIstLogic - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = new Guid("b248244f-f21c-4555-a14d-5dd49a2717cf"), - Name = "Vorspeisen & Snacks" - }, - new - { - Id = new Guid("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"), - Name = "Erste Gänge" - }, - new - { - Id = new Guid("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"), - Name = "Hauptgerichte" - }, - new - { - Id = new Guid("90deec39-dcd0-422d-9018-ac8389e332e1"), - Name = "Desserts & Süßspeisen" - }, - new - { - Id = new Guid("28e39168-701a-4084-81da-d96c987c462f"), - Name = "Beilagen & Salate" - }, - new - { - Id = new Guid("20585b74-4805-4aff-a6df-aa6b7af04ff1"), - Name = "Kuchen" - }, - new - { - Id = new Guid("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"), - Name = "Hefegebäck & Brot" - }, - new - { - Id = new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"), - Name = "Soßen & Saucen" - }, - new - { - Id = new Guid("23b1c740-e427-44f9-a6ea-d33d3f30f05a"), - Name = "Marmeladen & Eingemachtes" - }, - new - { - Id = new Guid("0a91a200-dc76-4e00-b38c-b38cab5b69d7"), - Name = "Getränke" - }); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.ToTable("Favorits"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Ingredients"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("IngredientId") - .HasColumnType("uniqueidentifier"); - - b.Property("IsChecked") - .HasColumnType("bit"); - - b.Property("RecipeIngredientId") - .HasColumnType("uniqueidentifier"); - - b.Property("RecipeShoppingListId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("IngredientId"); - - b.HasIndex("RecipeIngredientId"); - - b.HasIndex("RecipeShoppingListId"); - - b.ToTable("RecipeIngredientsShoppingLists"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Description") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Number") - .HasColumnType("int"); - - b.Property("RecipeId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("RecipeId"); - - b.ToTable("Instructions"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Data") - .HasColumnType("varbinary(max)"); - - b.Property("FileName") - .HasColumnType("nvarchar(max)"); - - b.Property("InstructionId") - .HasColumnType("uniqueidentifier"); - - b.Property("MimeType") - .HasColumnType("nvarchar(max)"); - - b.Property("RecipeId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("InstructionId"); - - b.HasIndex("RecipeId"); - - b.ToTable("MediaFiles"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("CategoryId") - .HasColumnType("uniqueidentifier"); - - b.Property("CookingTime") - .HasColumnType("time"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Difficulty") - .HasColumnType("int"); - - b.Property("FavoritId") - .HasColumnType("uniqueidentifier"); - - b.Property("IsFavorite") - .HasColumnType("bit"); - - b.Property("ModifiedAt") - .HasColumnType("datetime2"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("PreparationTime") - .HasColumnType("time"); - - b.Property("Servings") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("FavoritId"); - - b.ToTable("Recipes"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("IngredientId") - .HasColumnType("uniqueidentifier"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("RecipeId") - .HasColumnType("uniqueidentifier"); - - b.Property("UnitId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("IngredientId"); - - b.HasIndex("RecipeId"); - - b.HasIndex("UnitId"); - - b.ToTable("RecipeIngredients"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("RecipeId") - .HasColumnType("uniqueidentifier"); - - b.Property("ShoppingListId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("RecipeId"); - - b.HasIndex("ShoppingListId"); - - b.ToTable("RecipeShoppingLists"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("ModifiedAt") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.ToTable("ShoppingLists"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Symbol") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Units"); - - b.HasData( - new - { - Id = new Guid("62eb2c11-8768-46fb-9db0-c77f940bb4aa"), - Name = "liter", - Symbol = "l" - }, - new - { - Id = new Guid("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"), - Name = "gramm", - Symbol = "g" - }, - new - { - Id = new Guid("7e3d1b86-ac48-45d6-814e-d3492a86db1d"), - Name = "kilogramm", - Symbol = "kg" - }, - new - { - Id = new Guid("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"), - Name = "stücke", - Symbol = "stk" - }, - new - { - Id = new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"), - Name = "blatt", - Symbol = "blatt" - }, - new - { - Id = new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"), - Name = "messerspitze", - Symbol = "msp" - }, - new - { - Id = new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"), - Name = "stange", - Symbol = "stange" - }, - new - { - Id = new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"), - Name = "bund", - Symbol = "bund" - }, - new - { - Id = new Guid("66556e0e-eb2b-4bc3-9a56-135dd508ed09"), - Name = "zehe", - Symbol = "zehe" - }, - new - { - Id = new Guid("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"), - Name = "teelöffel", - Symbol = "TL" - }, - new - { - Id = new Guid("24a91b89-3389-4465-89e4-2f70d2ea6fd7"), - Name = "esslöffel", - Symbol = "EL" - }); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b => - { - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", null) - .WithMany("IngredientShoppingLists") - .HasForeignKey("IngredientId"); - - b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", "RecipeIngredient") - .WithMany() - .HasForeignKey("RecipeIngredientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", "RecipeShoppingList") - .WithMany("SelectedIngredients") - .HasForeignKey("RecipeShoppingListId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("RecipeIngredient"); - - b.Navigation("RecipeShoppingList"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => - { - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") - .WithMany("Instructions") - .HasForeignKey("RecipeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Recipe"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b => - { - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", "Instruction") - .WithMany("MediaFiles") - .HasForeignKey("InstructionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") - .WithMany("MediaFiles") - .HasForeignKey("RecipeId"); - - b.Navigation("Instruction"); - - b.Navigation("Recipe"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => - { - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", "Category") - .WithMany("Recipes") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorit") - .WithMany("Recipe") - .HasForeignKey("FavoritId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Favorit"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b => - { - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient") - .WithMany("RecipeIngredients") - .HasForeignKey("IngredientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") - .WithMany("RecipeIngredients") - .HasForeignKey("RecipeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit") - .WithMany("RecipeIngredient") - .HasForeignKey("UnitId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ingredient"); - - b.Navigation("Recipe"); - - b.Navigation("Unit"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => - { - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") - .WithMany() - .HasForeignKey("RecipeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList") - .WithMany("RecipeIngredientShoppingLists") - .HasForeignKey("ShoppingListId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Recipe"); - - b.Navigation("ShoppingList"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b => - { - b.Navigation("Recipes"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b => - { - b.Navigation("Recipe"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b => - { - b.Navigation("IngredientShoppingLists"); - - b.Navigation("RecipeIngredients"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => - { - b.Navigation("MediaFiles"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => - { - b.Navigation("Instructions"); - - b.Navigation("MediaFiles"); - - b.Navigation("RecipeIngredients"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => - { - b.Navigation("SelectedIngredients"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => - { - b.Navigation("RecipeIngredientShoppingLists"); - }); - - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b => - { - b.Navigation("RecipeIngredient"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.cs b/Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.cs deleted file mode 100644 index 5caf69f..0000000 --- a/Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.cs +++ /dev/null @@ -1,162 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Francesco.Recipes.World.Migrations -{ - /// - public partial class UpdateShoppingLIstLogic : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - if (migrationBuilder is null) - { - throw new ArgumentNullException(nameof(migrationBuilder)); - } - - migrationBuilder.DropForeignKey( - name: "FK_RecipeIngredientsShoppingLists_ShoppingLists_ShoppingListId", - table: "RecipeIngredientsShoppingLists"); - - migrationBuilder.DropForeignKey( - name: "FK_Recipes_Categories_CategoryId", - table: "Recipes"); - - migrationBuilder.RenameColumn( - name: "ShoppingListId", - table: "RecipeIngredientsShoppingLists", - newName: "RecipeShoppingListId"); - - migrationBuilder.RenameIndex( - name: "IX_RecipeIngredientsShoppingLists_ShoppingListId", - table: "RecipeIngredientsShoppingLists", - newName: "IX_RecipeIngredientsShoppingLists_RecipeShoppingListId"); - - migrationBuilder.AlterColumn( - name: "CategoryId", - table: "Recipes", - type: "uniqueidentifier", - nullable: false, - defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), - oldClrType: typeof(Guid), - oldType: "uniqueidentifier", - oldNullable: true); - - migrationBuilder.AddColumn( - name: "IsChecked", - table: "RecipeIngredientsShoppingLists", - type: "bit", - nullable: false, - defaultValue: false); - - migrationBuilder.CreateTable( - name: "RecipeShoppingLists", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - ShoppingListId = table.Column(type: "uniqueidentifier", nullable: false), - RecipeId = table.Column(type: "uniqueidentifier", nullable: false), - }, - constraints: table => - { - table.PrimaryKey("PK_RecipeShoppingLists", x => x.Id); - table.ForeignKey( - name: "FK_RecipeShoppingLists_Recipes_RecipeId", - column: x => x.RecipeId, - principalTable: "Recipes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_RecipeShoppingLists_ShoppingLists_ShoppingListId", - column: x => x.ShoppingListId, - principalTable: "ShoppingLists", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_RecipeShoppingLists_RecipeId", - table: "RecipeShoppingLists", - column: "RecipeId"); - - migrationBuilder.CreateIndex( - name: "IX_RecipeShoppingLists_ShoppingListId", - table: "RecipeShoppingLists", - column: "ShoppingListId"); - - migrationBuilder.AddForeignKey( - name: "FK_RecipeIngredientsShoppingLists_RecipeShoppingLists_RecipeShoppingListId", - table: "RecipeIngredientsShoppingLists", - column: "RecipeShoppingListId", - principalTable: "RecipeShoppingLists", - principalColumn: "Id", - onDelete: ReferentialAction.NoAction); - - migrationBuilder.AddForeignKey( - name: "FK_Recipes_Categories_CategoryId", - table: "Recipes", - column: "CategoryId", - principalTable: "Categories", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - if (migrationBuilder is null) - { - throw new ArgumentNullException(nameof(migrationBuilder)); - } - - migrationBuilder.DropForeignKey( - name: "FK_RecipeIngredientsShoppingLists_RecipeShoppingLists_RecipeShoppingListId", - table: "RecipeIngredientsShoppingLists"); - - migrationBuilder.DropForeignKey( - name: "FK_Recipes_Categories_CategoryId", - table: "Recipes"); - - migrationBuilder.DropTable( - name: "RecipeShoppingLists"); - - migrationBuilder.DropColumn( - name: "IsChecked", - table: "RecipeIngredientsShoppingLists"); - - migrationBuilder.RenameColumn( - name: "RecipeShoppingListId", - table: "RecipeIngredientsShoppingLists", - newName: "ShoppingListId"); - - migrationBuilder.RenameIndex( - name: "IX_RecipeIngredientsShoppingLists_RecipeShoppingListId", - table: "RecipeIngredientsShoppingLists", - newName: "IX_RecipeIngredientsShoppingLists_ShoppingListId"); - - migrationBuilder.AlterColumn( - name: "CategoryId", - table: "Recipes", - type: "uniqueidentifier", - nullable: true, - oldClrType: typeof(Guid), - oldType: "uniqueidentifier"); - - migrationBuilder.AddForeignKey( - name: "FK_RecipeIngredientsShoppingLists_ShoppingLists_ShoppingListId", - table: "RecipeIngredientsShoppingLists", - column: "ShoppingListId", - principalTable: "ShoppingLists", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_Recipes_Categories_CategoryId", - table: "Recipes", - column: "CategoryId", - principalTable: "Categories", - principalColumn: "Id"); - } - } -} diff --git a/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs b/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs index c27540a..6b432d8 100644 --- a/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs +++ b/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs @@ -1,10 +1,7 @@ // -using System; using Francesco.Recipes.World.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable @@ -127,13 +124,10 @@ namespace Francesco.Recipes.World.Migrations b.Property("IngredientId") .HasColumnType("uniqueidentifier"); - b.Property("IsChecked") - .HasColumnType("bit"); - b.Property("RecipeIngredientId") .HasColumnType("uniqueidentifier"); - b.Property("RecipeShoppingListId") + b.Property("ShoppingListId") .HasColumnType("uniqueidentifier"); b.HasKey("Id"); @@ -142,7 +136,7 @@ namespace Francesco.Recipes.World.Migrations b.HasIndex("RecipeIngredientId"); - b.HasIndex("RecipeShoppingListId"); + b.HasIndex("ShoppingListId"); b.ToTable("RecipeIngredientsShoppingLists"); }); @@ -206,7 +200,7 @@ namespace Francesco.Recipes.World.Migrations .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("CategoryId") + b.Property("CategoryId") .HasColumnType("uniqueidentifier"); b.Property("CookingTime") @@ -278,27 +272,6 @@ namespace Francesco.Recipes.World.Migrations b.ToTable("RecipeIngredients"); }); - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("RecipeId") - .HasColumnType("uniqueidentifier"); - - b.Property("ShoppingListId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("RecipeId"); - - b.HasIndex("ShoppingListId"); - - b.ToTable("RecipeShoppingLists"); - }); - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => { b.Property("Id") @@ -415,15 +388,15 @@ namespace Francesco.Recipes.World.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", "RecipeShoppingList") - .WithMany("SelectedIngredients") - .HasForeignKey("RecipeShoppingListId") + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList") + .WithMany("RecipeIngredientShoppingLists") + .HasForeignKey("ShoppingListId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.Navigation("RecipeIngredient"); - b.Navigation("RecipeShoppingList"); + b.Navigation("ShoppingList"); }); modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => @@ -456,11 +429,9 @@ namespace Francesco.Recipes.World.Migrations modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => { - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", "Category") + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", null) .WithMany("Recipes") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("CategoryId"); b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorit") .WithMany("Recipe") @@ -468,8 +439,6 @@ namespace Francesco.Recipes.World.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("Category"); - b.Navigation("Favorit"); }); @@ -500,25 +469,6 @@ namespace Francesco.Recipes.World.Migrations b.Navigation("Unit"); }); - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => - { - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") - .WithMany() - .HasForeignKey("RecipeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList") - .WithMany("RecipeIngredientShoppingLists") - .HasForeignKey("ShoppingListId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Recipe"); - - b.Navigation("ShoppingList"); - }); - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b => { b.Navigation("Recipes"); @@ -550,11 +500,6 @@ namespace Francesco.Recipes.World.Migrations b.Navigation("RecipeIngredients"); }); - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => - { - b.Navigation("SelectedIngredients"); - }); - modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => { b.Navigation("RecipeIngredientShoppingLists"); diff --git a/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs b/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs index 5dc055b..ee81fd2 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs @@ -1,22 +1,15 @@ -using System.ComponentModel.DataAnnotations; - -namespace Francesco.Recipes.World.Models.BackendModels.Recipe +namespace Francesco.Recipes.World.Models.BackendModels.Recipe { public enum Difficulty { - [Display(Name = "Sehr einfach")] VeryEasy = 0, - [Display(Name = "Einfach")] Easy = 1, - [Display(Name = "Mittel")] Medium = 2, - [Display(Name = "Schwer")] Hard = 3, - [Display(Name = "Experte")] Expert = 4, } } diff --git a/Francesco.Recipes.World/Models/BackendModels/RecipeIngredientShoppingList/RecipeIngredientShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/RecipeIngredientShoppingList/RecipeIngredientShoppingList.cs index fa691b8..1878caf 100644 --- a/Francesco.Recipes.World/Models/BackendModels/RecipeIngredientShoppingList/RecipeIngredientShoppingList.cs +++ b/Francesco.Recipes.World/Models/BackendModels/RecipeIngredientShoppingList/RecipeIngredientShoppingList.cs @@ -1,16 +1,14 @@ namespace Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList { using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; - using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList; + using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; public class RecipeIngredientShoppingList { public Guid Id { get; set; } - public virtual RecipeShoppingList RecipeShoppingList { get; set; } = new (); + public virtual ShoppingList ShoppingList { get; set; } = new (); public virtual RecipeIngredient RecipeIngredient { get; set; } = new (); - - public bool IsChecked { get; set; } = false; } } diff --git a/Francesco.Recipes.World/Models/BackendModels/RecipeShoppingList/RecipeShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/RecipeShoppingList/RecipeShoppingList.cs deleted file mode 100644 index 1173425..0000000 --- a/Francesco.Recipes.World/Models/BackendModels/RecipeShoppingList/RecipeShoppingList.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList -{ - using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; - using Francesco.Recipes.World.Models.BackendModels.Recipe; - using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; - - public class RecipeShoppingList - { - public Guid Id { get; set; } - - public virtual ShoppingList ShoppingList { get; set; } = new ShoppingList(); - - public virtual Recipe Recipe { get; set; } = new (); - - public virtual ICollection SelectedIngredients { get; set; } = new List(); - } -} diff --git a/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs index 1a06a05..c8fd12f 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs @@ -1,6 +1,6 @@ namespace Francesco.Recipes.World.Models.BackendModels.Shoppinglist { - using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList; + using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; public class ShoppingList : ITimeStampedEntity { @@ -10,6 +10,6 @@ public DateTime? ModifiedAt { get; set; } - public virtual ICollection RecipeShoppingList { get; set; } = new List(); + public virtual ICollection RecipeIngredientShoppingLists { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Program.cs b/Francesco.Recipes.World/Program.cs index 6f98ff1..719acda 100644 --- a/Francesco.Recipes.World/Program.cs +++ b/Francesco.Recipes.World/Program.cs @@ -1,6 +1,5 @@ using Francesco.Recipes.World.Data; using Francesco.Recipes.World.Repositories.Category; -using Francesco.Recipes.World.Repositories.Favorit; using Francesco.Recipes.World.Repositories.Ingredient; using Francesco.Recipes.World.Repositories.Instruction; using Francesco.Recipes.World.Repositories.MediaFile; @@ -8,6 +7,8 @@ using Francesco.Recipes.World.Repositories.Recipe; using Francesco.Recipes.World.Repositories.ShoppingList; using Francesco.Recipes.World.Repositories.Unit; +using Microsoft.AspNetCore.Identity; + using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); @@ -22,6 +23,9 @@ var connectionString = builder.Configuration.GetConnectionString("FrancescosReci services.AddDbContext(options => options.UseSqlServer(connectionString)); +services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) + .AddEntityFrameworkStores(); + // Add services to the container. builder.Services.AddControllersWithViews(); @@ -39,8 +43,6 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); - var app = builder.Build(); // Configure the HTTP request pipeline. @@ -60,6 +62,10 @@ app.UseStaticFiles(); app.UseRouting(); +app.UseAuthentication(); + +app.UseAuthorization(); + app.MapDefaultControllerRoute(); app.MapControllerRoute( diff --git a/Francesco.Recipes.World/Views/Category/CategoryRecipesViewModel.cs b/Francesco.Recipes.World/Views/Category/CategoryRecipesViewModel.cs deleted file mode 100644 index 1559dd8..0000000 --- a/Francesco.Recipes.World/Views/Category/CategoryRecipesViewModel.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Francesco.Recipes.World.Views.Category -{ - using Francesco.Recipes.World.Models.BackendModels.Category; - using Francesco.Recipes.World.Models.BackendModels.Recipe; - - public class CategoryRecipesViewModel - { - public Category Category { get; set; } = new (); - - public IEnumerable Recipes { get; set; } = new List(); - } -} diff --git a/Francesco.Recipes.World/Views/Recipe/AddInstruction.cshtml b/Francesco.Recipes.World/Views/Recipe/AddInstruction.cshtml deleted file mode 100644 index d6d5f79..0000000 --- a/Francesco.Recipes.World/Views/Recipe/AddInstruction.cshtml +++ /dev/null @@ -1,59 +0,0 @@ -@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe - -@{ - ViewData["Title"] = "Add Instructions"; -} - -

Add Instructions to @Model.Name

- -
-

Existing Instructions

-
    - @foreach (var instruction in Model.Instructions.OrderBy(i => i.Number)) - { -
  • @instruction.Number. @instruction.Description
  • - } -
-
- -
-

Add New Instruction

-
- -
- - -
-
- - -
- -
-
- -@section Scripts { - -} - diff --git a/Francesco.Recipes.World/Views/Recipe/AddOrCreateIngredient.cshtml b/Francesco.Recipes.World/Views/Recipe/AddOrCreateIngredient.cshtml index 1232469..c72fdcf 100644 --- a/Francesco.Recipes.World/Views/Recipe/AddOrCreateIngredient.cshtml +++ b/Francesco.Recipes.World/Views/Recipe/AddOrCreateIngredient.cshtml @@ -1,27 +1,29 @@ @{ - ViewData["Title"] = "Add or Create Ingredient to Recipe"; + ViewData["Title"] = "Add or Create Ingredient to Recipe"; }

@ViewData["Title"]

- -
- - -
-
- - -
-
- - -
- +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
@section Scripts { - + } - diff --git a/Francesco.Recipes.World/Views/Recipe/CategoryRecipes.cshtml b/Francesco.Recipes.World/Views/Recipe/CategoryRecipes.cshtml deleted file mode 100644 index df06c6e..0000000 --- a/Francesco.Recipes.World/Views/Recipe/CategoryRecipes.cshtml +++ /dev/null @@ -1,98 +0,0 @@ -@model IEnumerable - -@{ - ViewData["Title"] = "Category Recipes"; -} - -

Category Recipes

- -@foreach (var categoryRecipes in Model) -{ -
-

@categoryRecipes.Category.Name

- Rezept erstellen -
- @foreach (var recipe in categoryRecipes.Recipes) - { -
-
- @if (recipe.MediaFiles.Any() && recipe.MediaFiles.First().Data != null) - { - var mediaFile = recipe.MediaFiles.First(); - if (mediaFile.Data != null) - { - @recipe.Name - } - } -
-
-

@recipe.Name

-

@recipe.Description

-

Difficulty: @recipe.Difficulty

-

Servings: @recipe.Servings

-

Preparation Time: @recipe.PreparationTime

-

Cooking Time: @recipe.CookingTime

-
- @if (recipe.IsFavorite) - { -
- - -
- } - else - { -
- - -
- } -
-
-
- } - -
-
-} - - - diff --git a/Francesco.Recipes.World/Views/Recipe/Create.cshtml b/Francesco.Recipes.World/Views/Recipe/Create.cshtml index e8c3769..8b1e201 100644 --- a/Francesco.Recipes.World/Views/Recipe/Create.cshtml +++ b/Francesco.Recipes.World/Views/Recipe/Create.cshtml @@ -1,53 +1,45 @@ @model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe @using Francesco.Recipes.World.Models.BackendModels.Recipe @{ - ViewData["Title"] = "Create Recipe"; + ViewData["Title"] = "Create Recipe"; }

Create Recipe

-
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - -
- + +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ +
@section Scripts { diff --git a/Francesco.Recipes.World/Views/Recipe/Details.cshtml b/Francesco.Recipes.World/Views/Recipe/Details.cshtml deleted file mode 100644 index 4ea25c7..0000000 --- a/Francesco.Recipes.World/Views/Recipe/Details.cshtml +++ /dev/null @@ -1,68 +0,0 @@ -@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe - -@{ - ViewData["Title"] = "Recipe Details"; -} - -

@Model.Name

- -
-
- @if (Model.MediaFiles.Any() && Model.MediaFiles.First().Data != null) - { - var mediaFile = Model.MediaFiles.First(); - if (mediaFile.Data != null) - { - @Model.Name - } - } -
-
-

Description: @Model.Description

-

Difficulty: @Model.Difficulty

-

Servings: @Model.Servings

-

Preparation Time: @Model.PreparationTime

-

Cooking Time: @Model.CookingTime

-
-
-

Ingredients

-
-
    - @foreach (var ingredient in Model.RecipeIngredients) - { -
  • - - @ingredient.Ingredient.Name - @ingredient.Quantity @(ingredient.Unit != null ? ingredient.Unit.Symbol : string.Empty) -
  • - } -
- -
-
-
- -@section Scripts { - -} diff --git a/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml b/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml index b0e499e..01a77cb 100644 --- a/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml +++ b/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml @@ -1,72 +1,42 @@ -@using Francesco.Recipes.World.Models.BackendModels.Recipe -@model Francesco.Recipes.World.Views.Recipe.FilterByDifficultyViewModel + @model Francesco.Recipes.World.Views.Recipe.FilterByDifficultyViewModel + @using Francesco.Recipes.World.Models.BackendModels.Recipe -

Rezepte nach Schwierigkeitsgrad

+@{ + ViewData["Title"] = "Filter Recipes by Difficulty"; +} - +

Filter Recipes by Difficulty

-
-
-
-
- - -
-
+
+
+ + + +
+
-
-
- -
- @if (Model?.Recipes != null && Model.Recipes.Any()) - { -
- - - - - - - - - - - - - @foreach (var recipe in Model.Recipes) - { - - - - - - - - - } - -
NameBeschreibungSchwierigkeitsgradPortionenZubereitungszeitAktionen
@recipe.Name@(recipe.Description?.Length > 100 ? recipe.Description.Substring(0, 100) + "..." : recipe.Description)@recipe.?Difficulty@recipe.Servings@($"{recipe.PreparationTime.TotalMinutes} Min.") - Details - Bearbeiten -
-
- } - else - { -
-

Keine Rezepte gefunden.

-
- } -
+@if (Model.Recipes != null && Model.Recipes.Any()) +{ +

Filtered Recipes

+
    + @foreach (var recipe in Model.Recipes) + { +
  • @recipe.Name - @recipe.Difficulty.
  • + } +
+} +else +{ +

No recipes found for the selected difficulty.

+} @section Scripts { - + + @await Html.PartialAsync("_ValidationScriptsPartial") } diff --git a/Francesco.Recipes.World/Views/Recipe/FilterByDifficultyViewModel.cs b/Francesco.Recipes.World/Views/Recipe/FilterByDifficultyViewModel.cs index b0543f3..94f71f6 100644 --- a/Francesco.Recipes.World/Views/Recipe/FilterByDifficultyViewModel.cs +++ b/Francesco.Recipes.World/Views/Recipe/FilterByDifficultyViewModel.cs @@ -7,6 +7,6 @@ { public Difficulty? SelectedDifficulty { get; set; } - public IReadOnlyCollection Recipes { get; set; } = new List(); + public List Recipes { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Views/Shared/_AddIngredientsToShoppingListPartial.cshtml b/Francesco.Recipes.World/Views/Shared/_AddIngredientsToShoppingListPartial.cshtml deleted file mode 100644 index 565c6f6..0000000 --- a/Francesco.Recipes.World/Views/Shared/_AddIngredientsToShoppingListPartial.cshtml +++ /dev/null @@ -1,43 +0,0 @@ -@model IEnumerable - -
-

Zutaten

-
-
    - @foreach (var ingredient in Model) - { -
  • - - @ingredient.Ingredient.Name - @ingredient.Quantity @ingredient.Unit.Symbol -
  • - } -
- -
-
- -@section Scripts { - -} diff --git a/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml b/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml deleted file mode 100644 index 1efa149..0000000 --- a/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml +++ /dev/null @@ -1,56 +0,0 @@ -@model Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList -@using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList -@using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient -@using Francesco.Recipes.World.Models.BackendModels.Recipe - -@{ - ViewData["Title"] = "Einkaufsliste Details"; -} - -

Einkaufsliste Details

- -@if (TempData["SuccessMessage"] != null) -{ -
- @TempData["SuccessMessage"] -
-} - -
-

Einkaufsliste

-
-
-
- ID -
-
- @Model.Id -
-
-
- -

Rezepte

- - - - - - - - - @foreach (var recipeShoppingList in Model.RecipeShoppingList) - { - - - - - } - -
RezeptnameZutaten
@recipeShoppingList.Recipe.Name -
    - @foreach (var ingredient in recipeShoppingList.SelectedIngredients) - { -
  • @ingredient.RecipeIngredient.Ingredient.Name - @ingredient.RecipeIngredient.Quantity @ingredient.RecipeIngredient.Unit.Name
  • - } -
-
From d248ccb1dbe35ba99d2fb22a8b2ebcc503973484 Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 8 Apr 2025 15:52:32 +0200 Subject: [PATCH 033/183] Reapply "add CategoryController with endpoints" This reverts commit 928743303c13a39ee1120cec3b27f7ad51055ce5. --- .../Controller/Category/CategoryController.cs | 3 +- .../MediaFile/MediaFileController.cs | 44 +- .../Controller/Recipe/RecipeController.cs | 160 ++++- .../ShoppingList/ShoppingListController.cs | 48 +- .../Data/FrancescosRecipesWorldDbContext.cs | 5 +- .../Francesco.Recipes.World.csproj | 10 +- ...120842_UpdateShoppingLIstLogic.Designer.cs | 572 ++++++++++++++++++ .../20250402120842_UpdateShoppingLIstLogic.cs | 162 +++++ ...escosRecipesWorldDbContextModelSnapshot.cs | 73 ++- .../Models/BackendModels/Recipe/Difficulty.cs | 9 +- .../RecipeIngredientShoppingList.cs | 6 +- .../RecipeShoppingList/RecipeShoppingList.cs | 17 + .../Shoppinglist/ShoppingList.cs | 4 +- Francesco.Recipes.World/Program.cs | 12 +- .../Category/CategoryRecipesViewModel.cs | 12 + .../Views/Recipe/AddInstruction.cshtml | 59 ++ .../Views/Recipe/AddOrCreateIngredient.cshtml | 36 +- .../Views/Recipe/CategoryRecipes.cshtml | 98 +++ .../Views/Recipe/Create.cshtml | 78 +-- .../Views/Recipe/Details.cshtml | 68 +++ .../Views/Recipe/FilterByDifficulty.cshtml | 100 +-- .../Recipe/FilterByDifficultyViewModel.cs | 2 +- ...AddIngredientsToShoppingListPartial.cshtml | 43 ++ .../Views/ShoppingList/Details.cshtml | 56 ++ 24 files changed, 1535 insertions(+), 142 deletions(-) create mode 100644 Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.Designer.cs create mode 100644 Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.cs create mode 100644 Francesco.Recipes.World/Models/BackendModels/RecipeShoppingList/RecipeShoppingList.cs create mode 100644 Francesco.Recipes.World/Views/Category/CategoryRecipesViewModel.cs create mode 100644 Francesco.Recipes.World/Views/Recipe/AddInstruction.cshtml create mode 100644 Francesco.Recipes.World/Views/Recipe/CategoryRecipes.cshtml create mode 100644 Francesco.Recipes.World/Views/Recipe/Details.cshtml create mode 100644 Francesco.Recipes.World/Views/Shared/_AddIngredientsToShoppingListPartial.cshtml create mode 100644 Francesco.Recipes.World/Views/ShoppingList/Details.cshtml diff --git a/Francesco.Recipes.World/Controller/Category/CategoryController.cs b/Francesco.Recipes.World/Controller/Category/CategoryController.cs index 0257ab4..29b06b5 100644 --- a/Francesco.Recipes.World/Controller/Category/CategoryController.cs +++ b/Francesco.Recipes.World/Controller/Category/CategoryController.cs @@ -5,6 +5,7 @@ using Francesco.Recipes.World.Repositories.Category; using Microsoft.AspNetCore.Mvc; + [ValidateAntiForgeryToken] [Route("Category")] public class CategoryController : Controller @@ -37,7 +38,7 @@ public async Task>> GetRecipesByCategory(Guid id) { var recipes = await _categoryRepository.GetRecipesByCategoryAsync(id); - return Ok(recipes); + return View(recipes); } } } diff --git a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs index 6cec16d..14f59fe 100644 --- a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs +++ b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs @@ -1,6 +1,48 @@ namespace Francesco.Recipes.World.Controller.MediaFile { - public class MediaFileController + using Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Repositories.MediaFile; + using Microsoft.AspNetCore.Mvc; + + [ValidateAntiForgeryToken] + + [Route("categories/{categoryId}/Recipe")] + public class MediaFileController : Controller { + private readonly IMediaFileRepository _mediaFileRepository; + private readonly FrancescosRecipesWorldDbContext _context; + + public MediaFileController(IMediaFileRepository mediaFileRepository, FrancescosRecipesWorldDbContext context) + { + _mediaFileRepository = mediaFileRepository; + _context = context; + } + + // POST: /UploadImage + [HttpPost("UploadImage")] + public async Task UploadImage(Guid recipeId, IFormFile? mediaFile) + { + if (mediaFile is null) + { + return BadRequest("Photo is required."); + } + + try + { + await _mediaFileRepository.UploadRecipeMediaAsync(recipeId, mediaFile); + return Ok("Image uploaded successfully."); + } + catch (Exception ex) + { + return StatusCode(500, $"Internal server error: {ex.Message}"); + } + } + + // GET: /UploadImage + [HttpGet("UploadImage")] + public IActionResult UploadImageView() + { + return View(); + } } } diff --git a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs index 9b4cd9e..05d99aa 100644 --- a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs +++ b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs @@ -1,43 +1,94 @@ namespace Francesco.Recipes.World.Controller.Recipe { + using System.ComponentModel.DataAnnotations; using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Repositories.Category; + using Francesco.Recipes.World.Repositories.Favorit; using Francesco.Recipes.World.Repositories.Ingredient; + using Francesco.Recipes.World.Repositories.Instruction; + using Francesco.Recipes.World.Repositories.MediaFile; using Francesco.Recipes.World.Repositories.Recipe; using Francesco.Recipes.World.Repositories.Unit; + using Francesco.Recipes.World.Views.Category; + using Francesco.Recipes.World.Views.Recipe; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; - [Route("categories/{categoryId}/Recipe")] + [ValidateAntiForgeryToken] + [Route("Recipe")] public class RecipeController : Controller { private readonly IRecipeRepository _recipeRepository; private readonly IUnitRepository _unitRepository; private readonly ICategoryRepository _categoryRepository; private readonly IIngredientRepository _ingredientRepository; + private readonly IMediaFileRepository _mediaFileRepository; + private readonly IInstructionRepository _instructionRepository; + private readonly IFavoritRepository _favoritRepository; - public RecipeController(IRecipeRepository recipeRepository, IUnitRepository unitRepository, ICategoryRepository categoryRepository, IIngredientRepository ingredientRepository) + [Display(Name = "Schwierigkeitsgrad")] + [BindProperty(SupportsGet = true)] + public Difficulty? SelectedDifficulty { get; set; } + + public RecipeController(IRecipeRepository recipeRepository, IUnitRepository unitRepository, ICategoryRepository categoryRepository, IIngredientRepository ingredientRepository, IMediaFileRepository mediaFileRepository, IInstructionRepository instructionRepository, IFavoritRepository favoritRepository) { _recipeRepository = recipeRepository; _unitRepository = unitRepository; _categoryRepository = categoryRepository; _ingredientRepository = ingredientRepository; + Recipes = new List(); + _mediaFileRepository = mediaFileRepository; + _instructionRepository = instructionRepository; + _favoritRepository = favoritRepository; } public IReadOnlyCollection Recipes { get; set; } - // GET: /Recipe/AddOrCreateIngredient + // GET: /Recipe/{recipeId}/AddOrCreateIngredient [HttpGet("{recipeId}/AddOrCreateIngredient")] - public async Task AddOrCreateIngredient() + public async Task AddOrCreateIngredient(Guid recipeId) { var units = await _unitRepository.GetAllUnitsAsync(); ViewBag.Units = new SelectList(units, "Id", "Name"); + ViewBag.RecipeId = recipeId; return View(); } - // POST: /Recipe/AddOrCreateIngredient + // GET: /Recipe/Details/{recipeId} + [HttpGet("Details/{recipeId}")] + public async Task Details(Guid recipeId) + { + var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId); + if (recipe == null) + { + return NotFound("Recipe not found."); + } + + return View(recipe); + } + + // GET: /Recipe/CategoryRecipes + [HttpGet("CategoryRecipes")] + public async Task CategoryRecipes() + { + var categories = await _categoryRepository.GetAllCategoriesAsync(); + var viewModel = new List(); + + foreach (var category in categories) + { + var recipes = await _categoryRepository.GetRecipesByCategoryAsync(category.Id); + viewModel.Add(new CategoryRecipesViewModel + { + Category = category, + Recipes = recipes, + }); + } + + return View(viewModel); + } + + // POST: /Recipe/{recipeId}/AddOrCreateIngredient [HttpPost("{recipeId}/AddOrCreateIngredient")] - [ValidateAntiForgeryToken] public async Task AddOrCreateIngredient(Guid recipeId, string ingredientName, int quantity, Guid unitId) { if (quantity <= 0) @@ -56,8 +107,8 @@ return RedirectToAction("Details", new { id = recipeId }); } - // GET: /categories/{categoryId}/Recipe/Create - [HttpGet("Create")] + // GET: /Recipe/Create/{categoryId} + [HttpGet("Create/{categoryId}")] public async Task Create(Guid categoryId) { var category = await _categoryRepository.GetCategoryByIdAsync(categoryId); @@ -70,10 +121,9 @@ return View(); } - // POST: /categories/{categoryId}/Recipe/Create - [HttpPost("Create")] - [ValidateAntiForgeryToken] - public async Task Create(Guid categoryId, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime) + // POST: /Recipe/Create/{categoryId} + [HttpPost("Create/{categoryId}")] + public async Task Create(Guid categoryId, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime, IFormFile? photo) { if (string.IsNullOrWhiteSpace(name)) { @@ -103,13 +153,18 @@ return NotFound($"Kategorie mit ID {categoryId} wurde nicht gefunden."); } - await _recipeRepository.CreateRecipeForCategoryAsync(categoryEntity, name, description, difficulty, servings, preparationTime, cookingTime); + var recipe = await _recipeRepository.CreateRecipeForCategoryAsync(categoryEntity, name, description, difficulty, servings, preparationTime, cookingTime); + if (photo != null) + { + await _mediaFileRepository.UploadRecipeMediaAsync(recipe.Id, photo); + } + return RedirectToAction("Details", "Category", new { id = categoryId }); } - // GET: /categories/{categoryId}/Recipe/{recipeId}/RemoveIngredient/{ingredientId} + // GET: /Recipe/{recipeId}/RemoveIngredient/{ingredientId} [HttpGet("{recipeId}/RemoveIngredient/{ingredientId}")] - public async Task RemoveIngredient(Guid categoryId, Guid recipeId, Guid ingredientId) + public async Task RemoveIngredient(Guid recipeId, Guid ingredientId) { var recipe = await _recipeRepository.GetRecipeAsync(recipeId); var ingredient = await _ingredientRepository.GetIngredientByIdAsync(ingredientId); @@ -121,28 +176,87 @@ ViewBag.RecipeId = recipeId; ViewBag.IngredientId = ingredientId; - ViewBag.CategoryId = categoryId; ViewBag.IngredientName = ingredient.Name; return View(); } - // POST: /categories/{categoryId}/Recipe/{recipeId}/RemoveIngredient/{ingredientId} + // POST: /Recipe/{recipeId}/RemoveIngredient/{ingredientId} [HttpPost("{recipeId}/RemoveIngredient/{ingredientId}")] - [ValidateAntiForgeryToken] - public async Task RemoveIngredientConfirmed(Guid categoryId, Guid recipeId, Guid ingredientId) + public async Task RemoveIngredientConfirmed(Guid recipeId, Guid ingredientId) { await _recipeRepository.RemoveIngredientFromRecipeAsync(recipeId, ingredientId); TempData["SuccessMessage"] = "Ingredient removed successfully."; return RedirectToAction("Details", new { id = recipeId }); } - // GET: /categories/{categoryId}/Recipe/FilterByDifficulty + // GET: /Recipe/FilterByDifficulty [HttpGet("FilterByDifficulty")] - public async Task FilterByDifficulty(Difficulty? difficulty) + public async Task FilterByDifficulty(Difficulty? selectedDifficulty) { - Recipes = await _recipeRepository.GetRecipesByDifficultyAsync(difficulty); - return View(Recipes); + var recipes = await _recipeRepository.GetRecipesByDifficultyAsync(selectedDifficulty); + var viewModel = new FilterByDifficultyViewModel + { + SelectedDifficulty = selectedDifficulty, + Recipes = recipes, + }; + return View(viewModel); + } + + // GET: /Recipe/{recipeId}/AddInstruction + [HttpGet("{recipeId}/AddInstruction")] + public async Task AddInstruction(Guid recipeId) + { + var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId); + if (recipe == null) + { + return NotFound("Recipe not found."); + } + + ViewBag.RecipeId = recipeId; + return View(recipe); + } + + // POST: /Recipe/{recipeId}/AddInstruction + [HttpPost("{recipeId}/AddInstruction")] + public async Task AddInstruction(Guid recipeId, string description, int number) + { + try + { + await _instructionRepository.CreateInstructionToRecipeAsync(recipeId, description, number); + return RedirectToAction("AddInstruction", new { recipeId }); + } + catch (Exception ex) + { + ModelState.AddModelError(string.Empty, ex.Message); + var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId); + ViewBag.RecipeId = recipeId; + return View(recipe); + } + } + + // GET: /Recipe/Favorites + [HttpGet("Favorites")] + public async Task Favorites() + { + var favoriteRecipes = await _favoritRepository.GetFavoriteRecipesAsync(); + return View(favoriteRecipes); + } + + // POST: /Recipe/AddFavorite + [HttpPost("AddFavorite")] + public async Task AddFavorite(Guid recipeId) + { + await _favoritRepository.AddFavoriteAsync(recipeId); + return RedirectToAction("Details", new { recipeId }); + } + + // POST: /Recipe/RemoveFavorite + [HttpPost("RemoveFavorite")] + public async Task RemoveFavorite(Guid recipeId) + { + await _favoritRepository.RemoveFavoriteAsync(recipeId); + return RedirectToAction("Details", new { recipeId }); } } } diff --git a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs index 38fdf4b..7f6a434 100644 --- a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs +++ b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs @@ -1,6 +1,50 @@ -namespace Francesco.Recipes.World.Controller.ShoppingList +namespace Francesco.Recipes.World.Controllers { - public class ShoppingListController + using Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Repositories.ShoppingList; + using Microsoft.AspNetCore.Mvc; + using Microsoft.EntityFrameworkCore; + + [Route("ShoppingList")] + public class ShoppingListController : Controller { + private readonly IShoppingListRepository _shoppingListRepository; + private readonly FrancescosRecipesWorldDbContext _context; + + public ShoppingListController(IShoppingListRepository shoppingListRepository, FrancescosRecipesWorldDbContext context) + { + _shoppingListRepository = shoppingListRepository; + _context = context; + } + + [HttpPost("CreateOrAddIngredients")] + public async Task CreateOrAddIngredients([FromBody] CreateOrAddIngredientsRequest request) + { + if (request == null || request.IngredientIds == null || !request.IngredientIds.Any()) + { + return BadRequest("No ingredients provided."); + } + + await _shoppingListRepository.AddIngredientsToShoppingListAsync(request.RecipeId, request.IngredientIds); + + var shoppingList = await _context.ShoppingLists + .Include(sl => sl.RecipeShoppingList) + .ThenInclude(rsl => rsl.Recipe) + .FirstOrDefaultAsync(sl => sl.RecipeShoppingList.Any(rsl => rsl.Recipe.Id == request.RecipeId)); + + if (shoppingList == null) + { + return BadRequest("Error creating shopping list."); + } + + return Json(new { shoppingListId = shoppingList.Id }); + } + + public class CreateOrAddIngredientsRequest + { + public Guid RecipeId { get; set; } + + public List IngredientIds { get; set; } = new (); + } } } diff --git a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs index 4fd6bd8..7a8a5e7 100644 --- a/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs +++ b/Francesco.Recipes.World/Data/FrancescosRecipesWorldDbContext.cs @@ -9,7 +9,8 @@ using Francesco.Recipes.World.Models.BackendModels.MediaFile; using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; - using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; +using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList; +using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; using Francesco.Recipes.World.Models.BackendModels.Unit; using Microsoft.EntityFrameworkCore; @@ -36,6 +37,8 @@ public DbSet RecipeIngredientsShoppingLists => Set(); + public DbSet RecipeShoppingLists => Set(); + public DbSet ShoppingLists => Set(); public DbSet MediaFiles => Set(); diff --git a/Francesco.Recipes.World/Francesco.Recipes.World.csproj b/Francesco.Recipes.World/Francesco.Recipes.World.csproj index 549a91d..7d430a2 100644 --- a/Francesco.Recipes.World/Francesco.Recipes.World.csproj +++ b/Francesco.Recipes.World/Francesco.Recipes.World.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -45,5 +45,13 @@ + + + + + + + + diff --git a/Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.Designer.cs b/Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.Designer.cs new file mode 100644 index 0000000..d99ae26 --- /dev/null +++ b/Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.Designer.cs @@ -0,0 +1,572 @@ +// + +using Francesco.Recipes.World.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + + +#nullable disable + +namespace Francesco.Recipes.World.Migrations +{ + [DbContext(typeof(FrancescosRecipesWorldDbContext))] + [Migration("20250402120842_UpdateShoppingLIstLogic")] + partial class UpdateShoppingLIstLogic + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + + b.HasData( + new + { + Id = new Guid("b248244f-f21c-4555-a14d-5dd49a2717cf"), + Name = "Vorspeisen & Snacks" + }, + new + { + Id = new Guid("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"), + Name = "Erste Gänge" + }, + new + { + Id = new Guid("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"), + Name = "Hauptgerichte" + }, + new + { + Id = new Guid("90deec39-dcd0-422d-9018-ac8389e332e1"), + Name = "Desserts & Süßspeisen" + }, + new + { + Id = new Guid("28e39168-701a-4084-81da-d96c987c462f"), + Name = "Beilagen & Salate" + }, + new + { + Id = new Guid("20585b74-4805-4aff-a6df-aa6b7af04ff1"), + Name = "Kuchen" + }, + new + { + Id = new Guid("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"), + Name = "Hefegebäck & Brot" + }, + new + { + Id = new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"), + Name = "Soßen & Saucen" + }, + new + { + Id = new Guid("23b1c740-e427-44f9-a6ea-d33d3f30f05a"), + Name = "Marmeladen & Eingemachtes" + }, + new + { + Id = new Guid("0a91a200-dc76-4e00-b38c-b38cab5b69d7"), + Name = "Getränke" + }); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Favorits"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Ingredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("IngredientId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsChecked") + .HasColumnType("bit"); + + b.Property("RecipeIngredientId") + .HasColumnType("uniqueidentifier"); + + b.Property("RecipeShoppingListId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IngredientId"); + + b.HasIndex("RecipeIngredientId"); + + b.HasIndex("RecipeShoppingListId"); + + b.ToTable("RecipeIngredientsShoppingLists"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Number") + .HasColumnType("int"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RecipeId"); + + b.ToTable("Instructions"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Data") + .HasColumnType("varbinary(max)"); + + b.Property("FileName") + .HasColumnType("nvarchar(max)"); + + b.Property("InstructionId") + .HasColumnType("uniqueidentifier"); + + b.Property("MimeType") + .HasColumnType("nvarchar(max)"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("InstructionId"); + + b.HasIndex("RecipeId"); + + b.ToTable("MediaFiles"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("CookingTime") + .HasColumnType("time"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Difficulty") + .HasColumnType("int"); + + b.Property("FavoritId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsFavorite") + .HasColumnType("bit"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PreparationTime") + .HasColumnType("time"); + + b.Property("Servings") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("FavoritId"); + + b.ToTable("Recipes"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("IngredientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.Property("UnitId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IngredientId"); + + b.HasIndex("RecipeId"); + + b.HasIndex("UnitId"); + + b.ToTable("RecipeIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.Property("ShoppingListId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RecipeId"); + + b.HasIndex("ShoppingListId"); + + b.ToTable("RecipeShoppingLists"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("ShoppingLists"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Symbol") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Units"); + + b.HasData( + new + { + Id = new Guid("62eb2c11-8768-46fb-9db0-c77f940bb4aa"), + Name = "liter", + Symbol = "l" + }, + new + { + Id = new Guid("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"), + Name = "gramm", + Symbol = "g" + }, + new + { + Id = new Guid("7e3d1b86-ac48-45d6-814e-d3492a86db1d"), + Name = "kilogramm", + Symbol = "kg" + }, + new + { + Id = new Guid("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"), + Name = "stücke", + Symbol = "stk" + }, + new + { + Id = new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"), + Name = "blatt", + Symbol = "blatt" + }, + new + { + Id = new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"), + Name = "messerspitze", + Symbol = "msp" + }, + new + { + Id = new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"), + Name = "stange", + Symbol = "stange" + }, + new + { + Id = new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"), + Name = "bund", + Symbol = "bund" + }, + new + { + Id = new Guid("66556e0e-eb2b-4bc3-9a56-135dd508ed09"), + Name = "zehe", + Symbol = "zehe" + }, + new + { + Id = new Guid("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"), + Name = "teelöffel", + Symbol = "TL" + }, + new + { + Id = new Guid("24a91b89-3389-4465-89e4-2f70d2ea6fd7"), + Name = "esslöffel", + Symbol = "EL" + }); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", null) + .WithMany("IngredientShoppingLists") + .HasForeignKey("IngredientId"); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", "RecipeIngredient") + .WithMany() + .HasForeignKey("RecipeIngredientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", "RecipeShoppingList") + .WithMany("SelectedIngredients") + .HasForeignKey("RecipeShoppingListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RecipeIngredient"); + + b.Navigation("RecipeShoppingList"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany("Instructions") + .HasForeignKey("RecipeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Recipe"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", "Instruction") + .WithMany("MediaFiles") + .HasForeignKey("InstructionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany("MediaFiles") + .HasForeignKey("RecipeId"); + + b.Navigation("Instruction"); + + b.Navigation("Recipe"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", "Category") + .WithMany("Recipes") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorit") + .WithMany("Recipe") + .HasForeignKey("FavoritId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Favorit"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient") + .WithMany("RecipeIngredients") + .HasForeignKey("IngredientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany("RecipeIngredients") + .HasForeignKey("RecipeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit") + .WithMany("RecipeIngredient") + .HasForeignKey("UnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ingredient"); + + b.Navigation("Recipe"); + + b.Navigation("Unit"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany() + .HasForeignKey("RecipeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList") + .WithMany("RecipeIngredientShoppingLists") + .HasForeignKey("ShoppingListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Recipe"); + + b.Navigation("ShoppingList"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b => + { + b.Navigation("Recipes"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b => + { + b.Navigation("Recipe"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b => + { + b.Navigation("IngredientShoppingLists"); + + b.Navigation("RecipeIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => + { + b.Navigation("MediaFiles"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => + { + b.Navigation("Instructions"); + + b.Navigation("MediaFiles"); + + b.Navigation("RecipeIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => + { + b.Navigation("SelectedIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => + { + b.Navigation("RecipeIngredientShoppingLists"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b => + { + b.Navigation("RecipeIngredient"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.cs b/Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.cs new file mode 100644 index 0000000..5caf69f --- /dev/null +++ b/Francesco.Recipes.World/Migrations/20250402120842_UpdateShoppingLIstLogic.cs @@ -0,0 +1,162 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Francesco.Recipes.World.Migrations +{ + /// + public partial class UpdateShoppingLIstLogic : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + if (migrationBuilder is null) + { + throw new ArgumentNullException(nameof(migrationBuilder)); + } + + migrationBuilder.DropForeignKey( + name: "FK_RecipeIngredientsShoppingLists_ShoppingLists_ShoppingListId", + table: "RecipeIngredientsShoppingLists"); + + migrationBuilder.DropForeignKey( + name: "FK_Recipes_Categories_CategoryId", + table: "Recipes"); + + migrationBuilder.RenameColumn( + name: "ShoppingListId", + table: "RecipeIngredientsShoppingLists", + newName: "RecipeShoppingListId"); + + migrationBuilder.RenameIndex( + name: "IX_RecipeIngredientsShoppingLists_ShoppingListId", + table: "RecipeIngredientsShoppingLists", + newName: "IX_RecipeIngredientsShoppingLists_RecipeShoppingListId"); + + migrationBuilder.AlterColumn( + name: "CategoryId", + table: "Recipes", + type: "uniqueidentifier", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uniqueidentifier", + oldNullable: true); + + migrationBuilder.AddColumn( + name: "IsChecked", + table: "RecipeIngredientsShoppingLists", + type: "bit", + nullable: false, + defaultValue: false); + + migrationBuilder.CreateTable( + name: "RecipeShoppingLists", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + ShoppingListId = table.Column(type: "uniqueidentifier", nullable: false), + RecipeId = table.Column(type: "uniqueidentifier", nullable: false), + }, + constraints: table => + { + table.PrimaryKey("PK_RecipeShoppingLists", x => x.Id); + table.ForeignKey( + name: "FK_RecipeShoppingLists_Recipes_RecipeId", + column: x => x.RecipeId, + principalTable: "Recipes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RecipeShoppingLists_ShoppingLists_ShoppingListId", + column: x => x.ShoppingListId, + principalTable: "ShoppingLists", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_RecipeShoppingLists_RecipeId", + table: "RecipeShoppingLists", + column: "RecipeId"); + + migrationBuilder.CreateIndex( + name: "IX_RecipeShoppingLists_ShoppingListId", + table: "RecipeShoppingLists", + column: "ShoppingListId"); + + migrationBuilder.AddForeignKey( + name: "FK_RecipeIngredientsShoppingLists_RecipeShoppingLists_RecipeShoppingListId", + table: "RecipeIngredientsShoppingLists", + column: "RecipeShoppingListId", + principalTable: "RecipeShoppingLists", + principalColumn: "Id", + onDelete: ReferentialAction.NoAction); + + migrationBuilder.AddForeignKey( + name: "FK_Recipes_Categories_CategoryId", + table: "Recipes", + column: "CategoryId", + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + if (migrationBuilder is null) + { + throw new ArgumentNullException(nameof(migrationBuilder)); + } + + migrationBuilder.DropForeignKey( + name: "FK_RecipeIngredientsShoppingLists_RecipeShoppingLists_RecipeShoppingListId", + table: "RecipeIngredientsShoppingLists"); + + migrationBuilder.DropForeignKey( + name: "FK_Recipes_Categories_CategoryId", + table: "Recipes"); + + migrationBuilder.DropTable( + name: "RecipeShoppingLists"); + + migrationBuilder.DropColumn( + name: "IsChecked", + table: "RecipeIngredientsShoppingLists"); + + migrationBuilder.RenameColumn( + name: "RecipeShoppingListId", + table: "RecipeIngredientsShoppingLists", + newName: "ShoppingListId"); + + migrationBuilder.RenameIndex( + name: "IX_RecipeIngredientsShoppingLists_RecipeShoppingListId", + table: "RecipeIngredientsShoppingLists", + newName: "IX_RecipeIngredientsShoppingLists_ShoppingListId"); + + migrationBuilder.AlterColumn( + name: "CategoryId", + table: "Recipes", + type: "uniqueidentifier", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uniqueidentifier"); + + migrationBuilder.AddForeignKey( + name: "FK_RecipeIngredientsShoppingLists_ShoppingLists_ShoppingListId", + table: "RecipeIngredientsShoppingLists", + column: "ShoppingListId", + principalTable: "ShoppingLists", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Recipes_Categories_CategoryId", + table: "Recipes", + column: "CategoryId", + principalTable: "Categories", + principalColumn: "Id"); + } + } +} diff --git a/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs b/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs index 6b432d8..c27540a 100644 --- a/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs +++ b/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs @@ -1,7 +1,10 @@ // +using System; using Francesco.Recipes.World.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable @@ -124,10 +127,13 @@ namespace Francesco.Recipes.World.Migrations b.Property("IngredientId") .HasColumnType("uniqueidentifier"); + b.Property("IsChecked") + .HasColumnType("bit"); + b.Property("RecipeIngredientId") .HasColumnType("uniqueidentifier"); - b.Property("ShoppingListId") + b.Property("RecipeShoppingListId") .HasColumnType("uniqueidentifier"); b.HasKey("Id"); @@ -136,7 +142,7 @@ namespace Francesco.Recipes.World.Migrations b.HasIndex("RecipeIngredientId"); - b.HasIndex("ShoppingListId"); + b.HasIndex("RecipeShoppingListId"); b.ToTable("RecipeIngredientsShoppingLists"); }); @@ -200,7 +206,7 @@ namespace Francesco.Recipes.World.Migrations .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("CategoryId") + b.Property("CategoryId") .HasColumnType("uniqueidentifier"); b.Property("CookingTime") @@ -272,6 +278,27 @@ namespace Francesco.Recipes.World.Migrations b.ToTable("RecipeIngredients"); }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.Property("ShoppingListId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RecipeId"); + + b.HasIndex("ShoppingListId"); + + b.ToTable("RecipeShoppingLists"); + }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => { b.Property("Id") @@ -388,15 +415,15 @@ namespace Francesco.Recipes.World.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList") - .WithMany("RecipeIngredientShoppingLists") - .HasForeignKey("ShoppingListId") + b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", "RecipeShoppingList") + .WithMany("SelectedIngredients") + .HasForeignKey("RecipeShoppingListId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.Navigation("RecipeIngredient"); - b.Navigation("ShoppingList"); + b.Navigation("RecipeShoppingList"); }); modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => @@ -429,9 +456,11 @@ namespace Francesco.Recipes.World.Migrations modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => { - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", null) + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", "Category") .WithMany("Recipes") - .HasForeignKey("CategoryId"); + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorit") .WithMany("Recipe") @@ -439,6 +468,8 @@ namespace Francesco.Recipes.World.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.Navigation("Category"); + b.Navigation("Favorit"); }); @@ -469,6 +500,25 @@ namespace Francesco.Recipes.World.Migrations b.Navigation("Unit"); }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany() + .HasForeignKey("RecipeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList") + .WithMany("RecipeIngredientShoppingLists") + .HasForeignKey("ShoppingListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Recipe"); + + b.Navigation("ShoppingList"); + }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b => { b.Navigation("Recipes"); @@ -500,6 +550,11 @@ namespace Francesco.Recipes.World.Migrations b.Navigation("RecipeIngredients"); }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => + { + b.Navigation("SelectedIngredients"); + }); + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => { b.Navigation("RecipeIngredientShoppingLists"); diff --git a/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs b/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs index ee81fd2..5dc055b 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Recipe/Difficulty.cs @@ -1,15 +1,22 @@ -namespace Francesco.Recipes.World.Models.BackendModels.Recipe +using System.ComponentModel.DataAnnotations; + +namespace Francesco.Recipes.World.Models.BackendModels.Recipe { public enum Difficulty { + [Display(Name = "Sehr einfach")] VeryEasy = 0, + [Display(Name = "Einfach")] Easy = 1, + [Display(Name = "Mittel")] Medium = 2, + [Display(Name = "Schwer")] Hard = 3, + [Display(Name = "Experte")] Expert = 4, } } diff --git a/Francesco.Recipes.World/Models/BackendModels/RecipeIngredientShoppingList/RecipeIngredientShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/RecipeIngredientShoppingList/RecipeIngredientShoppingList.cs index 1878caf..fa691b8 100644 --- a/Francesco.Recipes.World/Models/BackendModels/RecipeIngredientShoppingList/RecipeIngredientShoppingList.cs +++ b/Francesco.Recipes.World/Models/BackendModels/RecipeIngredientShoppingList/RecipeIngredientShoppingList.cs @@ -1,14 +1,16 @@ namespace Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList { using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; - using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; + using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList; public class RecipeIngredientShoppingList { public Guid Id { get; set; } - public virtual ShoppingList ShoppingList { get; set; } = new (); + public virtual RecipeShoppingList RecipeShoppingList { get; set; } = new (); public virtual RecipeIngredient RecipeIngredient { get; set; } = new (); + + public bool IsChecked { get; set; } = false; } } diff --git a/Francesco.Recipes.World/Models/BackendModels/RecipeShoppingList/RecipeShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/RecipeShoppingList/RecipeShoppingList.cs new file mode 100644 index 0000000..1173425 --- /dev/null +++ b/Francesco.Recipes.World/Models/BackendModels/RecipeShoppingList/RecipeShoppingList.cs @@ -0,0 +1,17 @@ +namespace Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList +{ + using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; + using Francesco.Recipes.World.Models.BackendModels.Recipe; + using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; + + public class RecipeShoppingList + { + public Guid Id { get; set; } + + public virtual ShoppingList ShoppingList { get; set; } = new ShoppingList(); + + public virtual Recipe Recipe { get; set; } = new (); + + public virtual ICollection SelectedIngredients { get; set; } = new List(); + } +} diff --git a/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs index c8fd12f..1a06a05 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Shoppinglist/ShoppingList.cs @@ -1,6 +1,6 @@ namespace Francesco.Recipes.World.Models.BackendModels.Shoppinglist { - using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; + using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList; public class ShoppingList : ITimeStampedEntity { @@ -10,6 +10,6 @@ public DateTime? ModifiedAt { get; set; } - public virtual ICollection RecipeIngredientShoppingLists { get; set; } = new List(); + public virtual ICollection RecipeShoppingList { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Program.cs b/Francesco.Recipes.World/Program.cs index 719acda..6f98ff1 100644 --- a/Francesco.Recipes.World/Program.cs +++ b/Francesco.Recipes.World/Program.cs @@ -1,5 +1,6 @@ using Francesco.Recipes.World.Data; using Francesco.Recipes.World.Repositories.Category; +using Francesco.Recipes.World.Repositories.Favorit; using Francesco.Recipes.World.Repositories.Ingredient; using Francesco.Recipes.World.Repositories.Instruction; using Francesco.Recipes.World.Repositories.MediaFile; @@ -7,8 +8,6 @@ using Francesco.Recipes.World.Repositories.Recipe; using Francesco.Recipes.World.Repositories.ShoppingList; using Francesco.Recipes.World.Repositories.Unit; -using Microsoft.AspNetCore.Identity; - using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); @@ -23,9 +22,6 @@ var connectionString = builder.Configuration.GetConnectionString("FrancescosReci services.AddDbContext(options => options.UseSqlServer(connectionString)); -services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) - .AddEntityFrameworkStores(); - // Add services to the container. builder.Services.AddControllersWithViews(); @@ -43,6 +39,8 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -62,10 +60,6 @@ app.UseStaticFiles(); app.UseRouting(); -app.UseAuthentication(); - -app.UseAuthorization(); - app.MapDefaultControllerRoute(); app.MapControllerRoute( diff --git a/Francesco.Recipes.World/Views/Category/CategoryRecipesViewModel.cs b/Francesco.Recipes.World/Views/Category/CategoryRecipesViewModel.cs new file mode 100644 index 0000000..1559dd8 --- /dev/null +++ b/Francesco.Recipes.World/Views/Category/CategoryRecipesViewModel.cs @@ -0,0 +1,12 @@ +namespace Francesco.Recipes.World.Views.Category +{ + using Francesco.Recipes.World.Models.BackendModels.Category; + using Francesco.Recipes.World.Models.BackendModels.Recipe; + + public class CategoryRecipesViewModel + { + public Category Category { get; set; } = new (); + + public IEnumerable Recipes { get; set; } = new List(); + } +} diff --git a/Francesco.Recipes.World/Views/Recipe/AddInstruction.cshtml b/Francesco.Recipes.World/Views/Recipe/AddInstruction.cshtml new file mode 100644 index 0000000..d6d5f79 --- /dev/null +++ b/Francesco.Recipes.World/Views/Recipe/AddInstruction.cshtml @@ -0,0 +1,59 @@ +@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe + +@{ + ViewData["Title"] = "Add Instructions"; +} + +

Add Instructions to @Model.Name

+ +
+

Existing Instructions

+
    + @foreach (var instruction in Model.Instructions.OrderBy(i => i.Number)) + { +
  • @instruction.Number. @instruction.Description
  • + } +
+
+ +
+

Add New Instruction

+
+ +
+ + +
+
+ + +
+ +
+
+ +@section Scripts { + +} + diff --git a/Francesco.Recipes.World/Views/Recipe/AddOrCreateIngredient.cshtml b/Francesco.Recipes.World/Views/Recipe/AddOrCreateIngredient.cshtml index c72fdcf..1232469 100644 --- a/Francesco.Recipes.World/Views/Recipe/AddOrCreateIngredient.cshtml +++ b/Francesco.Recipes.World/Views/Recipe/AddOrCreateIngredient.cshtml @@ -1,29 +1,27 @@ @{ - ViewData["Title"] = "Add or Create Ingredient to Recipe"; + ViewData["Title"] = "Add or Create Ingredient to Recipe"; }

@ViewData["Title"]

-
- - -
-
- - -
-
- - -
-
- - -
- + +
+ + +
+
+ + +
+
+ + +
+
@section Scripts { - + } + diff --git a/Francesco.Recipes.World/Views/Recipe/CategoryRecipes.cshtml b/Francesco.Recipes.World/Views/Recipe/CategoryRecipes.cshtml new file mode 100644 index 0000000..df06c6e --- /dev/null +++ b/Francesco.Recipes.World/Views/Recipe/CategoryRecipes.cshtml @@ -0,0 +1,98 @@ +@model IEnumerable + +@{ + ViewData["Title"] = "Category Recipes"; +} + +

Category Recipes

+ +@foreach (var categoryRecipes in Model) +{ +
+

@categoryRecipes.Category.Name

+ Rezept erstellen +
+ @foreach (var recipe in categoryRecipes.Recipes) + { +
+
+ @if (recipe.MediaFiles.Any() && recipe.MediaFiles.First().Data != null) + { + var mediaFile = recipe.MediaFiles.First(); + if (mediaFile.Data != null) + { + @recipe.Name + } + } +
+
+

@recipe.Name

+

@recipe.Description

+

Difficulty: @recipe.Difficulty

+

Servings: @recipe.Servings

+

Preparation Time: @recipe.PreparationTime

+

Cooking Time: @recipe.CookingTime

+
+ @if (recipe.IsFavorite) + { +
+ + +
+ } + else + { +
+ + +
+ } +
+
+
+ } + +
+
+} + + + diff --git a/Francesco.Recipes.World/Views/Recipe/Create.cshtml b/Francesco.Recipes.World/Views/Recipe/Create.cshtml index 8b1e201..e8c3769 100644 --- a/Francesco.Recipes.World/Views/Recipe/Create.cshtml +++ b/Francesco.Recipes.World/Views/Recipe/Create.cshtml @@ -1,45 +1,53 @@ @model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe @using Francesco.Recipes.World.Models.BackendModels.Recipe @{ - ViewData["Title"] = "Create Recipe"; + ViewData["Title"] = "Create Recipe"; }

Create Recipe

-
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- -
+ +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
+
@section Scripts { diff --git a/Francesco.Recipes.World/Views/Recipe/Details.cshtml b/Francesco.Recipes.World/Views/Recipe/Details.cshtml new file mode 100644 index 0000000..4ea25c7 --- /dev/null +++ b/Francesco.Recipes.World/Views/Recipe/Details.cshtml @@ -0,0 +1,68 @@ +@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe + +@{ + ViewData["Title"] = "Recipe Details"; +} + +

@Model.Name

+ +
+
+ @if (Model.MediaFiles.Any() && Model.MediaFiles.First().Data != null) + { + var mediaFile = Model.MediaFiles.First(); + if (mediaFile.Data != null) + { + @Model.Name + } + } +
+
+

Description: @Model.Description

+

Difficulty: @Model.Difficulty

+

Servings: @Model.Servings

+

Preparation Time: @Model.PreparationTime

+

Cooking Time: @Model.CookingTime

+
+
+

Ingredients

+
+
    + @foreach (var ingredient in Model.RecipeIngredients) + { +
  • + + @ingredient.Ingredient.Name - @ingredient.Quantity @(ingredient.Unit != null ? ingredient.Unit.Symbol : string.Empty) +
  • + } +
+ +
+
+
+ +@section Scripts { + +} diff --git a/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml b/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml index 01a77cb..b0e499e 100644 --- a/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml +++ b/Francesco.Recipes.World/Views/Recipe/FilterByDifficulty.cshtml @@ -1,42 +1,72 @@ - @model Francesco.Recipes.World.Views.Recipe.FilterByDifficultyViewModel - @using Francesco.Recipes.World.Models.BackendModels.Recipe +@using Francesco.Recipes.World.Models.BackendModels.Recipe +@model Francesco.Recipes.World.Views.Recipe.FilterByDifficultyViewModel -@{ - ViewData["Title"] = "Filter Recipes by Difficulty"; -} +

Rezepte nach Schwierigkeitsgrad

-

Filter Recipes by Difficulty

+ -
-
- - - -
-
+
+
+
+
+ + +
+
-@if (Model.Recipes != null && Model.Recipes.Any()) -{ -

Filtered Recipes

-
    - @foreach (var recipe in Model.Recipes) - { -
  • @recipe.Name - @recipe.Difficulty.
  • - } -
-} -else -{ -

No recipes found for the selected difficulty.

-} +
+
+ +
+ @if (Model?.Recipes != null && Model.Recipes.Any()) + { +
+ + + + + + + + + + + + + @foreach (var recipe in Model.Recipes) + { + + + + + + + + + } + +
NameBeschreibungSchwierigkeitsgradPortionenZubereitungszeitAktionen
@recipe.Name@(recipe.Description?.Length > 100 ? recipe.Description.Substring(0, 100) + "..." : recipe.Description)@recipe.?Difficulty@recipe.Servings@($"{recipe.PreparationTime.TotalMinutes} Min.") + Details + Bearbeiten +
+
+ } + else + { +
+

Keine Rezepte gefunden.

+
+ } +
@section Scripts { - - @await Html.PartialAsync("_ValidationScriptsPartial") + } diff --git a/Francesco.Recipes.World/Views/Recipe/FilterByDifficultyViewModel.cs b/Francesco.Recipes.World/Views/Recipe/FilterByDifficultyViewModel.cs index 94f71f6..b0543f3 100644 --- a/Francesco.Recipes.World/Views/Recipe/FilterByDifficultyViewModel.cs +++ b/Francesco.Recipes.World/Views/Recipe/FilterByDifficultyViewModel.cs @@ -7,6 +7,6 @@ { public Difficulty? SelectedDifficulty { get; set; } - public List Recipes { get; set; } = new List(); + public IReadOnlyCollection Recipes { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Views/Shared/_AddIngredientsToShoppingListPartial.cshtml b/Francesco.Recipes.World/Views/Shared/_AddIngredientsToShoppingListPartial.cshtml new file mode 100644 index 0000000..565c6f6 --- /dev/null +++ b/Francesco.Recipes.World/Views/Shared/_AddIngredientsToShoppingListPartial.cshtml @@ -0,0 +1,43 @@ +@model IEnumerable + +
+

Zutaten

+
+
    + @foreach (var ingredient in Model) + { +
  • + + @ingredient.Ingredient.Name - @ingredient.Quantity @ingredient.Unit.Symbol +
  • + } +
+ +
+
+ +@section Scripts { + +} diff --git a/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml b/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml new file mode 100644 index 0000000..1efa149 --- /dev/null +++ b/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml @@ -0,0 +1,56 @@ +@model Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList +@using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList +@using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient +@using Francesco.Recipes.World.Models.BackendModels.Recipe + +@{ + ViewData["Title"] = "Einkaufsliste Details"; +} + +

Einkaufsliste Details

+ +@if (TempData["SuccessMessage"] != null) +{ +
+ @TempData["SuccessMessage"] +
+} + +
+

Einkaufsliste

+
+
+
+ ID +
+
+ @Model.Id +
+
+
+ +

Rezepte

+ + + + + + + + + @foreach (var recipeShoppingList in Model.RecipeShoppingList) + { + + + + + } + +
RezeptnameZutaten
@recipeShoppingList.Recipe.Name +
    + @foreach (var ingredient in recipeShoppingList.SelectedIngredients) + { +
  • @ingredient.RecipeIngredient.Ingredient.Name - @ingredient.RecipeIngredient.Quantity @ingredient.RecipeIngredient.Unit.Name
  • + } +
+
From 127a60b6217a198b264ec16ec8a5beff2b9147d2 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 9 Apr 2025 15:37:44 +0200 Subject: [PATCH 034/183] Refactoring some Code and clean Code --- .../Controller/Category/CategoryController.cs | 17 ++---- .../MediaFile/MediaFileController.cs | 32 +++++++++- .../Controller/Recipe/RecipeController.cs | 51 ++++++++-------- .../ShoppingList/ShoppingListController.cs | 12 +--- .../RecipeShoppingList/RecipeShoppingList.cs | 2 +- .../CreateOrAddIngredientRequestModel.cs | 9 +++ Francesco.Recipes.World/Program.cs | 4 +- .../Category/CategoryRepository.cs | 7 +++ .../Category/ICategoryRepository.cs | 2 + .../Repositories/Favorit/FavoritRepository.cs | 2 +- ...itRepository.cs => IFavoriteRepository.cs} | 2 +- .../Ingredient/IngredientRepository.cs | 2 - .../Instruction/IInstructionRepository.cs | 3 +- .../Instruction/InstructionRepository.cs | 29 +++++---- .../MediaFile/IMediaFileRepository.cs | 2 +- .../MediaFile/MediaFileRepository.cs | 60 +++++++------------ .../Repositories/Recipe/IRecipeRepository.cs | 2 +- .../Repositories/Recipe/RecipeRepository.cs | 6 +- .../ShoppingList/ShoppingListRepository.cs | 2 - 19 files changed, 132 insertions(+), 114 deletions(-) create mode 100644 Francesco.Recipes.World/Models/CreateOrAddIngredientRequestModel.cs rename Francesco.Recipes.World/Repositories/Favorit/{IFavoritRepository.cs => IFavoriteRepository.cs} (89%) diff --git a/Francesco.Recipes.World/Controller/Category/CategoryController.cs b/Francesco.Recipes.World/Controller/Category/CategoryController.cs index 29b06b5..f077af7 100644 --- a/Francesco.Recipes.World/Controller/Category/CategoryController.cs +++ b/Francesco.Recipes.World/Controller/Category/CategoryController.cs @@ -1,13 +1,8 @@ namespace Francesco.Recipes.World.Controller.Category { - using Francesco.Recipes.World.Models.BackendModels.Category; - using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Repositories.Category; using Microsoft.AspNetCore.Mvc; - [ValidateAntiForgeryToken] - [Route("Category")] - public class CategoryController : Controller { private readonly ICategoryRepository _categoryRepository; @@ -19,7 +14,7 @@ // GET: /Category [HttpGet] - public async Task>> Index() + public async Task Index() { var categories = await _categoryRepository.GetAllCategoriesAsync(); return View(categories); @@ -29,16 +24,16 @@ [HttpGet("{id:guid}")] public async Task Details(Guid id) { - var category = await _categoryRepository.GetCategoryByIdAsync(id); - return View(category); + var category = await _categoryRepository.GetCategoryByIdAsync(id); + return View(category); } // GET: /Category/{id}/recipes [HttpGet("{id:guid}/recipes")] - public async Task>> GetRecipesByCategory(Guid id) + public async Task GetRecipesByCategory(Guid id) { - var recipes = await _categoryRepository.GetRecipesByCategoryAsync(id); - return View(recipes); + var recipes = await _categoryRepository.GetRecipesByCategoryAsync(id); + return Ok(recipes); } } } diff --git a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs index 14f59fe..f021a8f 100644 --- a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs +++ b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs @@ -4,9 +4,7 @@ using Francesco.Recipes.World.Repositories.MediaFile; using Microsoft.AspNetCore.Mvc; - [ValidateAntiForgeryToken] - - [Route("categories/{categoryId}/Recipe")] + [Route("Category/{categoryId}/Recipe")] public class MediaFileController : Controller { private readonly IMediaFileRepository _mediaFileRepository; @@ -20,6 +18,7 @@ // POST: /UploadImage [HttpPost("UploadImage")] + [AutoValidateAntiforgeryToken] public async Task UploadImage(Guid recipeId, IFormFile? mediaFile) { if (mediaFile is null) @@ -44,5 +43,32 @@ { return View(); } + + [HttpPost("ReplaceInstructionImage")] + [ValidateAntiForgeryToken] + public async Task ReplaceInstructionImage(Guid instructionId, Guid mediaFileIdToReplace, IFormFile? newPhoto) + { + if (newPhoto is null) + { + return BadRequest("Photo is required."); + } + + using (var memoryStream = new MemoryStream()) + { + await newPhoto.CopyToAsync(memoryStream); + + var newMediaData = memoryStream.ToArray(); + + try + { + await _mediaFileRepository.ReplaceInstructionImageAsync(instructionId, mediaFileIdToReplace, newPhoto.FileName, newPhoto.ContentType, newMediaData); + return Ok("Image replaced successfully."); + } + catch (Exception ex) + { + return StatusCode(500, $"Internal server error: {ex.Message}"); + } + } + } } } diff --git a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs index 05d99aa..b86ef61 100644 --- a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs +++ b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs @@ -1,6 +1,5 @@ namespace Francesco.Recipes.World.Controller.Recipe { - using System.ComponentModel.DataAnnotations; using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Repositories.Category; using Francesco.Recipes.World.Repositories.Favorit; @@ -14,8 +13,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; - [ValidateAntiForgeryToken] - [Route("Recipe")] public class RecipeController : Controller { private readonly IRecipeRepository _recipeRepository; @@ -24,13 +21,18 @@ private readonly IIngredientRepository _ingredientRepository; private readonly IMediaFileRepository _mediaFileRepository; private readonly IInstructionRepository _instructionRepository; - private readonly IFavoritRepository _favoritRepository; + private readonly IFavoriteRepository _favoriteRepository; - [Display(Name = "Schwierigkeitsgrad")] - [BindProperty(SupportsGet = true)] - public Difficulty? SelectedDifficulty { get; set; } + public IReadOnlyCollection Recipes { get; set; } - public RecipeController(IRecipeRepository recipeRepository, IUnitRepository unitRepository, ICategoryRepository categoryRepository, IIngredientRepository ingredientRepository, IMediaFileRepository mediaFileRepository, IInstructionRepository instructionRepository, IFavoritRepository favoritRepository) + public RecipeController( + IRecipeRepository recipeRepository, + IUnitRepository unitRepository, + ICategoryRepository categoryRepository, + IIngredientRepository ingredientRepository, + IMediaFileRepository mediaFileRepository, + IInstructionRepository instructionRepository, + IFavoriteRepository favoriteRepository) { _recipeRepository = recipeRepository; _unitRepository = unitRepository; @@ -39,11 +41,9 @@ Recipes = new List(); _mediaFileRepository = mediaFileRepository; _instructionRepository = instructionRepository; - _favoritRepository = favoritRepository; + _favoriteRepository = favoriteRepository; } - public IReadOnlyCollection Recipes { get; set; } - // GET: /Recipe/{recipeId}/AddOrCreateIngredient [HttpGet("{recipeId}/AddOrCreateIngredient")] public async Task AddOrCreateIngredient(Guid recipeId) @@ -71,24 +71,20 @@ [HttpGet("CategoryRecipes")] public async Task CategoryRecipes() { - var categories = await _categoryRepository.GetAllCategoriesAsync(); - var viewModel = new List(); + var categories = await _categoryRepository.GetAllCategoriesWithRecipesAsync(); - foreach (var category in categories) + var viewModel = categories.Select(c => new CategoryRecipesViewModel { - var recipes = await _categoryRepository.GetRecipesByCategoryAsync(category.Id); - viewModel.Add(new CategoryRecipesViewModel - { - Category = category, - Recipes = recipes, - }); - } + Category = c, + Recipes = c.Recipes, + }).ToList(); return View(viewModel); } // POST: /Recipe/{recipeId}/AddOrCreateIngredient [HttpPost("{recipeId}/AddOrCreateIngredient")] + [ValidateAntiForgeryToken] public async Task AddOrCreateIngredient(Guid recipeId, string ingredientName, int quantity, Guid unitId) { if (quantity <= 0) @@ -123,6 +119,7 @@ // POST: /Recipe/Create/{categoryId} [HttpPost("Create/{categoryId}")] + [AutoValidateAntiforgeryToken] public async Task Create(Guid categoryId, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime, IFormFile? photo) { if (string.IsNullOrWhiteSpace(name)) @@ -153,6 +150,7 @@ return NotFound($"Kategorie mit ID {categoryId} wurde nicht gefunden."); } + await _recipeRepository.CreateRecipeForCategoryAsync(categoryEntity, name, description, difficulty, servings, preparationTime, cookingTime); var recipe = await _recipeRepository.CreateRecipeForCategoryAsync(categoryEntity, name, description, difficulty, servings, preparationTime, cookingTime); if (photo != null) { @@ -176,13 +174,13 @@ ViewBag.RecipeId = recipeId; ViewBag.IngredientId = ingredientId; - ViewBag.IngredientName = ingredient.Name; return View(); } // POST: /Recipe/{recipeId}/RemoveIngredient/{ingredientId} [HttpPost("{recipeId}/RemoveIngredient/{ingredientId}")] + [ValidateAntiForgeryToken] public async Task RemoveIngredientConfirmed(Guid recipeId, Guid ingredientId) { await _recipeRepository.RemoveIngredientFromRecipeAsync(recipeId, ingredientId); @@ -219,6 +217,7 @@ // POST: /Recipe/{recipeId}/AddInstruction [HttpPost("{recipeId}/AddInstruction")] + [ValidateAntiForgeryToken] public async Task AddInstruction(Guid recipeId, string description, int number) { try @@ -239,23 +238,25 @@ [HttpGet("Favorites")] public async Task Favorites() { - var favoriteRecipes = await _favoritRepository.GetFavoriteRecipesAsync(); + var favoriteRecipes = await _favoriteRepository.GetFavoriteRecipesAsync(); return View(favoriteRecipes); } // POST: /Recipe/AddFavorite [HttpPost("AddFavorite")] + [ValidateAntiForgeryToken] public async Task AddFavorite(Guid recipeId) { - await _favoritRepository.AddFavoriteAsync(recipeId); + await _favoriteRepository.AddFavoriteAsync(recipeId); return RedirectToAction("Details", new { recipeId }); } // POST: /Recipe/RemoveFavorite [HttpPost("RemoveFavorite")] + [ValidateAntiForgeryToken] public async Task RemoveFavorite(Guid recipeId) { - await _favoritRepository.RemoveFavoriteAsync(recipeId); + await _favoriteRepository.RemoveFavoriteAsync(recipeId); return RedirectToAction("Details", new { recipeId }); } } diff --git a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs index 7f6a434..7747880 100644 --- a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs +++ b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs @@ -1,11 +1,11 @@ namespace Francesco.Recipes.World.Controllers { using Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Models; using Francesco.Recipes.World.Repositories.ShoppingList; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; - [Route("ShoppingList")] public class ShoppingListController : Controller { private readonly IShoppingListRepository _shoppingListRepository; @@ -18,7 +18,8 @@ } [HttpPost("CreateOrAddIngredients")] - public async Task CreateOrAddIngredients([FromBody] CreateOrAddIngredientsRequest request) + [ValidateAntiForgeryToken] + public async Task CreateOrAddIngredients([FromBody] CreateOrAddIngredientRequestModel request) { if (request == null || request.IngredientIds == null || !request.IngredientIds.Any()) { @@ -39,12 +40,5 @@ return Json(new { shoppingListId = shoppingList.Id }); } - - public class CreateOrAddIngredientsRequest - { - public Guid RecipeId { get; set; } - - public List IngredientIds { get; set; } = new (); - } } } diff --git a/Francesco.Recipes.World/Models/BackendModels/RecipeShoppingList/RecipeShoppingList.cs b/Francesco.Recipes.World/Models/BackendModels/RecipeShoppingList/RecipeShoppingList.cs index 1173425..d7ba63d 100644 --- a/Francesco.Recipes.World/Models/BackendModels/RecipeShoppingList/RecipeShoppingList.cs +++ b/Francesco.Recipes.World/Models/BackendModels/RecipeShoppingList/RecipeShoppingList.cs @@ -10,7 +10,7 @@ public virtual ShoppingList ShoppingList { get; set; } = new ShoppingList(); - public virtual Recipe Recipe { get; set; } = new (); + public virtual Recipe Recipe { get; set; } = new Recipe(); public virtual ICollection SelectedIngredients { get; set; } = new List(); } diff --git a/Francesco.Recipes.World/Models/CreateOrAddIngredientRequestModel.cs b/Francesco.Recipes.World/Models/CreateOrAddIngredientRequestModel.cs new file mode 100644 index 0000000..8843bd8 --- /dev/null +++ b/Francesco.Recipes.World/Models/CreateOrAddIngredientRequestModel.cs @@ -0,0 +1,9 @@ +namespace Francesco.Recipes.World.Models +{ + public class CreateOrAddIngredientRequestModel + { + public Guid RecipeId { get; set; } + + public List IngredientIds { get; set; } = new (); + } +} diff --git a/Francesco.Recipes.World/Program.cs b/Francesco.Recipes.World/Program.cs index 6f98ff1..f5f164d 100644 --- a/Francesco.Recipes.World/Program.cs +++ b/Francesco.Recipes.World/Program.cs @@ -39,7 +39,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); +builder.Services.AddScoped(); var app = builder.Build(); @@ -64,6 +64,6 @@ app.MapDefaultControllerRoute(); app.MapControllerRoute( name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}"); + pattern: "{controller=Category}/{action=Index}/{id?}"); app.Run(); diff --git a/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs b/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs index 6e372f8..39e7228 100644 --- a/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs +++ b/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs @@ -41,5 +41,12 @@ return category.Recipes; } + + public async Task> GetAllCategoriesWithRecipesAsync() + { + return await _context.Categories + .Include(c => c.Recipes) + .ToListAsync(); + } } } diff --git a/Francesco.Recipes.World/Repositories/Category/ICategoryRepository.cs b/Francesco.Recipes.World/Repositories/Category/ICategoryRepository.cs index 04491aa..f449de0 100644 --- a/Francesco.Recipes.World/Repositories/Category/ICategoryRepository.cs +++ b/Francesco.Recipes.World/Repositories/Category/ICategoryRepository.cs @@ -13,5 +13,7 @@ Task> GetAllCategoriesAsync(); Task> GetRecipesByCategoryAsync(Guid categoryId); + + Task> GetAllCategoriesWithRecipesAsync(); } } diff --git a/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs b/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs index b4eddb5..eb8cae2 100644 --- a/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs +++ b/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs @@ -4,7 +4,7 @@ using Francesco.Recipes.World.Models.BackendModels.Recipe; using Microsoft.EntityFrameworkCore; - public class FavoritRepository : IFavoritRepository + public class FavoritRepository : IFavoriteRepository { private readonly FrancescosRecipesWorldDbContext _context; diff --git a/Francesco.Recipes.World/Repositories/Favorit/IFavoritRepository.cs b/Francesco.Recipes.World/Repositories/Favorit/IFavoriteRepository.cs similarity index 89% rename from Francesco.Recipes.World/Repositories/Favorit/IFavoritRepository.cs rename to Francesco.Recipes.World/Repositories/Favorit/IFavoriteRepository.cs index 8f47e0a..bf1fd13 100644 --- a/Francesco.Recipes.World/Repositories/Favorit/IFavoritRepository.cs +++ b/Francesco.Recipes.World/Repositories/Favorit/IFavoriteRepository.cs @@ -2,7 +2,7 @@ { using Francesco.Recipes.World.Models.BackendModels.Recipe; - public interface IFavoritRepository + public interface IFavoriteRepository { Task> GetFavoriteRecipesAsync(); diff --git a/Francesco.Recipes.World/Repositories/Ingredient/IngredientRepository.cs b/Francesco.Recipes.World/Repositories/Ingredient/IngredientRepository.cs index 69a21ed..6f85a97 100644 --- a/Francesco.Recipes.World/Repositories/Ingredient/IngredientRepository.cs +++ b/Francesco.Recipes.World/Repositories/Ingredient/IngredientRepository.cs @@ -37,7 +37,6 @@ namespace Francesco.Recipes.World.Repositories.Ingredient existingRecipeIngredient.Ingredient = recipeIngredient.Ingredient; existingRecipeIngredient.Unit = recipeIngredient.Unit; - _context.RecipeIngredients.Update(existingRecipeIngredient); await _context.SaveChangesAsync(); } @@ -77,7 +76,6 @@ namespace Francesco.Recipes.World.Repositories.Ingredient existingIngredient.Name = ingredient.Name; - _context.Ingredients.Update(existingIngredient); await _context.SaveChangesAsync(); } diff --git a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs index 1d22238..963099b 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs @@ -1,7 +1,6 @@ namespace Francesco.Recipes.World.Repositories.Instruction { using Francesco.Recipes.World.Models.BackendModels.Instruction; - using Francesco.Recipes.World.Models.BackendModels.Recipe; public interface IInstructionRepository { @@ -11,6 +10,6 @@ Task> GetInstructionsByRecipeIdAsync(Guid recipeId); - Task RemoveInstructionFromRecipeAsync(Recipe recipe, Guid instructionId); + Task RemoveInstructionFromRecipeAsync(Guid recipeId, Guid instructionId); } } diff --git a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs index 09561c5..b487d57 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs @@ -2,7 +2,6 @@ { using Francesco.Recipes.World.Data; using Francesco.Recipes.World.Models.BackendModels.Instruction; - using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Repositories.Recipe; using Microsoft.EntityFrameworkCore; @@ -25,19 +24,19 @@ public async Task CreateInstructionToRecipeAsync(Guid recipeId, string description, int number) { - var recipe = await _recipeRepository.GetRecipeAsync(recipeId); + var recipe = await _recipeRepository.GetRecipeAsync(recipeId); - if (string.IsNullOrWhiteSpace(description)) + if (string.IsNullOrWhiteSpace(description)) { throw new ArgumentException("Description cannot be empty", nameof(description)); } - if (number <= 0) + if (number <= 0) { throw new ArgumentOutOfRangeException(nameof(number), "Number must be greater than 0."); } - var newInstruction = new Instruction + var newInstruction = new Instruction { Id = Guid.NewGuid(), Description = description, @@ -45,24 +44,28 @@ Recipe = recipe, }; - _context.Instructions.Add(newInstruction); - await _context.SaveChangesAsync(); + _context.Instructions.Add(newInstruction); + await _context.SaveChangesAsync(); - return newInstruction; + return newInstruction; } - public async Task RemoveInstructionFromRecipeAsync(Recipe recipe, Guid instructionId) + public async Task RemoveInstructionFromRecipeAsync(Guid recipeId, Guid instructionId) { - if (recipe == null) + var recipe = await _recipeRepository.GetRecipeAsync(recipeId); + + if (recipe.Instructions == null || !recipe.Instructions.Any()) { - throw new ArgumentNullException(nameof(recipe)); + await _context.Entry(recipe) + .Collection(r => r.Instructions) + .LoadAsync(); } - var instructionToRemove = recipe.Instructions.FirstOrDefault(i => i.Id == instructionId); + var instructionToRemove = recipe.Instructions?.FirstOrDefault(i => i.Id == instructionId); if (instructionToRemove != null) { - recipe.Instructions.Remove(instructionToRemove); + recipe.Instructions?.Remove(instructionToRemove); await _context.SaveChangesAsync(); } } diff --git a/Francesco.Recipes.World/Repositories/MediaFile/IMediaFileRepository.cs b/Francesco.Recipes.World/Repositories/MediaFile/IMediaFileRepository.cs index 879a1b2..9320d8c 100644 --- a/Francesco.Recipes.World/Repositories/MediaFile/IMediaFileRepository.cs +++ b/Francesco.Recipes.World/Repositories/MediaFile/IMediaFileRepository.cs @@ -2,7 +2,7 @@ { public interface IMediaFileRepository { - Task ReplaceInstructionImageAsync(Guid instructionId, Guid mediafileId, IFormFile? newPhoto); + Task ReplaceInstructionImageAsync(Guid instructionId, Guid mediaFileIdToReplace, string fileName, string mimeType, byte[] newMediaData); Task UploadInstructionImageAsync(Guid instructionId, IFormFile? photo); diff --git a/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs b/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs index b4beb26..15f1fc6 100644 --- a/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs +++ b/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs @@ -2,6 +2,7 @@ { using Francesco.Recipes.World.Data; using Francesco.Recipes.World.Models.BackendModels.MediaFile; + using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Repositories.Instruction; using Francesco.Recipes.World.Repositories.Recipe; @@ -18,13 +19,8 @@ _recipeRepository = recipeRepository; } - public async Task ReplaceInstructionImageAsync(Guid instructionId, Guid mediaFileIdToReplace, IFormFile? newPhoto) + public async Task ReplaceInstructionImageAsync(Guid instructionId, Guid mediaFileIdToReplace, string fileName, string mimeType, byte[] newMediaData) { - if (newPhoto is null) - { - throw new ArgumentNullException(nameof(newPhoto)); - } - var instruction = await _instructionRepository.GetInstructionAsync(instructionId); var mediaToReplace = instruction.MediaFiles.FirstOrDefault(m => m.Id == mediaFileIdToReplace); @@ -36,22 +32,17 @@ _context.MediaFiles.Remove(mediaToReplace); await _context.SaveChangesAsync(); - using (var memoryStream = new MemoryStream()) + var newMedia = new MediaFile { - await newPhoto.CopyToAsync(memoryStream); + Id = Guid.NewGuid(), + FileName = fileName, + MimeType = mimeType, + Data = newMediaData, + Instruction = instruction, + }; - var newMedia = new MediaFile - { - Id = Guid.NewGuid(), - FileName = newPhoto.FileName, - MimeType = newPhoto.ContentType, - Data = memoryStream.ToArray(), - Instruction = instruction, - }; - - _context.MediaFiles.Add(newMedia); - await _context.SaveChangesAsync(); - } + _context.MediaFiles.Add(newMedia); + await _context.SaveChangesAsync(); } public async Task UploadInstructionImageAsync(Guid instructionId, IFormFile? photo) @@ -89,11 +80,6 @@ var recipe = await _recipeRepository.GetRecipeAsync(recipeId); - if (recipe == null) - { - throw new InvalidOperationException("The specified recipe does not exist."); - } - var isImage = mediaFile.ContentType.StartsWith("image/"); var isVideo = mediaFile.ContentType.StartsWith("video/"); @@ -104,21 +90,11 @@ if (isImage) { - var existingImage = recipe.MediaFiles?.FirstOrDefault(m => m.MimeType?.StartsWith("image/") == true); - if (existingImage != null) - { - _context.MediaFiles.Remove(existingImage); - await _context.SaveChangesAsync(); - } + await RemoveExistingMediaAsync(recipe, "image/"); } else if (isVideo) { - var existingVideo = recipe.MediaFiles?.FirstOrDefault(m => m.MimeType?.StartsWith("video/") == true); - if (existingVideo != null) - { - _context.MediaFiles.Remove(existingVideo); - await _context.SaveChangesAsync(); - } + await RemoveExistingMediaAsync(recipe, "video/"); } using var memoryStream = new MemoryStream(); @@ -136,5 +112,15 @@ _context.MediaFiles.Add(newMedia); await _context.SaveChangesAsync(); } + + private async Task RemoveExistingMediaAsync(Recipe recipe, string mediaTypePrefix) + { + var existingMedia = recipe.MediaFiles?.FirstOrDefault(m => m.MimeType?.StartsWith(mediaTypePrefix) == true); + if (existingMedia != null) + { + _context.MediaFiles.Remove(existingMedia); + await _context.SaveChangesAsync(); + } + } } } diff --git a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs index c5d46a0..03414e1 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs @@ -13,7 +13,7 @@ Task RemoveIngredientFromRecipeAsync(Guid recipeId, Guid ingredientId); - Task> GetRecipesByNameOrIngredientAsync(string name, string ingredient); + Task> GetRecipesByNameAndIngredientAsync(string name, string ingredient); Task> GetRecipesByDifficultyAsync(Difficulty? difficulty); diff --git a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs index ae33b43..1b7470a 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs @@ -37,6 +37,7 @@ .Include(r => r.RecipeIngredients) .ThenInclude(ri => ri.Unit) .Include(r => r.MediaFiles) + .Include(r => r.Instructions) .FirstOrDefaultAsync(r => r.Id == recipeId); } @@ -53,7 +54,7 @@ var ingredients = await _ingredientRepository.GetIngredientsByNameAsync(ingredientName); Ingredient ingredient; - if (ingredients == null || !ingredients.Any()) + if (!ingredients.Any()) { ingredient = new Ingredient { @@ -131,7 +132,6 @@ public async Task RemoveIngredientFromRecipeAsync(Guid recipeId, Guid ingredientId) { - var recipe = await GetRecipeAsync(recipeId); var recipeIngredient = await _context.RecipeIngredients .FirstOrDefaultAsync(ri => ri.Recipe.Id == recipeId && ri.Ingredient.Id == ingredientId); @@ -144,7 +144,7 @@ await _context.SaveChangesAsync(); } - public async Task> GetRecipesByNameOrIngredientAsync(string name, string ingredient) + public async Task> GetRecipesByNameAndIngredientAsync(string name, string ingredient) { var query = _context.Recipes .Include(r => r.RecipeIngredients) diff --git a/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs b/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs index 45793f0..c3e8590 100644 --- a/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs +++ b/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs @@ -114,7 +114,6 @@ public async Task> GetIngredientsOfRecipeInListAsync(Guid shoppingListRecipeId) { return await _context.RecipeIngredientsShoppingLists - .AsNoTracking() .Include(i => i.RecipeIngredient) .ThenInclude(ri => ri.Ingredient) .Include(i => i.RecipeIngredient.Unit) @@ -168,7 +167,6 @@ public async Task GetRecipeByNameAndImageAsync(string recipeName, string imageFileName) { return await _context.Recipes - .AsNoTracking() .Where(r => r.Name == recipeName && r.MediaFiles.Any(mf => mf.FileName == imageFileName)) .Include(r => r.MediaFiles.Where(mf => mf.FileName == imageFileName)) .FirstOrDefaultAsync(); From 603dc4a2c60df7d72cb715cb587a8f50b7530fd2 Mon Sep 17 00:00:00 2001 From: franc Date: Thu, 10 Apr 2025 10:23:43 +0200 Subject: [PATCH 035/183] All thread resolved --- .../MediaFile/MediaFileController.cs | 2 +- .../ShoppingList/ShoppingListController.cs | 13 ++++++------- Francesco.Recipes.World/Program.cs | 1 - .../MediaFile/MediaFileRepository.cs | 2 +- .../Repositories/Recipe/RecipeRepository.cs | 7 +++++-- .../ShoppingList/IShoppingListRepository.cs | 3 ++- .../ShoppingList/ShoppingListRepository.cs | 18 +++++++----------- .../Views/Shared/_Layout.cshtml | 1 + 8 files changed, 23 insertions(+), 24 deletions(-) diff --git a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs index f021a8f..0d9ddf3 100644 --- a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs +++ b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs @@ -45,7 +45,7 @@ } [HttpPost("ReplaceInstructionImage")] - [ValidateAntiForgeryToken] + [AutoValidateAntiforgeryToken] public async Task ReplaceInstructionImage(Guid instructionId, Guid mediaFileIdToReplace, IFormFile? newPhoto) { if (newPhoto is null) diff --git a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs index 7747880..648cc7a 100644 --- a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs +++ b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs @@ -4,7 +4,6 @@ using Francesco.Recipes.World.Models; using Francesco.Recipes.World.Repositories.ShoppingList; using Microsoft.AspNetCore.Mvc; - using Microsoft.EntityFrameworkCore; public class ShoppingListController : Controller { @@ -21,17 +20,17 @@ [ValidateAntiForgeryToken] public async Task CreateOrAddIngredients([FromBody] CreateOrAddIngredientRequestModel request) { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + if (request == null || request.IngredientIds == null || !request.IngredientIds.Any()) { return BadRequest("No ingredients provided."); } - await _shoppingListRepository.AddIngredientsToShoppingListAsync(request.RecipeId, request.IngredientIds); - - var shoppingList = await _context.ShoppingLists - .Include(sl => sl.RecipeShoppingList) - .ThenInclude(rsl => rsl.Recipe) - .FirstOrDefaultAsync(sl => sl.RecipeShoppingList.Any(rsl => rsl.Recipe.Id == request.RecipeId)); + var shoppingList = await _shoppingListRepository.AddIngredientsToShoppingListAsync(request.RecipeId, request.IngredientIds); if (shoppingList == null) { diff --git a/Francesco.Recipes.World/Program.cs b/Francesco.Recipes.World/Program.cs index f5f164d..40317b6 100644 --- a/Francesco.Recipes.World/Program.cs +++ b/Francesco.Recipes.World/Program.cs @@ -7,7 +7,6 @@ using Francesco.Recipes.World.Repositories.MediaFile; using Francesco.Recipes.World.Repositories.Recipe; using Francesco.Recipes.World.Repositories.ShoppingList; using Francesco.Recipes.World.Repositories.Unit; - using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); diff --git a/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs b/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs index 15f1fc6..ef3babb 100644 --- a/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs +++ b/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs @@ -92,7 +92,7 @@ { await RemoveExistingMediaAsync(recipe, "image/"); } - else if (isVideo) + else { await RemoveExistingMediaAsync(recipe, "video/"); } diff --git a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs index 1b7470a..f71303f 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs @@ -52,9 +52,11 @@ } var ingredients = await _ingredientRepository.GetIngredientsByNameAsync(ingredientName); + var exactMatch = ingredients + .FirstOrDefault(i => i.Name.Equals(ingredientName, StringComparison.OrdinalIgnoreCase)); Ingredient ingredient; - if (!ingredients.Any()) + if (exactMatch == null) { ingredient = new Ingredient { @@ -66,7 +68,7 @@ } else { - ingredient = ingredients.First(); + ingredient = exactMatch; } var existingEntry = await _context.RecipeIngredients @@ -83,6 +85,7 @@ Unit = unit, Quantity = quantity, }; + _context.Add(recipeIngredient); await _context.SaveChangesAsync(); } diff --git a/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs b/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs index 5411e89..90c35c0 100644 --- a/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs +++ b/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs @@ -2,10 +2,11 @@ { using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; using Francesco.Recipes.World.Models.BackendModels.Recipe; + using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; public interface IShoppingListRepository { - Task AddIngredientsToShoppingListAsync(Guid shoppingListId, List ingredientIds); + Task AddIngredientsToShoppingListAsync(Guid recipeId, List ingredientIds); Task> GetIngredientsOfRecipeInListAsync(Guid shoppingListRecipeId); diff --git a/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs b/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs index c3e8590..16d8934 100644 --- a/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs +++ b/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs @@ -16,7 +16,7 @@ _context = context; } - public async Task AddIngredientsToShoppingListAsync(Guid recipeId, List ingredientIds) + public async Task AddIngredientsToShoppingListAsync(Guid recipeId, List ingredientIds) { if (ingredientIds == null || !ingredientIds.Any()) { @@ -29,13 +29,12 @@ if (recipe == null) { - throw new Exception("Rezept nicht gefunden."); + throw new Exception("Recipe not found."); } var shoppingList = await _context.ShoppingLists .Include(sl => sl.RecipeShoppingList) .ThenInclude(rsl => rsl.SelectedIngredients) - .ThenInclude(si => si.RecipeIngredient) .Include(sl => sl.RecipeShoppingList) .ThenInclude(rsl => rsl.Recipe) .FirstOrDefaultAsync(sl => sl.RecipeShoppingList.Any(rsl => rsl.Recipe.Id == recipeId)); @@ -46,7 +45,7 @@ if (!recipeIngredients.Any()) { - throw new Exception("Keine gültigen Zutaten gefunden."); + throw new Exception("No valid ingredients found."); } if (shoppingList == null) @@ -55,7 +54,6 @@ { Id = Guid.NewGuid(), CreatedAt = DateTime.UtcNow, - ModifiedAt = null, RecipeShoppingList = new List(), }; @@ -93,10 +91,7 @@ foreach (var ri in recipeIngredients) { - var alreadyExists = existingRecipeList.SelectedIngredients - .Any(si => si.RecipeIngredient.Id == ri.Id); - - if (!alreadyExists) + if (!existingRecipeList.SelectedIngredients.Any(si => si.RecipeIngredient.Id == ri.Id)) { existingRecipeList.SelectedIngredients.Add(new RecipeIngredientShoppingList { @@ -109,6 +104,9 @@ shoppingList.ModifiedAt = DateTime.UtcNow; } + + await _context.SaveChangesAsync(); + return shoppingList; } public async Task> GetIngredientsOfRecipeInListAsync(Guid shoppingListRecipeId) @@ -153,8 +151,6 @@ public async Task DeleteShoppingListAsync(Guid shoppingListId) { var list = await _context.ShoppingLists - .Include(sl => sl.RecipeShoppingList) - .ThenInclude(r => r.SelectedIngredients) .FirstOrDefaultAsync(sl => sl.Id == shoppingListId); if (list != null) diff --git a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml index 1f862ba..9d445a9 100644 --- a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml @@ -44,6 +44,7 @@ + @await RenderSectionAsync("Scripts", required: false) From 7d11eaf7efabeb15b14f1d923d68d1df2881ed3b Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 15 Apr 2025 16:10:42 +0200 Subject: [PATCH 036/183] Implement two methods for the sorting logic on the service --- .../Instruction/IInstructionRepository.cs | 4 ++ .../Instruction/InstructionRepository.cs | 39 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs index 963099b..528f6b8 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs @@ -11,5 +11,9 @@ Task> GetInstructionsByRecipeIdAsync(Guid recipeId); Task RemoveInstructionFromRecipeAsync(Guid recipeId, Guid instructionId); + + Task SwapInstructionOrderAsync(Instruction a, Instruction b); + + Task GetInstructionWithRecipeAsync(Guid instructionId); } } diff --git a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs index b487d57..0a71b5f 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs @@ -77,5 +77,44 @@ .Where(i => i.Recipe.Id == recipeId) .ToListAsync(); } + + public async Task GetInstructionWithRecipeAsync(Guid instructionId) + { + var instruction = await _context.Instructions + .Include(i => i.Recipe) + .ThenInclude(r => r.Instructions) + .FirstOrDefaultAsync(i => i.Id == instructionId); + + if (instruction == null) + { + throw new InvalidDataException($"Instruction with ID {instructionId} not found."); + } + + if (instruction.Recipe == null) + { + throw new InvalidDataException($"The Recipe for Instruction with ID {instructionId} is not loaded or does not exist."); + } + + return instruction; + } + + public async Task SwapInstructionOrderAsync(Instruction a, Instruction b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a), "Instruction 'a' cannot be null."); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b), "Instruction 'b' cannot be null."); + } + + var temp = a.Number; + a.Number = b.Number; + b.Number = temp; + + await _context.SaveChangesAsync(); + } } } From 71ebfcf4bee42b279c74490aaa4d86b96544ca1a Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 15 Apr 2025 16:11:07 +0200 Subject: [PATCH 037/183] Implement the Sorting Logic --- .../Instruction/IInstructionService.cs | 9 ++++ .../Instruction/InstructionService.cs | 54 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 Francesco.Recipes.World/Services/Instruction/IInstructionService.cs create mode 100644 Francesco.Recipes.World/Services/Instruction/InstructionService.cs diff --git a/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs b/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs new file mode 100644 index 0000000..fa514a1 --- /dev/null +++ b/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs @@ -0,0 +1,9 @@ +namespace Francesco.Recipes.World.Services.Instruction +{ + public interface IInstructionService + { + Task MoveInstructionUpAsync(Guid instructionId); + + Task MoveInstructionDownAsync(Guid instructionId); + } +} diff --git a/Francesco.Recipes.World/Services/Instruction/InstructionService.cs b/Francesco.Recipes.World/Services/Instruction/InstructionService.cs new file mode 100644 index 0000000..5015426 --- /dev/null +++ b/Francesco.Recipes.World/Services/Instruction/InstructionService.cs @@ -0,0 +1,54 @@ +using Francesco.Recipes.World.Repositories.Instruction; + +namespace Francesco.Recipes.World.Services.Instruction +{ + public class InstructionService : IInstructionService + { + private readonly IInstructionRepository _instructionRepository; + + public InstructionService(IInstructionRepository instructionRepository) + { + _instructionRepository = instructionRepository; + } + + public async Task MoveInstructionDownAsync(Guid instructionId) + { + var instruction = await _instructionRepository.GetInstructionWithRecipeAsync(instructionId); + + var instructions = await _instructionRepository.GetInstructionsByRecipeIdAsync(instruction.Recipe.Id); + + var maxStep = instructions.Max(i => i.Number); + + if (instruction.Number >= maxStep) + { + return; + } + + var neighbor = instructions.FirstOrDefault(i => i.Number == instruction.Number + 1); + + if (neighbor != null) + { + await _instructionRepository.SwapInstructionOrderAsync(instruction, neighbor); + } + } + + public async Task MoveInstructionUpAsync(Guid instructionId) + { + var instruction = await _instructionRepository.GetInstructionWithRecipeAsync(instructionId); + + if (instruction.Number == 1) + { + return; + } + + var instructions = await _instructionRepository.GetInstructionsByRecipeIdAsync(instruction.Recipe.Id); + + var neighbor = instructions.FirstOrDefault(i => i.Number == instruction.Number - 1); + + if (neighbor != null) + { + await _instructionRepository.SwapInstructionOrderAsync(instruction, neighbor); + } + } + } +} From 84c75098cf6ea29bac082473b903f001e934fc0c Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 15 Apr 2025 16:20:08 +0200 Subject: [PATCH 038/183] Put one Method to Count all ShoppingListsRecipe --- .../Francesco.Recipes.World.csproj | 2 -- .../ShoppingList/IShoppingListService.cs | 7 ++++++ .../ShoppingList/ShoppingListService.cs | 22 +++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 Francesco.Recipes.World/Services/ShoppingList/IShoppingListService.cs create mode 100644 Francesco.Recipes.World/Services/ShoppingList/ShoppingListService.cs diff --git a/Francesco.Recipes.World/Francesco.Recipes.World.csproj b/Francesco.Recipes.World/Francesco.Recipes.World.csproj index 7d430a2..84f6487 100644 --- a/Francesco.Recipes.World/Francesco.Recipes.World.csproj +++ b/Francesco.Recipes.World/Francesco.Recipes.World.csproj @@ -49,8 +49,6 @@ - - diff --git a/Francesco.Recipes.World/Services/ShoppingList/IShoppingListService.cs b/Francesco.Recipes.World/Services/ShoppingList/IShoppingListService.cs new file mode 100644 index 0000000..702c426 --- /dev/null +++ b/Francesco.Recipes.World/Services/ShoppingList/IShoppingListService.cs @@ -0,0 +1,7 @@ +namespace Francesco.Recipes.World.Services.ShoppingList +{ + public interface IShoppingListService + { + Task GetShoppingListRecipeCountAsync(Guid shoppingListId); + } +} diff --git a/Francesco.Recipes.World/Services/ShoppingList/ShoppingListService.cs b/Francesco.Recipes.World/Services/ShoppingList/ShoppingListService.cs new file mode 100644 index 0000000..2428d90 --- /dev/null +++ b/Francesco.Recipes.World/Services/ShoppingList/ShoppingListService.cs @@ -0,0 +1,22 @@ +using Francesco.Recipes.World.Data; +using Microsoft.EntityFrameworkCore; + +namespace Francesco.Recipes.World.Services.ShoppingList +{ + public class ShoppingListService + { + private readonly FrancescosRecipesWorldDbContext _context; + + public ShoppingListService(FrancescosRecipesWorldDbContext context) + { + _context = context; + } + + public async Task GetShoppingListRecipeCountAsync(Guid shoppingListId) + { + return await _context.RecipeShoppingLists + .Where(rsl => rsl.ShoppingList.Id == shoppingListId) + .CountAsync(); + } + } +} From 2f33951c4e10e52dee5938fb6e271fdd00b87cc8 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 16 Apr 2025 11:17:42 +0200 Subject: [PATCH 039/183] Change Search Logic --- .../Repositories/Recipe/IRecipeRepository.cs | 4 +- .../Repositories/Recipe/RecipeRepository.cs | 38 ++++++++----------- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs index 03414e1..0f1d8a3 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs @@ -13,10 +13,10 @@ Task RemoveIngredientFromRecipeAsync(Guid recipeId, Guid ingredientId); - Task> GetRecipesByNameAndIngredientAsync(string name, string ingredient); - Task> GetRecipesByDifficultyAsync(Difficulty? difficulty); Task CreateRecipeForCategoryAsync(Category category, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime); + + Task> GetRecipesBySearchQueryAsync(string query); } } diff --git a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs index f71303f..41f7927 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs @@ -147,30 +147,24 @@ await _context.SaveChangesAsync(); } - public async Task> GetRecipesByNameAndIngredientAsync(string name, string ingredient) + public async Task> GetRecipesBySearchQueryAsync(string query) { - var query = _context.Recipes + if (string.IsNullOrWhiteSpace(query)) + { + return await _context.Recipes + .Include(r => r.RecipeIngredients) + .ThenInclude(ri => ri.Ingredient) + .ToListAsync(); + } + + query = query.ToLower(); + + return await _context.Recipes .Include(r => r.RecipeIngredients) - .ThenInclude(ri => ri.Ingredient) - .AsQueryable(); - - if (!string.IsNullOrWhiteSpace(name)) - { - query = query.Where(r => r.Name.Contains(name, StringComparison.OrdinalIgnoreCase)); - } - - if (!string.IsNullOrWhiteSpace(ingredient)) - { - var ingredientMatches = await _ingredientRepository.GetIngredientsByNameAsync(ingredient); - var ingredientIds = ingredientMatches.Select(i => i.Id).ToList(); - - if (ingredientIds.Any()) - { - query = query.Where(r => r.RecipeIngredients.Any(ri => ingredientIds.Contains(ri.Ingredient.Id))); - } - } - - return await query.ToListAsync(); + .ThenInclude(ri => ri.Ingredient) + .Where(r => r.Name.ToLower().Contains(query) || + r.RecipeIngredients.Any(ri => ri.Ingredient.Name.ToLower().Contains(query))) + .ToListAsync(); } public async Task> GetRecipesByDifficultyAsync(Difficulty? difficulty) From 3ec9d4be7995139bbb33f43e8ee70d209e221e70 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 16 Apr 2025 11:18:10 +0200 Subject: [PATCH 040/183] Create main Layout for Application/Web-Side --- .../Views/Shared/_Layout.cshtml | 111 +++++++++++------- 1 file changed, 70 insertions(+), 41 deletions(-) diff --git a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml index 9d445a9..fa98366 100644 --- a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml @@ -1,50 +1,79 @@  - - - @ViewData["Title"] - Francesco.Recipes.World - - - + + + @ViewData["Title"] - Francesco.Recipes.World + + + -
- -
-
-
- @RenderBody() -
-
+
+ +
+
+
+ @RenderBody() +
+
-
-
- © 2024 - Francesco.Recipes.World - Privacy -
-
- - - +
+
+ © 2024 - Francesco.Recipes.World - Privacy +
+
+ + + - @await RenderSectionAsync("Scripts", required: false) + + @await RenderSectionAsync("Scripts", required: false) + From c8ff0dd9dd6e941db3e81b959f6af60e882f16f1 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 16 Apr 2025 11:20:33 +0200 Subject: [PATCH 041/183] Make a searchResultPartial and initial HomePage with filter system --- .../Views/Home/Index.cshtml | 6 +++ .../Views/Home/_SearchResultsPartial.cshtml | 54 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 Francesco.Recipes.World/Views/Home/Index.cshtml create mode 100644 Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml diff --git a/Francesco.Recipes.World/Views/Home/Index.cshtml b/Francesco.Recipes.World/Views/Home/Index.cshtml new file mode 100644 index 0000000..8de8ea4 --- /dev/null +++ b/Francesco.Recipes.World/Views/Home/Index.cshtml @@ -0,0 +1,6 @@ +
+ +
+ +
+ diff --git a/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml b/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml new file mode 100644 index 0000000..e18ad55 --- /dev/null +++ b/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml @@ -0,0 +1,54 @@ +@model IEnumerable + +@if (!Model.Any()) +{ +

Keine Ergebnisse gefunden.

+} +else +{ +
+ @foreach (var recipe in Model) + { +
+
+
+ @{ + var mediaFile = recipe.MediaFiles.FirstOrDefault(); + } + @if (mediaFile != null && mediaFile.Data != null) + { + @recipe.Name + } + else + { + Platzhalter + } +
+ +
+
+
@recipe.Name
+

+ + @recipe.PreparationTime.Hours h @recipe.PreparationTime.Minutes min +

+
+ +
+ +
+
+
+
+ } +
+} From eb6d82a979662d354a8062224d1e53e8d29860fb Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 16 Apr 2025 11:22:09 +0200 Subject: [PATCH 042/183] Add HomeController with search endpoint for querying recipes --- .../HomeController/HomeController.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 Francesco.Recipes.World/Controller/HomeController/HomeController.cs diff --git a/Francesco.Recipes.World/Controller/HomeController/HomeController.cs b/Francesco.Recipes.World/Controller/HomeController/HomeController.cs new file mode 100644 index 0000000..0b1a5a0 --- /dev/null +++ b/Francesco.Recipes.World/Controller/HomeController/HomeController.cs @@ -0,0 +1,25 @@ +namespace Francesco.Recipes.World.Controller.Home +{ + using Francesco.Recipes.World.Repositories.Category; + using Francesco.Recipes.World.Repositories.Recipe; + using Microsoft.AspNetCore.Mvc; + + public class HomeController : Controller + { + private readonly ICategoryRepository _categoryRepository; + private readonly IRecipeRepository _recipeRepository; + + public HomeController(ICategoryRepository categoryRepository, IRecipeRepository recipeRepository) + { + _categoryRepository = categoryRepository; + _recipeRepository = recipeRepository; + } + + [HttpGet("/Home/Search")] + public async Task Search(string query) + { + var recipes = await _recipeRepository.GetRecipesBySearchQueryAsync(query); + return PartialView("_SearchResultsPartial", recipes); + } + } +} From b8814ce7089d7140b409ae70f7e5d84601995819 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 16 Apr 2025 15:18:39 +0200 Subject: [PATCH 043/183] Correct the Method and rename Method --- .../Repositories/Recipe/IRecipeRepository.cs | 2 +- .../Repositories/Recipe/RecipeRepository.cs | 27 +++++++++---------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs index 0f1d8a3..ca81b4b 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs @@ -17,6 +17,6 @@ Task CreateRecipeForCategoryAsync(Category category, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime); - Task> GetRecipesBySearchQueryAsync(string query); + Task> SerachRecipeAndIngredientAsync(string query); } } diff --git a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs index 41f7927..bd2216b 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs @@ -147,24 +147,21 @@ await _context.SaveChangesAsync(); } - public async Task> GetRecipesBySearchQueryAsync(string query) + public async Task> SerachRecipeAndIngredientAsync(string searchTerm) { - if (string.IsNullOrWhiteSpace(query)) - { - return await _context.Recipes - .Include(r => r.RecipeIngredients) - .ThenInclude(ri => ri.Ingredient) - .ToListAsync(); - } - - query = query.ToLower(); - - return await _context.Recipes + var queryable = _context.Recipes .Include(r => r.RecipeIngredients) .ThenInclude(ri => ri.Ingredient) - .Where(r => r.Name.ToLower().Contains(query) || - r.RecipeIngredients.Any(ri => ri.Ingredient.Name.ToLower().Contains(query))) - .ToListAsync(); + .AsQueryable(); + + if (!string.IsNullOrWhiteSpace(searchTerm)) + { + searchTerm = searchTerm.ToLower(); + queryable = queryable.Where(r => r.Name.ToLower().Contains(searchTerm) || + r.RecipeIngredients.Any(ri => ri.Ingredient.Name.ToLower().Contains(searchTerm))); + } + + return await queryable.ToListAsync(); } public async Task> GetRecipesByDifficultyAsync(Difficulty? difficulty) From 1110c74234d0fefe5257163300f4013fdaaaa7ec Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 16 Apr 2025 15:19:33 +0200 Subject: [PATCH 044/183] Clean the code and put Index EndPoint --- .../Controller/HomeController/HomeController.cs | 15 +++++++++++---- .../Views/Home/_SearchResultsPartial.cshtml | 7 +++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Francesco.Recipes.World/Controller/HomeController/HomeController.cs b/Francesco.Recipes.World/Controller/HomeController/HomeController.cs index 0b1a5a0..b745357 100644 --- a/Francesco.Recipes.World/Controller/HomeController/HomeController.cs +++ b/Francesco.Recipes.World/Controller/HomeController/HomeController.cs @@ -1,4 +1,4 @@ -namespace Francesco.Recipes.World.Controller.Home +namespace Francesco.Recipes.World.Controller.HomeController { using Francesco.Recipes.World.Repositories.Category; using Francesco.Recipes.World.Repositories.Recipe; @@ -6,19 +6,26 @@ public class HomeController : Controller { - private readonly ICategoryRepository _categoryRepository; private readonly IRecipeRepository _recipeRepository; + private readonly ICategoryRepository _categoryRepository; public HomeController(ICategoryRepository categoryRepository, IRecipeRepository recipeRepository) { - _categoryRepository = categoryRepository; _recipeRepository = recipeRepository; + _categoryRepository = categoryRepository; + } + + [HttpGet] + public async Task Index() + { + var categories = await _categoryRepository.GetAllCategoriesWithRecipesAsync(); + return View("Index", categories); } [HttpGet("/Home/Search")] public async Task Search(string query) { - var recipes = await _recipeRepository.GetRecipesBySearchQueryAsync(query); + var recipes = await _recipeRepository.SerachRecipeAndIngredientAsync(query); return PartialView("_SearchResultsPartial", recipes); } } diff --git a/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml b/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml index e18ad55..ee35af1 100644 --- a/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml +++ b/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml @@ -1,6 +1,7 @@ @model IEnumerable -@if (!Model.Any()) +@{ + if (!Model.Any()) {

Keine Ergebnisse gefunden.

} @@ -15,7 +16,7 @@ else @{ var mediaFile = recipe.MediaFiles.FirstOrDefault(); } - @if (mediaFile != null && mediaFile.Data != null) + @if (mediaFile?.Data != null) { @recipe.Name } +} + From 4b084eea9879a8af1ebf0b50dd148e26851dffa8 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 16 Apr 2025 17:22:19 +0200 Subject: [PATCH 045/183] Refactoring some code and change logic and make sure to reduce db calls and avoid redundants --- .../Instruction/IInstructionRepository.cs | 4 +- .../Instruction/InstructionRepository.cs | 21 ++-------- .../Instruction/IInstructionService.cs | 2 + .../Instruction/InstructionService.cs | 42 ++++++++----------- 4 files changed, 25 insertions(+), 44 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs index 528f6b8..057eb02 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs @@ -8,12 +8,10 @@ Task CreateInstructionToRecipeAsync(Guid recipeId, string description, int number); - Task> GetInstructionsByRecipeIdAsync(Guid recipeId); - Task RemoveInstructionFromRecipeAsync(Guid recipeId, Guid instructionId); Task SwapInstructionOrderAsync(Instruction a, Instruction b); - Task GetInstructionWithRecipeAsync(Guid instructionId); + Task> GetInstructionsByInstructionIdAsync(Guid instructionId); } } diff --git a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs index 0a71b5f..a95e22d 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs @@ -70,32 +70,19 @@ } } - public async Task> GetInstructionsByRecipeIdAsync(Guid recipeId) - { - return await _context.Instructions - .Include(i => i.Recipe) - .Where(i => i.Recipe.Id == recipeId) - .ToListAsync(); - } - - public async Task GetInstructionWithRecipeAsync(Guid instructionId) + public async Task> GetInstructionsByInstructionIdAsync(Guid instructionId) { var instruction = await _context.Instructions .Include(i => i.Recipe) .ThenInclude(r => r.Instructions) .FirstOrDefaultAsync(i => i.Id == instructionId); - if (instruction == null) + if (instruction?.Recipe == null) { - throw new InvalidDataException($"Instruction with ID {instructionId} not found."); + throw new InvalidDataException($"Instruction with ID {instructionId} or its Recipe not found."); } - if (instruction.Recipe == null) - { - throw new InvalidDataException($"The Recipe for Instruction with ID {instructionId} is not loaded or does not exist."); - } - - return instruction; + return instruction.Recipe.Instructions.ToList(); } public async Task SwapInstructionOrderAsync(Instruction a, Instruction b) diff --git a/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs b/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs index fa514a1..efb6b00 100644 --- a/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs +++ b/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs @@ -5,5 +5,7 @@ Task MoveInstructionUpAsync(Guid instructionId); Task MoveInstructionDownAsync(Guid instructionId); + + Task MoveInstructionAsync(Guid instructionId, bool moveUp); } } diff --git a/Francesco.Recipes.World/Services/Instruction/InstructionService.cs b/Francesco.Recipes.World/Services/Instruction/InstructionService.cs index 5015426..895cf66 100644 --- a/Francesco.Recipes.World/Services/Instruction/InstructionService.cs +++ b/Francesco.Recipes.World/Services/Instruction/InstructionService.cs @@ -11,39 +11,33 @@ namespace Francesco.Recipes.World.Services.Instruction _instructionRepository = instructionRepository; } - public async Task MoveInstructionDownAsync(Guid instructionId) + public Task MoveInstructionUpAsync(Guid instructionId) + => MoveInstructionAsync(instructionId, moveUp: true); + + public Task MoveInstructionDownAsync(Guid instructionId) + => MoveInstructionAsync(instructionId, moveUp: false); + + private async Task MoveInstructionAsync(Guid instructionId, bool moveUp) { - var instruction = await _instructionRepository.GetInstructionWithRecipeAsync(instructionId); + var instructions = await _instructionRepository.GetInstructionsByInstructionIdAsync(instructionId); - var instructions = await _instructionRepository.GetInstructionsByRecipeIdAsync(instruction.Recipe.Id); + var instruction = instructions.FirstOrDefault(i => i.Id == instructionId); + if (instruction == null) + { + throw new InvalidDataException($"Instruction with ID {instructionId} not found."); + } + + var minStep = 1; var maxStep = instructions.Max(i => i.Number); - if (instruction.Number >= maxStep) + if ((moveUp && instruction.Number == minStep) || (!moveUp && instruction.Number >= maxStep)) { return; } - var neighbor = instructions.FirstOrDefault(i => i.Number == instruction.Number + 1); - - if (neighbor != null) - { - await _instructionRepository.SwapInstructionOrderAsync(instruction, neighbor); - } - } - - public async Task MoveInstructionUpAsync(Guid instructionId) - { - var instruction = await _instructionRepository.GetInstructionWithRecipeAsync(instructionId); - - if (instruction.Number == 1) - { - return; - } - - var instructions = await _instructionRepository.GetInstructionsByRecipeIdAsync(instruction.Recipe.Id); - - var neighbor = instructions.FirstOrDefault(i => i.Number == instruction.Number - 1); + var targetNumber = moveUp ? instruction.Number - 1 : instruction.Number + 1; + var neighbor = instructions.FirstOrDefault(i => i.Number == targetNumber); if (neighbor != null) { From b7706de30c8db5b590caebbc76dee7ea42f5e8d7 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 16 Apr 2025 17:27:08 +0200 Subject: [PATCH 046/183] Remove the interface declaration for MoveInstructionAsync since it's now a private method --- .../Services/Instruction/IInstructionService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs b/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs index efb6b00..8ddedf5 100644 --- a/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs +++ b/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs @@ -6,6 +6,5 @@ Task MoveInstructionDownAsync(Guid instructionId); - Task MoveInstructionAsync(Guid instructionId, bool moveUp); } } From 032e2caf1b956be574d3844517f777ec5cda5e57 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 16 Apr 2025 17:27:41 +0200 Subject: [PATCH 047/183] Remove Blank line --- .../Services/Instruction/IInstructionService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs b/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs index 8ddedf5..fa514a1 100644 --- a/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs +++ b/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs @@ -5,6 +5,5 @@ Task MoveInstructionUpAsync(Guid instructionId); Task MoveInstructionDownAsync(Guid instructionId); - } } From c07cdf7eaecbd7d65c470e423bbe7847c1f1342d Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 16 Apr 2025 17:37:11 +0200 Subject: [PATCH 048/183] Move Method Body on Repositroy and inject Repository on Service --- .../ShoppingList/IShoppingListRepository.cs | 3 +++ .../ShoppingList/ShoppingListRepository.cs | 7 +++++++ .../Services/ShoppingList/ShoppingListService.cs | 16 ++++++++-------- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs b/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs index 90c35c0..b9ef9d4 100644 --- a/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs +++ b/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs @@ -17,5 +17,8 @@ Task DeleteShoppingListAsync(Guid shoppingListId); Task GetRecipeByNameAndImageAsync(string recipeName, string imageFileName); + + Task GetShoppingListRecipeCountAsync(Guid shoppingListId); + } } diff --git a/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs b/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs index 16d8934..2b23dea 100644 --- a/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs +++ b/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs @@ -167,5 +167,12 @@ .Include(r => r.MediaFiles.Where(mf => mf.FileName == imageFileName)) .FirstOrDefaultAsync(); } + + public async Task GetShoppingListRecipeCountAsync(Guid shoppingListId) + { + return await _context.RecipeShoppingLists + .Where(rsl => rsl.ShoppingList.Id == shoppingListId) + .CountAsync(); + } } } diff --git a/Francesco.Recipes.World/Services/ShoppingList/ShoppingListService.cs b/Francesco.Recipes.World/Services/ShoppingList/ShoppingListService.cs index 2428d90..ef4e37c 100644 --- a/Francesco.Recipes.World/Services/ShoppingList/ShoppingListService.cs +++ b/Francesco.Recipes.World/Services/ShoppingList/ShoppingListService.cs @@ -1,22 +1,22 @@ -using Francesco.Recipes.World.Data; -using Microsoft.EntityFrameworkCore; - -namespace Francesco.Recipes.World.Services.ShoppingList +namespace Francesco.Recipes.World.Services.ShoppingList { + using Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Repositories.ShoppingList; + public class ShoppingListService { private readonly FrancescosRecipesWorldDbContext _context; + private readonly IShoppingListRepository _shoppingListRepository; - public ShoppingListService(FrancescosRecipesWorldDbContext context) + public ShoppingListService(FrancescosRecipesWorldDbContext context, IShoppingListRepository shoppingListRepository) { _context = context; + _shoppingListRepository = shoppingListRepository; } public async Task GetShoppingListRecipeCountAsync(Guid shoppingListId) { - return await _context.RecipeShoppingLists - .Where(rsl => rsl.ShoppingList.Id == shoppingListId) - .CountAsync(); + return await _shoppingListRepository.GetShoppingListRecipeCountAsync(shoppingListId); } } } From c225eedc8bdc37a733006e40496514725be48dd9 Mon Sep 17 00:00:00 2001 From: franc Date: Thu, 17 Apr 2025 10:20:55 +0200 Subject: [PATCH 049/183] Fix some formatting and Rename Refactoring --- .../Repositories/Recipe/IRecipeRepository.cs | 2 +- .../Views/Home/_SearchResultsPartial.cshtml | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs index ca81b4b..b4c046d 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs @@ -17,6 +17,6 @@ Task CreateRecipeForCategoryAsync(Category category, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime); - Task> SerachRecipeAndIngredientAsync(string query); + Task> SerachRecipeAndIngredientAsync(string searchterm); } } diff --git a/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml b/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml index ee35af1..4edbb64 100644 --- a/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml +++ b/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml @@ -13,10 +13,10 @@ else
- @{ - var mediaFile = recipe.MediaFiles.FirstOrDefault(); - } - @if (mediaFile?.Data != null) + @{ + var mediaFile = recipe.MediaFiles.FirstOrDefault(); + + if (mediaFile?.Data != null) { @recipe.Name } + }
From 556b22b8dfbc6f4f8d910ddd0267546dd6c09197 Mon Sep 17 00:00:00 2001 From: franc Date: Thu, 17 Apr 2025 10:24:08 +0200 Subject: [PATCH 050/183] Rename Refactoring --- .../Services/ShoppingList/ShoppingListService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Services/ShoppingList/ShoppingListService.cs b/Francesco.Recipes.World/Services/ShoppingList/ShoppingListService.cs index ef4e37c..d650db7 100644 --- a/Francesco.Recipes.World/Services/ShoppingList/ShoppingListService.cs +++ b/Francesco.Recipes.World/Services/ShoppingList/ShoppingListService.cs @@ -14,7 +14,7 @@ _shoppingListRepository = shoppingListRepository; } - public async Task GetShoppingListRecipeCountAsync(Guid shoppingListId) + public async Task CountRecipeLinkedToShoppingListAsync(Guid shoppingListId) { return await _shoppingListRepository.GetShoppingListRecipeCountAsync(shoppingListId); } From 71ac7eb03fb94eda51a42b593bab3bca21383a7f Mon Sep 17 00:00:00 2001 From: franc Date: Thu, 17 Apr 2025 10:34:12 +0200 Subject: [PATCH 051/183] Rename Refactoring --- .../Repositories/Instruction/IInstructionRepository.cs | 4 ++-- .../Repositories/Instruction/InstructionRepository.cs | 4 ++-- .../Services/Instruction/InstructionService.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs index 057eb02..e1fd1e6 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs @@ -10,8 +10,8 @@ Task RemoveInstructionFromRecipeAsync(Guid recipeId, Guid instructionId); - Task SwapInstructionOrderAsync(Instruction a, Instruction b); + Task SwapInstructionNumbersAsync(Instruction a, Instruction b); - Task> GetInstructionsByInstructionIdAsync(Guid instructionId); + Task> GetInstructionsOfRecipeAsync(Guid instructionId); } } diff --git a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs index a95e22d..9a4445d 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs @@ -70,7 +70,7 @@ } } - public async Task> GetInstructionsByInstructionIdAsync(Guid instructionId) + public async Task> GetInstructionsOfRecipeAsync(Guid instructionId) { var instruction = await _context.Instructions .Include(i => i.Recipe) @@ -85,7 +85,7 @@ return instruction.Recipe.Instructions.ToList(); } - public async Task SwapInstructionOrderAsync(Instruction a, Instruction b) + public async Task SwapInstructionNumbersAsync(Instruction a, Instruction b) { if (a == null) { diff --git a/Francesco.Recipes.World/Services/Instruction/InstructionService.cs b/Francesco.Recipes.World/Services/Instruction/InstructionService.cs index 895cf66..c7a4acc 100644 --- a/Francesco.Recipes.World/Services/Instruction/InstructionService.cs +++ b/Francesco.Recipes.World/Services/Instruction/InstructionService.cs @@ -19,7 +19,7 @@ namespace Francesco.Recipes.World.Services.Instruction private async Task MoveInstructionAsync(Guid instructionId, bool moveUp) { - var instructions = await _instructionRepository.GetInstructionsByInstructionIdAsync(instructionId); + var instructions = await _instructionRepository.GetInstructionsOfRecipeAsync(instructionId); var instruction = instructions.FirstOrDefault(i => i.Id == instructionId); @@ -36,12 +36,12 @@ namespace Francesco.Recipes.World.Services.Instruction return; } - var targetNumber = moveUp ? instruction.Number - 1 : instruction.Number + 1; + var targetNumber = moveUp ? instruction.Number + 1 : instruction.Number - 1; var neighbor = instructions.FirstOrDefault(i => i.Number == targetNumber); if (neighbor != null) { - await _instructionRepository.SwapInstructionOrderAsync(instruction, neighbor); + await _instructionRepository.SwapInstructionNumbersAsync(instruction, neighbor); } } } From 0d6b9eb294a508840a0ff9f78b07000e97c42cd0 Mon Sep 17 00:00:00 2001 From: franc Date: Thu, 17 Apr 2025 10:49:40 +0200 Subject: [PATCH 052/183] Rename refactoring --- .../Controller/HomeController/HomeController.cs | 2 +- .../Repositories/Recipe/IRecipeRepository.cs | 2 +- Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Francesco.Recipes.World/Controller/HomeController/HomeController.cs b/Francesco.Recipes.World/Controller/HomeController/HomeController.cs index b745357..bb816dd 100644 --- a/Francesco.Recipes.World/Controller/HomeController/HomeController.cs +++ b/Francesco.Recipes.World/Controller/HomeController/HomeController.cs @@ -25,7 +25,7 @@ [HttpGet("/Home/Search")] public async Task Search(string query) { - var recipes = await _recipeRepository.SerachRecipeAndIngredientAsync(query); + var recipes = await _recipeRepository.SearchRecipeAndIngredientAsync(query); return PartialView("_SearchResultsPartial", recipes); } } diff --git a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs index b4c046d..15de523 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs @@ -17,6 +17,6 @@ Task CreateRecipeForCategoryAsync(Category category, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime); - Task> SerachRecipeAndIngredientAsync(string searchterm); + Task> SearchRecipeAndIngredientAsync(string searchterm); } } diff --git a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs index bd2216b..d1d7df6 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs @@ -147,7 +147,7 @@ await _context.SaveChangesAsync(); } - public async Task> SerachRecipeAndIngredientAsync(string searchTerm) + public async Task> SearchRecipeAndIngredientAsync(string searchTerm) { var queryable = _context.Recipes .Include(r => r.RecipeIngredients) From df146a24b089b6cffad04ac89681eea8dd820c3b Mon Sep 17 00:00:00 2001 From: franc Date: Thu, 17 Apr 2025 11:15:15 +0200 Subject: [PATCH 053/183] Rename Refactoring --- .../Controller/HomeController/HomeController.cs | 2 +- .../Repositories/Recipe/IRecipeRepository.cs | 2 +- Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Francesco.Recipes.World/Controller/HomeController/HomeController.cs b/Francesco.Recipes.World/Controller/HomeController/HomeController.cs index bb816dd..33910a2 100644 --- a/Francesco.Recipes.World/Controller/HomeController/HomeController.cs +++ b/Francesco.Recipes.World/Controller/HomeController/HomeController.cs @@ -25,7 +25,7 @@ [HttpGet("/Home/Search")] public async Task Search(string query) { - var recipes = await _recipeRepository.SearchRecipeAndIngredientAsync(query); + var recipes = await _recipeRepository.SearchRecipeAsync(query); return PartialView("_SearchResultsPartial", recipes); } } diff --git a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs index 15de523..de46d1c 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs @@ -17,6 +17,6 @@ Task CreateRecipeForCategoryAsync(Category category, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime); - Task> SearchRecipeAndIngredientAsync(string searchterm); + Task> SearchRecipeAsync(string searchterm); } } diff --git a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs index d1d7df6..00c30ca 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs @@ -147,7 +147,7 @@ await _context.SaveChangesAsync(); } - public async Task> SearchRecipeAndIngredientAsync(string searchTerm) + public async Task> SearchRecipeAsync(string searchTerm) { var queryable = _context.Recipes .Include(r => r.RecipeIngredients) From 4eaf03698ca3b453782d534f592759c5bd9c34d7 Mon Sep 17 00:00:00 2001 From: franc Date: Thu, 17 Apr 2025 11:20:02 +0200 Subject: [PATCH 054/183] Rename refactoring --- .../Controller/HomeController/HomeController.cs | 2 +- .../Repositories/Recipe/IRecipeRepository.cs | 2 +- Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Francesco.Recipes.World/Controller/HomeController/HomeController.cs b/Francesco.Recipes.World/Controller/HomeController/HomeController.cs index 33910a2..facd4de 100644 --- a/Francesco.Recipes.World/Controller/HomeController/HomeController.cs +++ b/Francesco.Recipes.World/Controller/HomeController/HomeController.cs @@ -25,7 +25,7 @@ [HttpGet("/Home/Search")] public async Task Search(string query) { - var recipes = await _recipeRepository.SearchRecipeAsync(query); + var recipes = await _recipeRepository.SearchInRecipesandIngredients(query); return PartialView("_SearchResultsPartial", recipes); } } diff --git a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs index de46d1c..35394e2 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs @@ -17,6 +17,6 @@ Task CreateRecipeForCategoryAsync(Category category, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime); - Task> SearchRecipeAsync(string searchterm); + Task> SearchInRecipesandIngredients(string searchterm); } } diff --git a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs index 00c30ca..6d24ab3 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs @@ -147,7 +147,7 @@ await _context.SaveChangesAsync(); } - public async Task> SearchRecipeAsync(string searchTerm) + public async Task> SearchInRecipesandIngredients(string searchTerm) { var queryable = _context.Recipes .Include(r => r.RecipeIngredients) From 35f2d1675dd407eb7f2cdcd80bdc9e98a1433cc8 Mon Sep 17 00:00:00 2001 From: franc Date: Thu, 17 Apr 2025 11:41:20 +0200 Subject: [PATCH 055/183] Rename Refactoring --- .../Controller/HomeController/HomeController.cs | 4 ++-- .../Repositories/Recipe/IRecipeRepository.cs | 2 +- .../Repositories/Recipe/RecipeRepository.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Francesco.Recipes.World/Controller/HomeController/HomeController.cs b/Francesco.Recipes.World/Controller/HomeController/HomeController.cs index facd4de..9bd9a0a 100644 --- a/Francesco.Recipes.World/Controller/HomeController/HomeController.cs +++ b/Francesco.Recipes.World/Controller/HomeController/HomeController.cs @@ -23,9 +23,9 @@ } [HttpGet("/Home/Search")] - public async Task Search(string query) + public async Task Search(string term) { - var recipes = await _recipeRepository.SearchInRecipesandIngredients(query); + var recipes = await _recipeRepository.SearchInRecipesAndIngredients(term); return PartialView("_SearchResultsPartial", recipes); } } diff --git a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs index 35394e2..63c20af 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs @@ -17,6 +17,6 @@ Task CreateRecipeForCategoryAsync(Category category, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime); - Task> SearchInRecipesandIngredients(string searchterm); + Task> SearchInRecipesAndIngredients(string searchterm); } } diff --git a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs index 6d24ab3..c0f6b27 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs @@ -147,7 +147,7 @@ await _context.SaveChangesAsync(); } - public async Task> SearchInRecipesandIngredients(string searchTerm) + public async Task> SearchInRecipesAndIngredients(string searchTerm) { var queryable = _context.Recipes .Include(r => r.RecipeIngredients) From cbd2ba7dfb217ee8b51c9c2ddcac11be723eac1d Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 22 Apr 2025 14:32:19 +0200 Subject: [PATCH 056/183] use ViewModel in Index action instead of raw entities --- .../Controller/HomeController/HomeController.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Controller/HomeController/HomeController.cs b/Francesco.Recipes.World/Controller/HomeController/HomeController.cs index 9bd9a0a..88df8bc 100644 --- a/Francesco.Recipes.World/Controller/HomeController/HomeController.cs +++ b/Francesco.Recipes.World/Controller/HomeController/HomeController.cs @@ -2,6 +2,7 @@ { using Francesco.Recipes.World.Repositories.Category; using Francesco.Recipes.World.Repositories.Recipe; + using Francesco.Recipes.World.Views.Category; using Microsoft.AspNetCore.Mvc; public class HomeController : Controller @@ -19,7 +20,14 @@ public async Task Index() { var categories = await _categoryRepository.GetAllCategoriesWithRecipesAsync(); - return View("Index", categories); + + var viewModel = categories.Select(category => new CategoryRecipesViewModel + { + Category = category, + Recipes = category.Recipes, + }); + + return View("Index", viewModel); } [HttpGet("/Home/Search")] From 71c9f27d64a516d09e771b71562c0770519d7c67 Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 22 Apr 2025 14:37:19 +0200 Subject: [PATCH 057/183] Including MediaFile of Recipe for show it on card --- .../Repositories/Category/CategoryRepository.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs b/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs index 39e7228..821f14f 100644 --- a/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs +++ b/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs @@ -46,6 +46,7 @@ { return await _context.Categories .Include(c => c.Recipes) + .ThenInclude(r => r.MediaFiles) .ToListAsync(); } } From cc590fa65c6fff791beccf7405da4a3a2684bba3 Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 22 Apr 2025 14:38:57 +0200 Subject: [PATCH 058/183] Make a rude Index Home Page with a bit style --- .../Views/Home/Index.cshtml | 82 ++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Views/Home/Index.cshtml b/Francesco.Recipes.World/Views/Home/Index.cshtml index 8de8ea4..38c290a 100644 --- a/Francesco.Recipes.World/Views/Home/Index.cshtml +++ b/Francesco.Recipes.World/Views/Home/Index.cshtml @@ -1,6 +1,86 @@ -
+@model IEnumerable +@Html.AntiForgeryToken() + +
+ Willkommen +

Willkommen in der Rezept-App

+
+ + + +
+ +@foreach (var category in Model) +{ +
+
+

@category.Category.Name

+ Alle @category.Category.Name-Rezepte anzeigen +
+ +
+ @foreach (var recipe in category.Recipes) + { + var mediaFile = recipe.MediaFiles?.FirstOrDefault(); + var imageData = mediaFile?.Data; + var mimeType = mediaFile?.MimeType; + + + } + +
+ +
+
+
+} + + From 5f92625c16c5112a159dd385bb7805607a2df076 Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 22 Apr 2025 14:40:25 +0200 Subject: [PATCH 059/183] Make a PartialView for favorite System --- .../Views/Shared/_FavoriteButton.cshtml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 Francesco.Recipes.World/Views/Shared/_FavoriteButton.cshtml diff --git a/Francesco.Recipes.World/Views/Shared/_FavoriteButton.cshtml b/Francesco.Recipes.World/Views/Shared/_FavoriteButton.cshtml new file mode 100644 index 0000000..5cd045a --- /dev/null +++ b/Francesco.Recipes.World/Views/Shared/_FavoriteButton.cshtml @@ -0,0 +1,25 @@ +@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe + + From 1239f316733c0881ad9077ae0d1815deb365d387 Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 22 Apr 2025 14:42:04 +0200 Subject: [PATCH 060/183] add HTMX CSRF token header support --- Francesco.Recipes.World/Views/Shared/_Layout.cshtml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml index fa98366..dace8db 100644 --- a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml @@ -73,6 +73,15 @@ document.body.classList.remove('bg-dark', 'text-white'); }); + + @await RenderSectionAsync("Scripts", required: false) From 323af8d6f0475a8c8b575f4a158b0fd6afe44e45 Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 22 Apr 2025 14:42:46 +0200 Subject: [PATCH 061/183] Create new folder for images and put one svg for favorite symbol --- Francesco.Recipes.World/wwwroot/images/star.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 Francesco.Recipes.World/wwwroot/images/star.svg diff --git a/Francesco.Recipes.World/wwwroot/images/star.svg b/Francesco.Recipes.World/wwwroot/images/star.svg new file mode 100644 index 0000000..25afd22 --- /dev/null +++ b/Francesco.Recipes.World/wwwroot/images/star.svg @@ -0,0 +1 @@ + \ No newline at end of file From 16a9d1084452e8546ad6e778649c452d39a66798 Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 22 Apr 2025 14:49:29 +0200 Subject: [PATCH 062/183] Return PartialView instead an Redirection --- .../Controller/Recipe/RecipeController.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs index b86ef61..d78e31a 100644 --- a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs +++ b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs @@ -13,6 +13,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; + [Route("Recipe")] + public class RecipeController : Controller { private readonly IRecipeRepository _recipeRepository; @@ -248,7 +250,13 @@ public async Task AddFavorite(Guid recipeId) { await _favoriteRepository.AddFavoriteAsync(recipeId); - return RedirectToAction("Details", new { recipeId }); + var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId); + if (recipe == null) + { + return NotFound("Recipe not found."); + } + + return PartialView("_FavoriteButton", recipe); } // POST: /Recipe/RemoveFavorite @@ -257,7 +265,13 @@ public async Task RemoveFavorite(Guid recipeId) { await _favoriteRepository.RemoveFavoriteAsync(recipeId); - return RedirectToAction("Details", new { recipeId }); + var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId); + if (recipe == null) + { + return NotFound("Recipe not found."); + } + + return PartialView("_FavoriteButton", recipe); } } } From 12752c4834325549a2df63daea22701562b6074b Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 23 Apr 2025 11:26:14 +0200 Subject: [PATCH 063/183] Delete unecessary Route --- Francesco.Recipes.World/Controller/Recipe/RecipeController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs index d78e31a..24d9618 100644 --- a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs +++ b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs @@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; - [Route("Recipe")] public class RecipeController : Controller { From 0fb9333b82ac64c1c9547196a499945a55cc3cbf Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 15 Apr 2025 16:10:42 +0200 Subject: [PATCH 064/183] Implement two methods for the sorting logic on the service --- .../Instruction/IInstructionRepository.cs | 4 ++ .../Instruction/InstructionRepository.cs | 39 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs index 963099b..528f6b8 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs @@ -11,5 +11,9 @@ Task> GetInstructionsByRecipeIdAsync(Guid recipeId); Task RemoveInstructionFromRecipeAsync(Guid recipeId, Guid instructionId); + + Task SwapInstructionOrderAsync(Instruction a, Instruction b); + + Task GetInstructionWithRecipeAsync(Guid instructionId); } } diff --git a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs index b487d57..0a71b5f 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs @@ -77,5 +77,44 @@ .Where(i => i.Recipe.Id == recipeId) .ToListAsync(); } + + public async Task GetInstructionWithRecipeAsync(Guid instructionId) + { + var instruction = await _context.Instructions + .Include(i => i.Recipe) + .ThenInclude(r => r.Instructions) + .FirstOrDefaultAsync(i => i.Id == instructionId); + + if (instruction == null) + { + throw new InvalidDataException($"Instruction with ID {instructionId} not found."); + } + + if (instruction.Recipe == null) + { + throw new InvalidDataException($"The Recipe for Instruction with ID {instructionId} is not loaded or does not exist."); + } + + return instruction; + } + + public async Task SwapInstructionOrderAsync(Instruction a, Instruction b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a), "Instruction 'a' cannot be null."); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b), "Instruction 'b' cannot be null."); + } + + var temp = a.Number; + a.Number = b.Number; + b.Number = temp; + + await _context.SaveChangesAsync(); + } } } From d2560e297124e58f64c0d29ce354fed4a9836ed0 Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 15 Apr 2025 16:11:07 +0200 Subject: [PATCH 065/183] Implement the Sorting Logic --- .../Instruction/IInstructionService.cs | 9 ++++ .../Instruction/InstructionService.cs | 54 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 Francesco.Recipes.World/Services/Instruction/IInstructionService.cs create mode 100644 Francesco.Recipes.World/Services/Instruction/InstructionService.cs diff --git a/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs b/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs new file mode 100644 index 0000000..fa514a1 --- /dev/null +++ b/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs @@ -0,0 +1,9 @@ +namespace Francesco.Recipes.World.Services.Instruction +{ + public interface IInstructionService + { + Task MoveInstructionUpAsync(Guid instructionId); + + Task MoveInstructionDownAsync(Guid instructionId); + } +} diff --git a/Francesco.Recipes.World/Services/Instruction/InstructionService.cs b/Francesco.Recipes.World/Services/Instruction/InstructionService.cs new file mode 100644 index 0000000..5015426 --- /dev/null +++ b/Francesco.Recipes.World/Services/Instruction/InstructionService.cs @@ -0,0 +1,54 @@ +using Francesco.Recipes.World.Repositories.Instruction; + +namespace Francesco.Recipes.World.Services.Instruction +{ + public class InstructionService : IInstructionService + { + private readonly IInstructionRepository _instructionRepository; + + public InstructionService(IInstructionRepository instructionRepository) + { + _instructionRepository = instructionRepository; + } + + public async Task MoveInstructionDownAsync(Guid instructionId) + { + var instruction = await _instructionRepository.GetInstructionWithRecipeAsync(instructionId); + + var instructions = await _instructionRepository.GetInstructionsByRecipeIdAsync(instruction.Recipe.Id); + + var maxStep = instructions.Max(i => i.Number); + + if (instruction.Number >= maxStep) + { + return; + } + + var neighbor = instructions.FirstOrDefault(i => i.Number == instruction.Number + 1); + + if (neighbor != null) + { + await _instructionRepository.SwapInstructionOrderAsync(instruction, neighbor); + } + } + + public async Task MoveInstructionUpAsync(Guid instructionId) + { + var instruction = await _instructionRepository.GetInstructionWithRecipeAsync(instructionId); + + if (instruction.Number == 1) + { + return; + } + + var instructions = await _instructionRepository.GetInstructionsByRecipeIdAsync(instruction.Recipe.Id); + + var neighbor = instructions.FirstOrDefault(i => i.Number == instruction.Number - 1); + + if (neighbor != null) + { + await _instructionRepository.SwapInstructionOrderAsync(instruction, neighbor); + } + } + } +} From 825818645a274b8b3b7903e9c2f9833496d1456a Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 16 Apr 2025 17:22:19 +0200 Subject: [PATCH 066/183] Refactoring some code and change logic and make sure to reduce db calls and avoid redundants --- .../Instruction/IInstructionRepository.cs | 4 +- .../Instruction/InstructionRepository.cs | 21 ++-------- .../Instruction/IInstructionService.cs | 2 + .../Instruction/InstructionService.cs | 42 ++++++++----------- 4 files changed, 25 insertions(+), 44 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs index 528f6b8..057eb02 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs @@ -8,12 +8,10 @@ Task CreateInstructionToRecipeAsync(Guid recipeId, string description, int number); - Task> GetInstructionsByRecipeIdAsync(Guid recipeId); - Task RemoveInstructionFromRecipeAsync(Guid recipeId, Guid instructionId); Task SwapInstructionOrderAsync(Instruction a, Instruction b); - Task GetInstructionWithRecipeAsync(Guid instructionId); + Task> GetInstructionsByInstructionIdAsync(Guid instructionId); } } diff --git a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs index 0a71b5f..a95e22d 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs @@ -70,32 +70,19 @@ } } - public async Task> GetInstructionsByRecipeIdAsync(Guid recipeId) - { - return await _context.Instructions - .Include(i => i.Recipe) - .Where(i => i.Recipe.Id == recipeId) - .ToListAsync(); - } - - public async Task GetInstructionWithRecipeAsync(Guid instructionId) + public async Task> GetInstructionsByInstructionIdAsync(Guid instructionId) { var instruction = await _context.Instructions .Include(i => i.Recipe) .ThenInclude(r => r.Instructions) .FirstOrDefaultAsync(i => i.Id == instructionId); - if (instruction == null) + if (instruction?.Recipe == null) { - throw new InvalidDataException($"Instruction with ID {instructionId} not found."); + throw new InvalidDataException($"Instruction with ID {instructionId} or its Recipe not found."); } - if (instruction.Recipe == null) - { - throw new InvalidDataException($"The Recipe for Instruction with ID {instructionId} is not loaded or does not exist."); - } - - return instruction; + return instruction.Recipe.Instructions.ToList(); } public async Task SwapInstructionOrderAsync(Instruction a, Instruction b) diff --git a/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs b/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs index fa514a1..efb6b00 100644 --- a/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs +++ b/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs @@ -5,5 +5,7 @@ Task MoveInstructionUpAsync(Guid instructionId); Task MoveInstructionDownAsync(Guid instructionId); + + Task MoveInstructionAsync(Guid instructionId, bool moveUp); } } diff --git a/Francesco.Recipes.World/Services/Instruction/InstructionService.cs b/Francesco.Recipes.World/Services/Instruction/InstructionService.cs index 5015426..895cf66 100644 --- a/Francesco.Recipes.World/Services/Instruction/InstructionService.cs +++ b/Francesco.Recipes.World/Services/Instruction/InstructionService.cs @@ -11,39 +11,33 @@ namespace Francesco.Recipes.World.Services.Instruction _instructionRepository = instructionRepository; } - public async Task MoveInstructionDownAsync(Guid instructionId) + public Task MoveInstructionUpAsync(Guid instructionId) + => MoveInstructionAsync(instructionId, moveUp: true); + + public Task MoveInstructionDownAsync(Guid instructionId) + => MoveInstructionAsync(instructionId, moveUp: false); + + private async Task MoveInstructionAsync(Guid instructionId, bool moveUp) { - var instruction = await _instructionRepository.GetInstructionWithRecipeAsync(instructionId); + var instructions = await _instructionRepository.GetInstructionsByInstructionIdAsync(instructionId); - var instructions = await _instructionRepository.GetInstructionsByRecipeIdAsync(instruction.Recipe.Id); + var instruction = instructions.FirstOrDefault(i => i.Id == instructionId); + if (instruction == null) + { + throw new InvalidDataException($"Instruction with ID {instructionId} not found."); + } + + var minStep = 1; var maxStep = instructions.Max(i => i.Number); - if (instruction.Number >= maxStep) + if ((moveUp && instruction.Number == minStep) || (!moveUp && instruction.Number >= maxStep)) { return; } - var neighbor = instructions.FirstOrDefault(i => i.Number == instruction.Number + 1); - - if (neighbor != null) - { - await _instructionRepository.SwapInstructionOrderAsync(instruction, neighbor); - } - } - - public async Task MoveInstructionUpAsync(Guid instructionId) - { - var instruction = await _instructionRepository.GetInstructionWithRecipeAsync(instructionId); - - if (instruction.Number == 1) - { - return; - } - - var instructions = await _instructionRepository.GetInstructionsByRecipeIdAsync(instruction.Recipe.Id); - - var neighbor = instructions.FirstOrDefault(i => i.Number == instruction.Number - 1); + var targetNumber = moveUp ? instruction.Number - 1 : instruction.Number + 1; + var neighbor = instructions.FirstOrDefault(i => i.Number == targetNumber); if (neighbor != null) { From 32aaec7d65d83c0ca28359e4429035af87771c08 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 16 Apr 2025 17:27:08 +0200 Subject: [PATCH 067/183] Remove the interface declaration for MoveInstructionAsync since it's now a private method --- .../Services/Instruction/IInstructionService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs b/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs index efb6b00..8ddedf5 100644 --- a/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs +++ b/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs @@ -6,6 +6,5 @@ Task MoveInstructionDownAsync(Guid instructionId); - Task MoveInstructionAsync(Guid instructionId, bool moveUp); } } From 059e93cb86542ab12c1597d2a87a16be2ac38fa9 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 16 Apr 2025 17:27:41 +0200 Subject: [PATCH 068/183] Remove Blank line --- .../Services/Instruction/IInstructionService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs b/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs index 8ddedf5..fa514a1 100644 --- a/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs +++ b/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs @@ -5,6 +5,5 @@ Task MoveInstructionUpAsync(Guid instructionId); Task MoveInstructionDownAsync(Guid instructionId); - } } From 1bdb14e9d3d5642ae63b3e37fcddc0d71d988980 Mon Sep 17 00:00:00 2001 From: franc Date: Thu, 17 Apr 2025 10:34:12 +0200 Subject: [PATCH 069/183] Rename Refactoring --- .../Repositories/Instruction/IInstructionRepository.cs | 4 ++-- .../Repositories/Instruction/InstructionRepository.cs | 4 ++-- .../Services/Instruction/InstructionService.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs index 057eb02..e1fd1e6 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs @@ -10,8 +10,8 @@ Task RemoveInstructionFromRecipeAsync(Guid recipeId, Guid instructionId); - Task SwapInstructionOrderAsync(Instruction a, Instruction b); + Task SwapInstructionNumbersAsync(Instruction a, Instruction b); - Task> GetInstructionsByInstructionIdAsync(Guid instructionId); + Task> GetInstructionsOfRecipeAsync(Guid instructionId); } } diff --git a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs index a95e22d..9a4445d 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs @@ -70,7 +70,7 @@ } } - public async Task> GetInstructionsByInstructionIdAsync(Guid instructionId) + public async Task> GetInstructionsOfRecipeAsync(Guid instructionId) { var instruction = await _context.Instructions .Include(i => i.Recipe) @@ -85,7 +85,7 @@ return instruction.Recipe.Instructions.ToList(); } - public async Task SwapInstructionOrderAsync(Instruction a, Instruction b) + public async Task SwapInstructionNumbersAsync(Instruction a, Instruction b) { if (a == null) { diff --git a/Francesco.Recipes.World/Services/Instruction/InstructionService.cs b/Francesco.Recipes.World/Services/Instruction/InstructionService.cs index 895cf66..c7a4acc 100644 --- a/Francesco.Recipes.World/Services/Instruction/InstructionService.cs +++ b/Francesco.Recipes.World/Services/Instruction/InstructionService.cs @@ -19,7 +19,7 @@ namespace Francesco.Recipes.World.Services.Instruction private async Task MoveInstructionAsync(Guid instructionId, bool moveUp) { - var instructions = await _instructionRepository.GetInstructionsByInstructionIdAsync(instructionId); + var instructions = await _instructionRepository.GetInstructionsOfRecipeAsync(instructionId); var instruction = instructions.FirstOrDefault(i => i.Id == instructionId); @@ -36,12 +36,12 @@ namespace Francesco.Recipes.World.Services.Instruction return; } - var targetNumber = moveUp ? instruction.Number - 1 : instruction.Number + 1; + var targetNumber = moveUp ? instruction.Number + 1 : instruction.Number - 1; var neighbor = instructions.FirstOrDefault(i => i.Number == targetNumber); if (neighbor != null) { - await _instructionRepository.SwapInstructionOrderAsync(instruction, neighbor); + await _instructionRepository.SwapInstructionNumbersAsync(instruction, neighbor); } } } From 0859e3aa2b9b84a6aff0de1b3e838e560a9346db Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 23 Apr 2025 13:27:51 +0200 Subject: [PATCH 070/183] Add recipeid to the methods seperate the Id logic between instructionId and recipeId --- .../Services/Instruction/IInstructionService.cs | 4 ++-- .../Services/Instruction/InstructionService.cs | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs b/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs index fa514a1..41bfe0f 100644 --- a/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs +++ b/Francesco.Recipes.World/Services/Instruction/IInstructionService.cs @@ -2,8 +2,8 @@ { public interface IInstructionService { - Task MoveInstructionUpAsync(Guid instructionId); + Task MoveInstructionUpAsync(Guid recipeId, Guid instructionId); - Task MoveInstructionDownAsync(Guid instructionId); + Task MoveInstructionDownAsync(Guid recipeId, Guid instructionId); } } diff --git a/Francesco.Recipes.World/Services/Instruction/InstructionService.cs b/Francesco.Recipes.World/Services/Instruction/InstructionService.cs index c7a4acc..ed95792 100644 --- a/Francesco.Recipes.World/Services/Instruction/InstructionService.cs +++ b/Francesco.Recipes.World/Services/Instruction/InstructionService.cs @@ -11,15 +11,15 @@ namespace Francesco.Recipes.World.Services.Instruction _instructionRepository = instructionRepository; } - public Task MoveInstructionUpAsync(Guid instructionId) - => MoveInstructionAsync(instructionId, moveUp: true); + public Task MoveInstructionUpAsync(Guid recipeId, Guid instructionId) + => MoveInstructionAsync(recipeId, instructionId, moveUp: true); - public Task MoveInstructionDownAsync(Guid instructionId) - => MoveInstructionAsync(instructionId, moveUp: false); + public Task MoveInstructionDownAsync(Guid recipeId, Guid instructionId) + => MoveInstructionAsync(recipeId, instructionId, moveUp: false); - private async Task MoveInstructionAsync(Guid instructionId, bool moveUp) + private async Task MoveInstructionAsync(Guid recipeId, Guid instructionId, bool moveUp) { - var instructions = await _instructionRepository.GetInstructionsOfRecipeAsync(instructionId); + var instructions = await _instructionRepository.GetInstructionsOfRecipeAsync(recipeId); var instruction = instructions.FirstOrDefault(i => i.Id == instructionId); @@ -36,7 +36,7 @@ namespace Francesco.Recipes.World.Services.Instruction return; } - var targetNumber = moveUp ? instruction.Number + 1 : instruction.Number - 1; + var targetNumber = moveUp ? instruction.Number - 1 : instruction.Number + 1; var neighbor = instructions.FirstOrDefault(i => i.Number == targetNumber); if (neighbor != null) From 0d1f18ecf688e0d4fd25aaf7420ec17f4a6de21a Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 23 Apr 2025 13:28:24 +0200 Subject: [PATCH 071/183] Just add scope for InstructionService --- Francesco.Recipes.World/Program.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Francesco.Recipes.World/Program.cs b/Francesco.Recipes.World/Program.cs index 40317b6..1caf8cf 100644 --- a/Francesco.Recipes.World/Program.cs +++ b/Francesco.Recipes.World/Program.cs @@ -7,6 +7,7 @@ using Francesco.Recipes.World.Repositories.MediaFile; using Francesco.Recipes.World.Repositories.Recipe; using Francesco.Recipes.World.Repositories.ShoppingList; using Francesco.Recipes.World.Repositories.Unit; +using Francesco.Recipes.World.Services.Instruction; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); @@ -40,6 +41,8 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); + var app = builder.Build(); // Configure the HTTP request pipeline. From 146cdf9bffb310005a70dcc0b9754ef8a577223e Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 23 Apr 2025 13:32:18 +0200 Subject: [PATCH 072/183] Remove unnecessary context dependency --- .../Controller/MediaFile/MediaFileController.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs index 0d9ddf3..5e2ebde 100644 --- a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs +++ b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs @@ -8,12 +8,10 @@ public class MediaFileController : Controller { private readonly IMediaFileRepository _mediaFileRepository; - private readonly FrancescosRecipesWorldDbContext _context; public MediaFileController(IMediaFileRepository mediaFileRepository, FrancescosRecipesWorldDbContext context) { _mediaFileRepository = mediaFileRepository; - _context = context; } // POST: /UploadImage From 7035a9b64ddafdb89187cd548ec6fca201fadb6d Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 23 Apr 2025 13:33:33 +0200 Subject: [PATCH 073/183] Add also here recipeId --- .../Instruction/InstructionController.cs | 61 ++++++++++++- .../MediaFile/MediaFileController.cs | 4 +- .../Controller/Recipe/RecipeController.cs | 1 - .../Instruction/IInstructionRepository.cs | 2 +- .../Instruction/InstructionRepository.cs | 16 ++-- .../Views/Shared/_GetInstructions.cshtml | 89 +++++++++++++++++++ 6 files changed, 160 insertions(+), 13 deletions(-) create mode 100644 Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml diff --git a/Francesco.Recipes.World/Controller/Instruction/InstructionController.cs b/Francesco.Recipes.World/Controller/Instruction/InstructionController.cs index d86aadf..4ce79ef 100644 --- a/Francesco.Recipes.World/Controller/Instruction/InstructionController.cs +++ b/Francesco.Recipes.World/Controller/Instruction/InstructionController.cs @@ -1,6 +1,65 @@ namespace Francesco.Recipes.World.Controller.Instruction { - public class InstructionController + using Francesco.Recipes.World.Repositories.Instruction; + using Francesco.Recipes.World.Services.Instruction; + using Microsoft.AspNetCore.Mvc; + + public class InstructionController : Controller { + private readonly IInstructionService _instructionService; + private readonly IInstructionRepository _instructionRepository; + + public InstructionController(IInstructionService instructionService, IInstructionRepository instructionRepository) + { + _instructionService = instructionService; + _instructionRepository = instructionRepository; + } + + [HttpPost("Recipe/{recipeId}/Instruction/{instructionId}/move-up")] + [ValidateAntiForgeryToken] + public async Task MoveUp(Guid recipeId, Guid instructionId) + { + try + { + await _instructionService.MoveInstructionUpAsync(recipeId, instructionId); + return Ok(new { Message = "Instruction moved up successfully." }); + } + catch (Exception ex) + { + return BadRequest(new { Error = ex.Message }); + } + } + + [HttpPost("Recipe/{recipeId}/Instruction/{instructionId}/move-down")] + [ValidateAntiForgeryToken] + public async Task MoveDown(Guid recipeId, Guid instructionId) + { + try + { + await _instructionService.MoveInstructionDownAsync(recipeId, instructionId); + return Ok(new { Message = "Instruction moved down successfully." }); + } + catch (Exception ex) + { + return BadRequest(new { Error = ex.Message }); + } + } + + [HttpGet("Recipe/{recipeId}/Instructions")] + public async Task GetInstructions(Guid recipeId) + { + try + { + var instructions = await _instructionRepository.GetInstructionsOfRecipeAsync(recipeId); + var sortedInstructions = instructions.OrderBy(i => i.Number).ToList(); + ViewData["RecipeId"] = recipeId; + + return View("~/Views/Shared/_GetInstructions.cshtml", sortedInstructions); + } + catch (Exception ex) + { + return BadRequest(new { Error = ex.Message }); + } + } } } diff --git a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs index 5e2ebde..c624b4a 100644 --- a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs +++ b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs @@ -1,6 +1,6 @@ namespace Francesco.Recipes.World.Controller.MediaFile { - using Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Repositories.MediaFile; using Microsoft.AspNetCore.Mvc; @@ -9,7 +9,7 @@ { private readonly IMediaFileRepository _mediaFileRepository; - public MediaFileController(IMediaFileRepository mediaFileRepository, FrancescosRecipesWorldDbContext context) + public MediaFileController(IMediaFileRepository mediaFileRepository) { _mediaFileRepository = mediaFileRepository; } diff --git a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs index 24d9618..7f31c54 100644 --- a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs +++ b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs @@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; - public class RecipeController : Controller { private readonly IRecipeRepository _recipeRepository; diff --git a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs index e1fd1e6..2b17a6f 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs @@ -12,6 +12,6 @@ Task SwapInstructionNumbersAsync(Instruction a, Instruction b); - Task> GetInstructionsOfRecipeAsync(Guid instructionId); + Task> GetInstructionsOfRecipeAsync(Guid recipeId); } } diff --git a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs index 9a4445d..e94fd34 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs @@ -70,19 +70,19 @@ } } - public async Task> GetInstructionsOfRecipeAsync(Guid instructionId) + public async Task> GetInstructionsOfRecipeAsync(Guid recipeId) { - var instruction = await _context.Instructions - .Include(i => i.Recipe) - .ThenInclude(r => r.Instructions) - .FirstOrDefaultAsync(i => i.Id == instructionId); + var instructions = await _context.Instructions + .Where(i => i.Recipe.Id == recipeId) + .OrderBy(i => i.Number) + .ToListAsync(); - if (instruction?.Recipe == null) + if (!instructions.Any()) { - throw new InvalidDataException($"Instruction with ID {instructionId} or its Recipe not found."); + throw new InvalidDataException($"No instructions found for Recipe ID {recipeId}."); } - return instruction.Recipe.Instructions.ToList(); + return instructions; } public async Task SwapInstructionNumbersAsync(Instruction a, Instruction b) diff --git a/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml b/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml new file mode 100644 index 0000000..251b4fc --- /dev/null +++ b/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml @@ -0,0 +1,89 @@ +@Html.AntiForgeryToken() +
+ @for (int i = 0; i < Model.Count; i++) + { +
+
+ + + +
+
+ + +
+
+ } +
+ + + +@section Scripts { + +} From e169e94c8e22fe3908edeebed2ccb5ec589f7af0 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 23 Apr 2025 13:40:27 +0200 Subject: [PATCH 074/183] Solve format --- .../Controller/MediaFile/MediaFileController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs index c624b4a..1f17d7a 100644 --- a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs +++ b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs @@ -1,6 +1,5 @@ namespace Francesco.Recipes.World.Controller.MediaFile { - using Francesco.Recipes.World.Repositories.MediaFile; using Microsoft.AspNetCore.Mvc; From bcc0a606694c3b6f719cc63c10fc9e29430d6180 Mon Sep 17 00:00:00 2001 From: franc Date: Mon, 28 Apr 2025 13:10:41 +0200 Subject: [PATCH 075/183] Update Controller stuff with Repo Methods --- .../Controller/Recipe/RecipeController.cs | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs index 7f31c54..72bf903 100644 --- a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs +++ b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs @@ -188,6 +188,47 @@ return RedirectToAction("Details", new { id = recipeId }); } + // GET: /Recipe/{recipeId}/RemoveInstruction/{instructionId} + [HttpGet("{recipeId}/RemoveInstruction/{instructionId}")] + public async Task RemoveInstruction(Guid recipeId, Guid instructionId) + { + var recipe = await _recipeRepository.GetRecipeAsync(recipeId); + + if (recipe == null) + { + return NotFound("Recipe not found."); + } + + var instruction = recipe.Instructions?.FirstOrDefault(i => i.Id == instructionId); + if (instruction == null) + { + return NotFound("Instruction not found in the specified recipe."); + } + + ViewBag.RecipeId = recipeId; + ViewBag.InstructionId = instructionId; + + return View(); + } + + // POST: /Recipe/{recipeId}/RemoveInstruction/{instructionId} + [HttpPost("{recipeId}/RemoveInstruction/{instructionId}")] + [ValidateAntiForgeryToken] + public async Task RemoveInstructionConfirmed(Guid recipeId, Guid instructionId) + { + try + { + await _instructionRepository.RemoveInstructionFromRecipeAsync(recipeId, instructionId); + TempData["SuccessMessage"] = "Instruction removed successfully."; + return RedirectToAction("Details", new { recipeId }); + } + catch (Exception ex) + { + TempData["ErrorMessage"] = $"An error occurred while removing the instruction: {ex.Message}"; + return RedirectToAction("Details", new { recipeId }); + } + } + // GET: /Recipe/FilterByDifficulty [HttpGet("FilterByDifficulty")] public async Task FilterByDifficulty(Difficulty? selectedDifficulty) @@ -218,11 +259,11 @@ // POST: /Recipe/{recipeId}/AddInstruction [HttpPost("{recipeId}/AddInstruction")] [ValidateAntiForgeryToken] - public async Task AddInstruction(Guid recipeId, string description, int number) + public async Task AddInstruction(Guid recipeId, string description) { try { - await _instructionRepository.CreateInstructionToRecipeAsync(recipeId, description, number); + await _instructionRepository.CreateInstructionToRecipeAsync(recipeId, description); return RedirectToAction("AddInstruction", new { recipeId }); } catch (Exception ex) From 2f879d987bf8f948c530512ce0389e89940b2c19 Mon Sep 17 00:00:00 2001 From: franc Date: Mon, 28 Apr 2025 13:11:28 +0200 Subject: [PATCH 076/183] Add more methods for improve sort logic --- .../Instruction/IInstructionRepository.cs | 4 +- .../Instruction/InstructionRepository.cs | 42 ++++++++++++++++--- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs index 2b17a6f..ef8094f 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs @@ -6,12 +6,14 @@ { Task GetInstructionAsync(Guid instructionId); - Task CreateInstructionToRecipeAsync(Guid recipeId, string description, int number); + Task CreateInstructionToRecipeAsync(Guid recipeId, string description); Task RemoveInstructionFromRecipeAsync(Guid recipeId, Guid instructionId); Task SwapInstructionNumbersAsync(Instruction a, Instruction b); Task> GetInstructionsOfRecipeAsync(Guid recipeId); + + Task RenumberInstructionsAsync(Guid recipeId); } } diff --git a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs index e94fd34..6844c0a 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs @@ -22,25 +22,30 @@ return instruction ?? throw new InvalidDataException($"Instruction {instructionId} not found."); } - public async Task CreateInstructionToRecipeAsync(Guid recipeId, string description, int number) + public async Task CreateInstructionToRecipeAsync(Guid recipeId, string description) { - var recipe = await _recipeRepository.GetRecipeAsync(recipeId); - if (string.IsNullOrWhiteSpace(description)) { throw new ArgumentException("Description cannot be empty", nameof(description)); } - if (number <= 0) + var recipe = await _context.Recipes + .Include(r => r.Instructions) + .FirstOrDefaultAsync(r => r.Id == recipeId); + + if (recipe == null) { - throw new ArgumentOutOfRangeException(nameof(number), "Number must be greater than 0."); + throw new ArgumentException("Recipe not found.", nameof(recipeId)); } + var nextNumber = recipe.Instructions?.Max(i => i.Number) ?? 0; + nextNumber++; + var newInstruction = new Instruction { Id = Guid.NewGuid(), Description = description, - Number = number, + Number = nextNumber, Recipe = recipe, }; @@ -65,8 +70,18 @@ if (instructionToRemove != null) { + await _context.Entry(instructionToRemove) + .Collection(i => i.MediaFiles) + .LoadAsync(); + + if (instructionToRemove.MediaFiles != null && instructionToRemove.MediaFiles.Any()) + { + _context.MediaFiles.RemoveRange(instructionToRemove.MediaFiles); + } + recipe.Instructions?.Remove(instructionToRemove); await _context.SaveChangesAsync(); + await RenumberInstructionsAsync(recipeId); } } @@ -85,6 +100,21 @@ return instructions; } + public async Task RenumberInstructionsAsync(Guid recipeId) + { + var instructions = await _context.Instructions + .Where(i => i.Recipe.Id == recipeId) + .OrderBy(i => i.Number) + .ToListAsync(); + + for (var i = 0; i < instructions.Count; i++) + { + instructions[i].Number = i + 1; + } + + await _context.SaveChangesAsync(); + } + public async Task SwapInstructionNumbersAsync(Instruction a, Instruction b) { if (a == null) From 9c1584d621dc1c0ad00903933c4c4c1c23902c45 Mon Sep 17 00:00:00 2001 From: franc Date: Mon, 28 Apr 2025 13:13:24 +0200 Subject: [PATCH 077/183] Make two views for sort logic one for show the instructions and the other for add Instruction --- .../Views/Recipe/AddInstruction.cshtml | 50 ++++++++++--------- .../Views/Shared/_GetInstructions.cshtml | 28 ++++++++++- 2 files changed, 53 insertions(+), 25 deletions(-) diff --git a/Francesco.Recipes.World/Views/Recipe/AddInstruction.cshtml b/Francesco.Recipes.World/Views/Recipe/AddInstruction.cshtml index d6d5f79..e2ce85d 100644 --- a/Francesco.Recipes.World/Views/Recipe/AddInstruction.cshtml +++ b/Francesco.Recipes.World/Views/Recipe/AddInstruction.cshtml @@ -24,36 +24,38 @@
-
- - -
- + +
@section Scripts { - if (response.ok) { - location.reload(); - } else { - alert('Failed to add instruction.'); - } - } - } diff --git a/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml b/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml index 251b4fc..695bda0 100644 --- a/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml @@ -6,7 +6,7 @@
- +
@@ -68,6 +68,32 @@ } } + async function removeInstruction(instructionId) { + if (!confirm('Möchtest du diesen Schritt wirklich löschen?')) return; + + try { + const response = await fetch(`/${recipeId}/RemoveInstruction/${instructionId}`, { + method: 'POST', + headers: { + 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value + } + }); + + if (response.ok) { + const element = document.getElementById(`instruction-${instructionId}`); + if (element) { + element.remove(); + } + } else { + const error = await response.json(); + alert(error.Error || 'Fehler beim Löschen der Anweisung.'); + } + } catch (error) { + console.error('Fehler beim Löschen:', error); + } + } + + function addInstruction() { const container = document.getElementById('instructions-container'); const newInstructionHtml = ` From e6a9e927484c4935ad8f436b7a5bd70fb6c328fd Mon Sep 17 00:00:00 2001 From: franc Date: Mon, 28 Apr 2025 13:36:40 +0200 Subject: [PATCH 078/183] Rename refactoring Method --- .../Services/ShoppingList/IShoppingListService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Services/ShoppingList/IShoppingListService.cs b/Francesco.Recipes.World/Services/ShoppingList/IShoppingListService.cs index 702c426..fe9d19a 100644 --- a/Francesco.Recipes.World/Services/ShoppingList/IShoppingListService.cs +++ b/Francesco.Recipes.World/Services/ShoppingList/IShoppingListService.cs @@ -2,6 +2,6 @@ { public interface IShoppingListService { - Task GetShoppingListRecipeCountAsync(Guid shoppingListId); + Task GetShoppingListRecipesCountAsync(Guid shoppingListId); } } From aa5cc3d6835bec9c4c7b5ccda5c8942b5f506275 Mon Sep 17 00:00:00 2001 From: franc Date: Mon, 28 Apr 2025 15:00:50 +0200 Subject: [PATCH 079/183] Use InstructionViewModel --- .../Views/Shared/_GetInstructions.cshtml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml b/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml index 695bda0..5884c07 100644 --- a/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml @@ -1,16 +1,17 @@ -@Html.AntiForgeryToken() +@model Francesco.Recipes.World.Models.InstructionViewModel +@Html.AntiForgeryToken()
- @for (int i = 0; i < Model.Count; i++) + @for (int i = 0; i < Model.Instructions.Count; i++) { -
+
- - + +
- - + +
} @@ -18,6 +19,7 @@ + @section Scripts { + + @await Html.PartialAsync("_ValidationScriptsPartial") + +} From 1e6d354343ecff777c6c66b2c30e0496f1887d77 Mon Sep 17 00:00:00 2001 From: franc Date: Mon, 5 May 2025 12:32:32 +0200 Subject: [PATCH 101/183] Remove js an placed to site.js --- .../Views/Shared/_GetInstructions.cshtml | 118 ++++-------------- .../Views/Shared/_IngredientsPartial.cshtml | 35 ++++++ 2 files changed, 56 insertions(+), 97 deletions(-) create mode 100644 Francesco.Recipes.World/Views/Shared/_IngredientsPartial.cshtml diff --git a/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml b/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml index 0c8a725..aa05493 100644 --- a/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml @@ -5,114 +5,38 @@ {
- - - + @if (Model.Instructions[i].MediaFiles != null && Model.Instructions[i].MediaFiles.Any()) + { + var mediaFile = Model.Instructions[i].MediaFiles.First(); + if (mediaFile.Data != null) + { +
+ Instruction Media +
+ } + } + + + +
- - + +
}
+ + + @section Scripts { } diff --git a/Francesco.Recipes.World/Views/Shared/_IngredientsPartial.cshtml b/Francesco.Recipes.World/Views/Shared/_IngredientsPartial.cshtml new file mode 100644 index 0000000..98670c5 --- /dev/null +++ b/Francesco.Recipes.World/Views/Shared/_IngredientsPartial.cshtml @@ -0,0 +1,35 @@ +@model Francesco.Recipes.World.Models.IngredientViewModel +@Html.AntiForgeryToken() +
+ @for (int i = 0; i < Model.Ingredients.Count; i++) + { +
+
+ + + + +
+
+ } +
+ + + +@section Scripts { + +} From a2e50a01955826e9ed423ebf07ea4409da525d1c Mon Sep 17 00:00:00 2001 From: franc Date: Mon, 5 May 2025 12:33:05 +0200 Subject: [PATCH 102/183] Move all js scripts into this file --- Francesco.Recipes.World/wwwroot/js/site.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Francesco.Recipes.World/wwwroot/js/site.js b/Francesco.Recipes.World/wwwroot/js/site.js index c69a044..12bdab8 100644 --- a/Francesco.Recipes.World/wwwroot/js/site.js +++ b/Francesco.Recipes.World/wwwroot/js/site.js @@ -103,7 +103,7 @@ async function removeInstruction(instructionId, recipeIdParam) { function addInstruction() { const container = document.getElementById('instructions-container'); - const index = document.querySelectorAll('.instruction-item').length; // Index for model binding + const index = document.querySelectorAll('.instruction-item').length; const newInstructionHtml = `
@@ -150,7 +150,7 @@ async function removeIngredient(ingredientId) { async function addIngredient() { const container = document.getElementById('ingredients-container'); - const index = document.querySelectorAll('.ingredient-item').length; // Index for model binding + const index = document.querySelectorAll('.ingredient-item').length; const newIngredientHtml = `
From a0044a8d30d07ebef9755edd69d8f34506627a3e Mon Sep 17 00:00:00 2001 From: franc Date: Mon, 5 May 2025 17:05:30 +0200 Subject: [PATCH 103/183] Check recipeId null --- .../Controller/MediaFile/MediaFileController.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs index 452bb9f..d0bfe25 100644 --- a/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs +++ b/Francesco.Recipes.World/Controller/MediaFile/MediaFileController.cs @@ -72,6 +72,11 @@ [ValidateAntiForgeryToken] public async Task UploadInstructionImage(Guid recipeId, Guid instructionId, IFormFile? photo) { + if (recipeId == Guid.Empty) + { + return BadRequest("Recipe ID is required."); + } + if (photo == null) { return BadRequest("Photo is required."); From 0ff81912a93598dc0ca059586839472293b0b524 Mon Sep 17 00:00:00 2001 From: franc Date: Mon, 5 May 2025 17:07:39 +0200 Subject: [PATCH 104/183] Rename Method from CreateInstructionWithImageToRecipeAsync to CreateInstructionAsync --- .../Repositories/Instruction/IInstructionRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs index 40dc4a1..7de7e9e 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/IInstructionRepository.cs @@ -6,7 +6,7 @@ { Task GetInstructionAsync(Guid instructionId); - Task CreateInstructionWithImageToRecipeAsync(Guid recipeId, string description, IFormFile? photo); + Task CreateInstructionAsync(Guid recipeId, string description, IFormFile? photo); Task RemoveInstructionFromRecipeAsync(Guid recipeId, Guid instructionId); From 402f5f2fe9a498c9ec77abceb62a6809be5ff191 Mon Sep 17 00:00:00 2001 From: franc Date: Mon, 5 May 2025 17:08:17 +0200 Subject: [PATCH 105/183] Rename method --- .../Repositories/Recipe/IRecipeRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs index 63c20af..fdbc876 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs @@ -9,7 +9,7 @@ Task GetRecipeByIdAsync(Guid id); - Task AddOrCreateIngredientToRecipeAsync(Guid recipeId, string ingredientName, int quantity, Guid unitId); + Task CreateRecipeIngredientAsync(Guid recipeId, string ingredientName, int quantity, Guid unitId); Task RemoveIngredientFromRecipeAsync(Guid recipeId, Guid ingredientId); From dc9a1f1b9cd032e2f23a9fc3fcb6b4ad6b3117c1 Mon Sep 17 00:00:00 2001 From: franc Date: Mon, 5 May 2025 17:10:05 +0200 Subject: [PATCH 106/183] Move script section stuff that is implementet on more than one script section on layout --- .../Views/Recipe/RemoveIngredient.cshtml | 3 --- .../Views/Shared/_GetInstructions.cshtml | 6 +----- .../Views/Shared/_IngredientsPartial.cshtml | 6 +----- .../Views/Shared/_Layout.cshtml | 17 +++++++++++++---- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/Francesco.Recipes.World/Views/Recipe/RemoveIngredient.cshtml b/Francesco.Recipes.World/Views/Recipe/RemoveIngredient.cshtml index ca32396..6e868fd 100644 --- a/Francesco.Recipes.World/Views/Recipe/RemoveIngredient.cshtml +++ b/Francesco.Recipes.World/Views/Recipe/RemoveIngredient.cshtml @@ -18,6 +18,3 @@
-@section Scripts { - @await Html.PartialAsync("_ValidationScriptsPartial") -} diff --git a/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml b/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml index aa05493..206818b 100644 --- a/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml @@ -35,8 +35,4 @@ -@section Scripts { - -} + diff --git a/Francesco.Recipes.World/Views/Shared/_IngredientsPartial.cshtml b/Francesco.Recipes.World/Views/Shared/_IngredientsPartial.cshtml index 98670c5..ee780e2 100644 --- a/Francesco.Recipes.World/Views/Shared/_IngredientsPartial.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_IngredientsPartial.cshtml @@ -28,8 +28,4 @@ -@section Scripts { - -} + diff --git a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml index dace8db..463da8d 100644 --- a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml @@ -63,7 +63,16 @@ - - + @await Html.PartialAsync("_ValidationScriptsPartial") @await RenderSectionAsync("Scripts", required: false) From 4928a3cc7da5f66e05b838b2976a7bccc8c270a9 Mon Sep 17 00:00:00 2001 From: franc Date: Mon, 5 May 2025 17:10:40 +0200 Subject: [PATCH 107/183] Move css style on a separate .css file --- .../Views/Recipe/Create.cshtml | 76 +------------------ Francesco.Recipes.World/wwwroot/css/site.css | 65 ++++++++++++++++ 2 files changed, 66 insertions(+), 75 deletions(-) diff --git a/Francesco.Recipes.World/Views/Recipe/Create.cshtml b/Francesco.Recipes.World/Views/Recipe/Create.cshtml index 5e3f77a..b10907b 100644 --- a/Francesco.Recipes.World/Views/Recipe/Create.cshtml +++ b/Francesco.Recipes.World/Views/Recipe/Create.cshtml @@ -78,7 +78,7 @@

Zutaten

@await Html.PartialAsync("_IngredientsPartial", Model.IngredientViewModel ?? new IngredientViewModel { - RecipeId = Model.CategoryId, // Setzt die richtige ID für das globale Script + RecipeId = Model.CategoryId, Ingredients = new List(), Units = ViewBag.Units ?? new List() }) @@ -111,78 +111,4 @@
- - -@section Scripts { - - - @await Html.PartialAsync("_ValidationScriptsPartial") - -} diff --git a/Francesco.Recipes.World/wwwroot/css/site.css b/Francesco.Recipes.World/wwwroot/css/site.css index f8d98fc..a505610 100644 --- a/Francesco.Recipes.World/wwwroot/css/site.css +++ b/Francesco.Recipes.World/wwwroot/css/site.css @@ -19,4 +19,69 @@ html { body { margin-bottom: 60px; +} + +.instruction-item, .ingredient-item { + background-color: #f8f9fa; + padding: 15px; + margin-bottom: 10px; + border-radius: 4px; +} + +.instruction-controls, .ingredient-controls { + display: flex; + align-items: center; + gap: 10px; +} + +.instruction-media { + width: 80px; + height: 80px; + overflow: hidden; + margin-right: 10px; +} + + .instruction-media img { + width: 100%; + height: 100%; + object-fit: cover; + } + +.instruction-file { + max-width: 200px; +} + +textarea.form-control { + min-height: 80px; +} + +.btn-delete, .btn-move-up, .btn-move-down, .btn-save { + background: none; + border: none; + font-size: 1.2rem; + cursor: pointer; +} + +.btn-delete { + color: #dc3545; +} + +.btn-save { + color: #28a745; +} + +.instruction-actions { + display: flex; + justify-content: flex-end; + margin-top: 5px; +} + +.btn-add { + background-color: #007bff; + color: white; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; + margin-top: 10px; } \ No newline at end of file From a0d05dcdc2d9d5740b44b6c1a3e77beff7f7da1e Mon Sep 17 00:00:00 2001 From: franc Date: Mon, 5 May 2025 17:11:18 +0200 Subject: [PATCH 108/183] change from ienumerable to list --- Francesco.Recipes.World/Repositories/Unit/IUnitRepository.cs | 2 +- Francesco.Recipes.World/Repositories/Unit/UnitRepository.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/Unit/IUnitRepository.cs b/Francesco.Recipes.World/Repositories/Unit/IUnitRepository.cs index a8244cd..8ce5267 100644 --- a/Francesco.Recipes.World/Repositories/Unit/IUnitRepository.cs +++ b/Francesco.Recipes.World/Repositories/Unit/IUnitRepository.cs @@ -8,6 +8,6 @@ Task AddUnitAsync(string name, string symbol); - Task> GetAllUnitsAsync(); + Task> GetAllUnitsAsync(); } } diff --git a/Francesco.Recipes.World/Repositories/Unit/UnitRepository.cs b/Francesco.Recipes.World/Repositories/Unit/UnitRepository.cs index 04297cd..6d6a800 100644 --- a/Francesco.Recipes.World/Repositories/Unit/UnitRepository.cs +++ b/Francesco.Recipes.World/Repositories/Unit/UnitRepository.cs @@ -35,7 +35,7 @@ return unit; } - public async Task> GetAllUnitsAsync() + public async Task> GetAllUnitsAsync() { return await _context.Units.ToListAsync(); } From 7168d936216a3b4777f2726767fdc5afabacf27b Mon Sep 17 00:00:00 2001 From: franc Date: Mon, 5 May 2025 17:12:06 +0200 Subject: [PATCH 109/183] Rename Methode --- .../Repositories/Instruction/InstructionRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs index 9f03f28..aed5d2a 100644 --- a/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs +++ b/Francesco.Recipes.World/Repositories/Instruction/InstructionRepository.cs @@ -23,7 +23,7 @@ return instruction ?? throw new InvalidDataException($"Instruction {instructionId} not found."); } - public async Task CreateInstructionWithImageToRecipeAsync(Guid recipeId, string description, IFormFile? photo) + public async Task CreateInstructionAsync(Guid recipeId, string description, IFormFile? photo) { if (string.IsNullOrWhiteSpace(description)) { From abb1d774feb284236c909a56c78a0b397acd44d6 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 7 May 2025 08:53:12 +0200 Subject: [PATCH 110/183] Solve all thread from mergeRequest to RecipeController --- .../Controller/Recipe/RecipeController.cs | 206 ++++++++++-------- 1 file changed, 120 insertions(+), 86 deletions(-) diff --git a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs index 4477962..d9b6996 100644 --- a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs +++ b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs @@ -2,6 +2,8 @@ { using Francesco.Recipes.World.Data; using Francesco.Recipes.World.Models; + using Francesco.Recipes.World.Models.BackendModels.Ingredient; + using Francesco.Recipes.World.Models.BackendModels.Instruction; using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Repositories.Category; using Francesco.Recipes.World.Repositories.Favorit; @@ -14,7 +16,6 @@ using Francesco.Recipes.World.Views.Recipe; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; - using Microsoft.EntityFrameworkCore; public class RecipeController : Controller { @@ -101,6 +102,11 @@ [ValidateAntiForgeryToken] public async Task AddOrCreateIngredient(Guid recipeId, string ingredientName, int quantity, Guid unitId) { + if (recipeId == Guid.Empty) + { + return BadRequest("Recipe ID cannot be empty."); + } + if (quantity <= 0) { ModelState.AddModelError(nameof(quantity), "Die Menge muss größer als 0 sein."); @@ -113,7 +119,7 @@ return View(); } - await _recipeRepository.AddOrCreateIngredientToRecipeAsync(recipeId, ingredientName, quantity, unitId); + await _recipeRepository.CreateRecipeIngredientAsync(recipeId, ingredientName, quantity, unitId); return RedirectToAction("Details", new { id = recipeId }); } @@ -128,32 +134,30 @@ } var units = await _unitRepository.GetAllUnitsAsync(); - ViewBag.Units = new SelectList(units, "Id", "Name"); var viewModel = new CreateRecipeViewModel { CategoryId = categoryId, + CategoryName = category.Name, IngredientViewModel = new IngredientViewModel { RecipeId = Guid.Empty, - Ingredients = new List(), + Ingredients = new List(), Units = units.ToList(), }, InstructionViewModel = new InstructionViewModel { RecipeId = Guid.Empty, - Instructions = new List(), + Instructions = new List(), }, }; - - ViewBag.CategoryName = category.Name; return View(viewModel); } // POST: /Recipe/Create/{categoryId} [HttpPost("Create/{categoryId}")] [ValidateAntiForgeryToken] - public async Task Create(Guid categoryId, CreateRecipeViewModel model, IFormFile? photo, IFormFile? video) + public async Task Create(Guid categoryId, CreateRecipeViewModel model) { if (model == null) { @@ -179,79 +183,124 @@ } var units = await _unitRepository.GetAllUnitsAsync(); - ViewBag.Units = new SelectList(units, "Id", "Name"); - ViewBag.CategoryName = category.Name; + + if (model.IngredientViewModel == null) + { + model.IngredientViewModel = new IngredientViewModel + { + RecipeId = Guid.Empty, + Ingredients = new List(), + Units = units.ToList(), + }; + } + else + { + model.IngredientViewModel.Units = units.ToList(); + } + + model.CategoryName = category.Name; return View(model); } - var categoryEntity = await _categoryRepository.GetCategoryByIdAsync(categoryId); - if (categoryEntity == null) + using var transaction = await _context.Database.BeginTransactionAsync(); + try { - return NotFound($"Kategorie mit ID {categoryId} wurde nicht gefunden."); - } - - model.PreparationTime = new TimeSpan(model.PrepHours, model.PrepMinutes, 0); - model.CookingTime = new TimeSpan(model.CookHours, model.CookMinutes, 0); - - var recipe = await _recipeRepository.CreateRecipeForCategoryAsync( - categoryEntity, - model.Name, - model.Description ?? string.Empty, - model.Difficulty, - model.Servings, - model.PreparationTime, - model.CookingTime); - - if (photo != null) - { - await _mediaFileRepository.UploadRecipeMediaAsync(recipe.Id, photo); - } - - if (video != null) - { - await _mediaFileRepository.UploadRecipeMediaAsync(recipe.Id, video); - } - - if (model.IngredientViewModel?.Ingredients != null) - { - foreach (var ingredient in model.IngredientViewModel.Ingredients) + var categoryEntity = await _categoryRepository.GetCategoryByIdAsync(categoryId); + if (categoryEntity == null) { - var ri = ingredient.RecipeIngredients?.FirstOrDefault(); - if (!string.IsNullOrWhiteSpace(ingredient.Name) && ri?.Quantity > 0 && ri?.Unit?.Id != null) - { - await _recipeRepository.AddOrCreateIngredientToRecipeAsync( - recipe.Id, - ingredient.Name, - ri.Quantity, - ri.Unit.Id); - } + return NotFound($"Kategorie mit ID {categoryId} wurde nicht gefunden."); } - } - if (model.InstructionViewModel?.Instructions != null) - { - for (var i = 0; i < model.InstructionViewModel.Instructions.Count; i++) + model.PreparationTime = new TimeSpan(model.PrepHours, model.PrepMinutes, 0); + model.CookingTime = new TimeSpan(model.CookHours, model.CookMinutes, 0); + + var recipe = await _recipeRepository.CreateRecipeForCategoryAsync( + categoryEntity, + model.Name, + model.Description ?? string.Empty, + model.Difficulty, + model.Servings, + model.PreparationTime, + model.CookingTime); + + if (model.Photo != null) { - var instruction = model.InstructionViewModel.Instructions[i]; - if (!string.IsNullOrWhiteSpace(instruction.Description)) - { - var fileKey = $"InstructionViewModel.Instructions[{i}].MediaFile"; - IFormFile? imageFile = null; + await _mediaFileRepository.UploadRecipeMediaAsync(recipe.Id, model.Photo); + } - if (Request.Form.Files.Any(f => f.Name == fileKey)) + if (model.Video != null) + { + await _mediaFileRepository.UploadRecipeMediaAsync(recipe.Id, model.Video); + } + + if (model.IngredientViewModel?.Ingredients != null) + { + foreach (var ingredient in model.IngredientViewModel.Ingredients) + { + var ri = ingredient.RecipeIngredients?.FirstOrDefault(); + if (!string.IsNullOrWhiteSpace(ingredient.Name) && ri?.Quantity > 0 && ri?.Unit?.Id != null) { - imageFile = Request.Form.Files[fileKey]; + await _recipeRepository.CreateRecipeIngredientAsync( + recipe.Id, + ingredient.Name, + ri.Quantity, + ri.Unit.Id); } - - await _instructionRepository.CreateInstructionWithImageToRecipeAsync( - recipe.Id, - instruction.Description, - imageFile); } } - } - return RedirectToAction("Details", new { recipeId = recipe.Id }); + if (model.InstructionViewModel?.Instructions != null) + { + for (var i = 0; i < model.InstructionViewModel.Instructions.Count; i++) + { + var instruction = model.InstructionViewModel.Instructions[i]; + if (!string.IsNullOrWhiteSpace(instruction.Description)) + { + var fileKey = $"InstructionViewModel.Instructions[{i}].MediaFile"; + IFormFile? imageFile = null; + + if (Request.Form.Files.Any(f => f.Name == fileKey)) + { + imageFile = Request.Form.Files[fileKey]; + } + + await _instructionRepository.CreateInstructionAsync( + recipe.Id, + instruction.Description, + imageFile); + } + } + } + + await transaction.CommitAsync(); + return RedirectToAction("Details", new { recipeId = recipe.Id }); + } + catch (Exception ex) + { + await transaction.RollbackAsync(); + + ModelState.AddModelError(string.Empty, $"Ein Fehler ist aufgetreten beim Erstellen des Rezepts: {ex.Message}"); + + var category = await _categoryRepository.GetCategoryByIdAsync(categoryId); + var units = await _unitRepository.GetAllUnitsAsync(); + + if (model.IngredientViewModel == null) + { + model.IngredientViewModel = new IngredientViewModel + { + RecipeId = Guid.Empty, + Ingredients = new List(), + Units = units.ToList(), + }; + } + else + { + model.IngredientViewModel.Units = units.ToList(); + } + + model.CategoryName = category?.Name ?? string.Empty; + return View(model); + } } // GET: /Recipe/{recipeId}/RemoveIngredient/{ingredientId} @@ -346,11 +395,7 @@ return NotFound("Recipe not found."); } - var instructions = await _context.Instructions - .Include(i => i.MediaFiles) - .Where(i => i.Recipe.Id == recipeId) - .OrderBy(i => i.Number) - .ToListAsync(); + var instructions = await _instructionRepository.GetInstructionsOfRecipeAsync(recipeId); var viewModel = new InstructionViewModel { @@ -368,7 +413,7 @@ { try { - await _instructionRepository.CreateInstructionWithImageToRecipeAsync(recipeId, description, image); + await _instructionRepository.CreateInstructionAsync(recipeId, description, image); var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId); var instructions = recipe?.Instructions?.ToList() ?? new List(); @@ -383,18 +428,7 @@ } catch (Exception ex) { - ModelState.AddModelError(string.Empty, ex.Message); - - var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId); - var instructions = recipe?.Instructions?.ToList() ?? new List(); - - var viewModel = new InstructionViewModel - { - RecipeId = recipeId, - Instructions = instructions, - }; - - return View(viewModel); + return BadRequest(ex.Message); } } @@ -441,7 +475,7 @@ public async Task GetIngredients(Guid recipeId) { var recipeIngredients = (await _ingredientRepository.GetIngredientsByRecipeIdAsync(recipeId)).Select(ri => ri.Ingredient).ToList(); - var units = (await _unitRepository.GetAllUnitsAsync()).ToList(); + var units = await _unitRepository.GetAllUnitsAsync(); var viewModel = new IngredientViewModel { From d172642b1ebedf776b51df4d16b6cd397506603f Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 7 May 2025 08:54:18 +0200 Subject: [PATCH 111/183] Add an new Attribut --- Francesco.Recipes.World/Models/CreateRecipeViewModel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Francesco.Recipes.World/Models/CreateRecipeViewModel.cs b/Francesco.Recipes.World/Models/CreateRecipeViewModel.cs index 3f15930..bccb60a 100644 --- a/Francesco.Recipes.World/Models/CreateRecipeViewModel.cs +++ b/Francesco.Recipes.World/Models/CreateRecipeViewModel.cs @@ -26,6 +26,8 @@ namespace Francesco.Recipes.World.Models public Guid CategoryId { get; set; } + public string? CategoryName { get; set; } + public IFormFile? Photo { get; set; } public IFormFile? Video { get; set; } From 070df12049b89521d72162631425742634b3a39c Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 7 May 2025 08:55:04 +0200 Subject: [PATCH 112/183] Add new Attribut --- Francesco.Recipes.World/Models/InstructionViewModel.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Francesco.Recipes.World/Models/InstructionViewModel.cs b/Francesco.Recipes.World/Models/InstructionViewModel.cs index 28c6911..fc5afda 100644 --- a/Francesco.Recipes.World/Models/InstructionViewModel.cs +++ b/Francesco.Recipes.World/Models/InstructionViewModel.cs @@ -1,11 +1,13 @@ -namespace Francesco.Recipes.World.Models -{ - using Francesco.Recipes.World.Models.BackendModels.Instruction; +using Francesco.Recipes.World.Models.BackendModels.Instruction; +namespace Francesco.Recipes.World.Models +{ public class InstructionViewModel { public Guid RecipeId { get; set; } + public string Description { get; set; } = string.Empty; + public List Instructions { get; set; } = new List(); } } From 7bf43b1855886d78fc65cf029f6fbe918f836268 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 7 May 2025 08:55:40 +0200 Subject: [PATCH 113/183] Change some things --- .../MediaFile/MediaFileRepository.cs | 71 ++++++++++--------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs b/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs index 4e53ba9..d4a8d9a 100644 --- a/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs +++ b/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs @@ -74,45 +74,52 @@ public async Task UploadRecipeMediaAsync(Guid recipeId, IFormFile mediaFile) { - if (mediaFile == null) + if (mediaFile == null || mediaFile.Length == 0) { - throw new ArgumentNullException(nameof(mediaFile)); + return; } - var recipe = await _recipeRepository.GetRecipeAsync(recipeId); - - var isImage = mediaFile.ContentType.StartsWith("image/"); - var isVideo = mediaFile.ContentType.StartsWith("video/"); - - if (!isImage && !isVideo) + try { - throw new InvalidOperationException("Only image or video files are allowed."); + var recipe = await _recipeRepository.GetRecipeAsync(recipeId); + + var isImage = mediaFile.ContentType.StartsWith("image/"); + var isVideo = mediaFile.ContentType.StartsWith("video/"); + + if (!isImage && !isVideo) + { + throw new InvalidOperationException("Only image or video files are allowed."); + } + + if (isImage) + { + await RemoveExistingMediaAsync(recipe, "image/"); + } + else + { + await RemoveExistingMediaAsync(recipe, "video/"); + } + + using var memoryStream = new MemoryStream(); + await mediaFile.CopyToAsync(memoryStream); + + var newMedia = new MediaFile + { + Id = Guid.NewGuid(), + FileName = mediaFile.FileName, + MimeType = mediaFile.ContentType, + Data = memoryStream.ToArray(), + Recipe = recipe, + Instruction = null, + }; + + _context.MediaFiles.Add(newMedia); + await _context.SaveChangesAsync(); } - - if (isImage) + catch (Exception ex) { - await RemoveExistingMediaAsync(recipe, "image/"); + throw new InvalidOperationException("An error occurred while uploading the media file.", ex); } - else - { - await RemoveExistingMediaAsync(recipe, "video/"); - } - - using var memoryStream = new MemoryStream(); - await mediaFile.CopyToAsync(memoryStream); - - var newMedia = new MediaFile - { - Id = Guid.NewGuid(), - FileName = mediaFile.FileName, - MimeType = mediaFile.ContentType, - Data = memoryStream.ToArray(), - Recipe = recipe, - Instruction = null, - }; - - _context.MediaFiles.Add(newMedia); - await _context.SaveChangesAsync(); } private async Task RemoveExistingMediaAsync(Recipe recipe, string mediaTypePrefix) From 12b217ea5905dae6a022e9dc18e0404e0fa90c98 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 7 May 2025 08:56:00 +0200 Subject: [PATCH 114/183] Rename Method --- Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs index c0f6b27..382bf90 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs @@ -41,7 +41,7 @@ .FirstOrDefaultAsync(r => r.Id == recipeId); } - public async Task AddOrCreateIngredientToRecipeAsync(Guid recipeId, string ingredientName, int quantity, Guid unitId) + public async Task CreateRecipeIngredientAsync(Guid recipeId, string ingredientName, int quantity, Guid unitId) { var recipe = await GetRecipeAsync(recipeId); var unit = await _unitRepository.GetUnitByIdAsync(unitId); From a7cafb479b2b61379a09faac3f30d59a321c5435 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 7 May 2025 08:59:23 +0200 Subject: [PATCH 115/183] Use normal List instead interface list --- Francesco.Recipes.World/Repositories/Unit/IUnitRepository.cs | 2 +- Francesco.Recipes.World/Repositories/Unit/UnitRepository.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/Unit/IUnitRepository.cs b/Francesco.Recipes.World/Repositories/Unit/IUnitRepository.cs index 8ce5267..5be0996 100644 --- a/Francesco.Recipes.World/Repositories/Unit/IUnitRepository.cs +++ b/Francesco.Recipes.World/Repositories/Unit/IUnitRepository.cs @@ -8,6 +8,6 @@ Task AddUnitAsync(string name, string symbol); - Task> GetAllUnitsAsync(); + Task> GetAllUnitsAsync(); } } diff --git a/Francesco.Recipes.World/Repositories/Unit/UnitRepository.cs b/Francesco.Recipes.World/Repositories/Unit/UnitRepository.cs index 6d6a800..29a3807 100644 --- a/Francesco.Recipes.World/Repositories/Unit/UnitRepository.cs +++ b/Francesco.Recipes.World/Repositories/Unit/UnitRepository.cs @@ -35,7 +35,7 @@ return unit; } - public async Task> GetAllUnitsAsync() + public async Task> GetAllUnitsAsync() { return await _context.Units.ToListAsync(); } From 03932b380de58fda836a14885caf5ecaf68f5750 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 7 May 2025 08:59:43 +0200 Subject: [PATCH 116/183] change some things --- Francesco.Recipes.World/Views/Shared/_Layout.cshtml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml index 463da8d..a06f9bd 100644 --- a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml @@ -63,16 +63,18 @@ } From bfbfd34624c2401b001db06ae1b624ed99bb361d Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 14 May 2025 11:47:29 +0200 Subject: [PATCH 124/183] Create Grid for show instructions of recipe --- .../_RecipeInstructionGridPartial.cshtml | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Francesco.Recipes.World/Views/Shared/_RecipeInstructionGridPartial.cshtml diff --git a/Francesco.Recipes.World/Views/Shared/_RecipeInstructionGridPartial.cshtml b/Francesco.Recipes.World/Views/Shared/_RecipeInstructionGridPartial.cshtml new file mode 100644 index 0000000..2a7b44b --- /dev/null +++ b/Francesco.Recipes.World/Views/Shared/_RecipeInstructionGridPartial.cshtml @@ -0,0 +1,43 @@ +@model Francesco.Recipes.World.Models.InstructionViewModel + +
+

Instructions

+ +
+ @{ + var groupedInstructions = Model.Instructions.OrderBy(i => i.Number).ToList(); + var rows = (int)Math.Ceiling(groupedInstructions.Count / 3.0); + + for (int row = 0; row < rows; row++) + { +
+ @for (int col = 0; col < 3 && (row * 3 + col < groupedInstructions.Count); col++) + { + var instruction = groupedInstructions[row * 3 + col]; +
+
+ @if (instruction.MediaFiles != null && instruction.MediaFiles.Any()) + { + var mediaFile = instruction.MediaFiles.First(); + @if (mediaFile.Data != null && mediaFile.Data.Length > 0) + { + Step @instruction.Number + } + } + + else + { +
+ +
+ } + @instruction.Number +
+

@instruction.Description

+
+ } +
+ } + } +
+
From e470dd11bbc252e3e76b264c93c1be29f33e1f79 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 14 May 2025 11:57:03 +0200 Subject: [PATCH 125/183] Make a partial view for adjust amount of ingredient --- .../_AdjustableIngredientsPartial.cshtml | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 Francesco.Recipes.World/Views/Shared/_AdjustableIngredientsPartial.cshtml diff --git a/Francesco.Recipes.World/Views/Shared/_AdjustableIngredientsPartial.cshtml b/Francesco.Recipes.World/Views/Shared/_AdjustableIngredientsPartial.cshtml new file mode 100644 index 0000000..7855058 --- /dev/null +++ b/Francesco.Recipes.World/Views/Shared/_AdjustableIngredientsPartial.cshtml @@ -0,0 +1,36 @@ +@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe + +
+
+

Ingredients

+
+ +
+ + + +
+ (Original: @Model.Servings) +
+
+ +
+
    + @foreach (var ingredient in Model.RecipeIngredients) + { +
  • + + @ingredient.Ingredient.Name - + @ingredient.Quantity + @(ingredient.Unit != null ? ingredient.Unit.Symbol : string.Empty) + +
  • + } +
+ +
+
From 144328423626813657714921799ee43a5bc2671c Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 14 May 2025 12:00:54 +0200 Subject: [PATCH 126/183] Add bootastrap for icon by darkMode and LightMode --- Francesco.Recipes.World/Views/Shared/_Layout.cshtml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml index a06f9bd..8eae480 100644 --- a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml @@ -7,6 +7,8 @@ + +
From da2fd1e61d2f424b0cdeeb3f2842b469af599ab4 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 14 May 2025 12:01:20 +0200 Subject: [PATCH 127/183] change redirection to index --- Francesco.Recipes.World/Controller/Recipe/RecipeController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs index 551e8fa..ae4e79f 100644 --- a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs +++ b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs @@ -506,7 +506,7 @@ { await _recipeRepository.DeleteRecipeAsync(recipeId); TempData["SuccessMessage"] = "Rezept wurde erfolgreich gelöscht."; - return RedirectToAction("CategoryRecipes", "Recipe"); + return RedirectToAction("Index", "Home"); } catch (Exception ex) { From a5b70532e6ae4c5092aa87e784cb51fb244b130a Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 14 May 2025 12:01:31 +0200 Subject: [PATCH 128/183] rest --- Francesco.Recipes.World/wwwroot/js/site.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Francesco.Recipes.World/wwwroot/js/site.js b/Francesco.Recipes.World/wwwroot/js/site.js index 12bdab8..20666ef 100644 --- a/Francesco.Recipes.World/wwwroot/js/site.js +++ b/Francesco.Recipes.World/wwwroot/js/site.js @@ -189,3 +189,25 @@ async function addIngredient() { } } + + async function addSelectedIngredientsToShoppingList() { + var form = document.getElementById('ingredient-form'); + var formData = new FormData(form); + var selectedIngredientIds = formData.getAll('ingredientIds'); + + var response = await fetch('/ShoppingList/CreateOrAddIngredients', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ recipeId: '@Model.Id', ingredientIds: selectedIngredientIds }) + }); + + if (response.ok) { + var result = await response.json(); + localStorage.setItem('shoppingListId', result.shoppingListId); + alert('Shopping list updated.'); + } else { + alert('Failed to update shopping list.'); + } +} \ No newline at end of file From 3fe194886740e51e5de68e9a1d4a5fabb4ffa725 Mon Sep 17 00:00:00 2001 From: franc Date: Wed, 14 May 2025 12:01:59 +0200 Subject: [PATCH 129/183] remove Service Folder --- Francesco.Recipes.World/Francesco.Recipes.World.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/Francesco.Recipes.World/Francesco.Recipes.World.csproj b/Francesco.Recipes.World/Francesco.Recipes.World.csproj index 84f6487..e28537c 100644 --- a/Francesco.Recipes.World/Francesco.Recipes.World.csproj +++ b/Francesco.Recipes.World/Francesco.Recipes.World.csproj @@ -49,7 +49,6 @@ - From 9c9253c94a5e3da797d14c800280c41b80a7cf72 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 19 May 2025 13:23:44 +0200 Subject: [PATCH 130/183] Implement endpoint to fetch shopping list details by ID including total recipe count --- .../ShoppingList/ShoppingListController.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs index 648cc7a..8fe8fca 100644 --- a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs +++ b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs @@ -5,6 +5,7 @@ using Francesco.Recipes.World.Repositories.ShoppingList; using Microsoft.AspNetCore.Mvc; + [Route("ShoppingList")] public class ShoppingListController : Controller { private readonly IShoppingListRepository _shoppingListRepository; @@ -39,5 +40,33 @@ return Json(new { shoppingListId = shoppingList.Id }); } + + [HttpGet("RecipeCount")] + public async Task RecipeCount() + { + var count = await _shoppingListRepository.CountAllRecipeShoppinglistsAsync(); + return Json(new { count }); + } + + // GET: /ShoppingList/Details/{id} + [HttpGet("Details/{id}")] + public async Task Details(Guid id) + { + var shoppingList = await _shoppingListRepository.GetByIdAsync(id); + var recipeCount = await _shoppingListRepository.CountAllRecipeShoppinglistsAsync(); + + if (shoppingList == null) + { + return NotFound(); + } + + var viewModel = new ShoppingListDetailsViewModel + { + ShoppingList = shoppingList, + RecipeCount = recipeCount, + }; + + return View(viewModel); + } } } From 00cd006bf87138262ef39084a482b29c51b18a31 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 19 May 2025 13:24:38 +0200 Subject: [PATCH 131/183] Create an initial ViewModel for the ShoppingList Detail --- .../Models/ShoppingListDetailsViewModel.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Francesco.Recipes.World/Models/ShoppingListDetailsViewModel.cs diff --git a/Francesco.Recipes.World/Models/ShoppingListDetailsViewModel.cs b/Francesco.Recipes.World/Models/ShoppingListDetailsViewModel.cs new file mode 100644 index 0000000..757f792 --- /dev/null +++ b/Francesco.Recipes.World/Models/ShoppingListDetailsViewModel.cs @@ -0,0 +1,11 @@ +using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; + +namespace Francesco.Recipes.World.Models +{ + public class ShoppingListDetailsViewModel + { + public ShoppingList ShoppingList { get; set; } = new ShoppingList(); + + public int RecipeCount { get; set; } + } +} From 385be506a7e57630c514bb358fa722cd254a3379 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 19 May 2025 13:25:35 +0200 Subject: [PATCH 132/183] Implement a method to get whole shoppinglist model --- .../ShoppingList/IShoppingListRepository.cs | 2 ++ .../ShoppingList/ShoppingListRepository.cs | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs b/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs index c14f0aa..3b30be5 100644 --- a/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs +++ b/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs @@ -18,6 +18,8 @@ Task GetRecipeByNameAndImageAsync(string recipeName, string imageFileName); + Task GetByIdAsync(Guid shoppingListId); + Task CountAllRecipeShoppinglistsAsync(); } } diff --git a/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs b/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs index 1e64469..f49bfce 100644 --- a/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs +++ b/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs @@ -109,6 +109,22 @@ return shoppingList; } + public async Task GetByIdAsync(Guid shoppingListId) + { + return await _context.ShoppingLists + .Include(sl => sl.RecipeShoppingList) + .ThenInclude(rsl => rsl.Recipe) + .Include(sl => sl.RecipeShoppingList) + .ThenInclude(rsl => rsl.SelectedIngredients) + .ThenInclude(si => si.RecipeIngredient) + .ThenInclude(ri => ri.Ingredient) + .Include(sl => sl.RecipeShoppingList) + .ThenInclude(rsl => rsl.SelectedIngredients) + .ThenInclude(si => si.RecipeIngredient) + .ThenInclude(ri => ri.Unit) + .FirstOrDefaultAsync(sl => sl.Id == shoppingListId); + } + public async Task> GetIngredientsOfRecipeInListAsync(Guid shoppingListRecipeId) { return await _context.RecipeIngredientsShoppingLists From dca567ba9f278b5e356f29d4a8fcba1e5f24f6a6 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 19 May 2025 13:28:26 +0200 Subject: [PATCH 133/183] Show amount of shoppinglists in the view with dynamically change of amount --- .../Views/ShoppingList/Details.cshtml | 78 +++++++------------ 1 file changed, 30 insertions(+), 48 deletions(-) diff --git a/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml b/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml index 1efa149..22b6c11 100644 --- a/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml +++ b/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml @@ -1,56 +1,38 @@ -@model Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList -@using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList -@using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient -@using Francesco.Recipes.World.Models.BackendModels.Recipe - +@model Francesco.Recipes.World.Models.ShoppingListDetailsViewModel @{ ViewData["Title"] = "Einkaufsliste Details"; }

Einkaufsliste Details

- -@if (TempData["SuccessMessage"] != null) -{ -
- @TempData["SuccessMessage"] -
-} - -
-

Einkaufsliste

-
-
-
- ID -
-
- @Model.Id -
-
+
+ Anzahl Rezepte: @Model.RecipeCount
-

Rezepte

- - - - - - - - - @foreach (var recipeShoppingList in Model.RecipeShoppingList) - { - - - - + +@section Scripts { + +} \ No newline at end of file From 8d9eb4b364e10c336a60d49e45eb4f87194e0831 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 20 May 2025 14:22:28 +0200 Subject: [PATCH 134/183] Everywhere an delete operation is name from HttpPost to HttpDelete --- .../Controller/Recipe/RecipeController.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs index ae4e79f..9d31328 100644 --- a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs +++ b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs @@ -305,8 +305,8 @@ return View(); } - // POST: /Recipe/{recipeId}/RemoveIngredient/{ingredientId} - [HttpPost("{recipeId}/RemoveIngredient/{ingredientId}")] + // DELETE: /Recipe/{recipeId}/RemoveIngredient/{ingredientId} + [HttpDelete("{recipeId}/RemoveIngredient/{ingredientId}")] [ValidateAntiForgeryToken] public async Task RemoveIngredientConfirmed(Guid recipeId, Guid ingredientId) { @@ -338,8 +338,8 @@ return View(); } - // POST: /Recipe/{recipeId}/RemoveInstruction/{instructionId} - [HttpPost("{recipeId}/RemoveInstruction/{instructionId}")] + // DELETE: /Recipe/{recipeId}/RemoveInstruction/{instructionId} + [HttpDelete("{recipeId}/RemoveInstruction/{instructionId}")] [ValidateAntiForgeryToken] public async Task RemoveInstructionConfirmed(Guid recipeId, Guid instructionId) { @@ -498,19 +498,20 @@ } // POST: /Recipe/{recipeId}/Delete - [HttpPost("{recipeId}/Delete")] + [HttpDelete("{recipeId}/Delete")] [ValidateAntiForgeryToken] public async Task DeleteConfirmed(Guid recipeId) { - try + var deleted = await _recipeRepository.DeleteRecipeAsync(recipeId); + + if (deleted) { - await _recipeRepository.DeleteRecipeAsync(recipeId); TempData["SuccessMessage"] = "Rezept wurde erfolgreich gelöscht."; return RedirectToAction("Index", "Home"); } - catch (Exception ex) + else { - TempData["ErrorMessage"] = $"Ein Fehler ist aufgetreten: {ex.Message}"; + TempData["ErrorMessage"] = "Rezept nicht gefunden oder konnte nicht gelöscht werden."; return RedirectToAction("Details", new { recipeId }); } } From 0c3b7380c5e9dd9c1c9aa009bf9586d211664c07 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 20 May 2025 14:23:46 +0200 Subject: [PATCH 135/183] Refactoring some Repo methods to not throw exception --- .../Repositories/Recipe/IRecipeRepository.cs | 2 +- .../Repositories/Recipe/RecipeRepository.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs index 2e4b375..5afb2f0 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs @@ -19,6 +19,6 @@ Task> SearchInRecipesAndIngredients(string searchterm); - Task DeleteRecipeAsync(Guid recipeId); + Task DeleteRecipeAsync(Guid recipeId); } } diff --git a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs index 4492d1e..73c76a7 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs @@ -184,13 +184,13 @@ .ToListAsync(); } - public async Task DeleteRecipeAsync(Guid recipeId) + public async Task DeleteRecipeAsync(Guid recipeId) { var recipe = await GetRecipeByIdAsync(recipeId); if (recipe == null) { - throw new InvalidDataException($"Rezept mit ID {recipeId} nicht gefunden."); + return false; } if (recipe.RecipeIngredients != null && recipe.RecipeIngredients.Any()) @@ -224,6 +224,7 @@ _context.Recipes.Remove(recipe); await _context.SaveChangesAsync(); + return true; } } } From 06ef8927a7a6160067d66f4fba14ca26699e5942 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 20 May 2025 14:25:19 +0200 Subject: [PATCH 136/183] Use one way of loading for avoid inconsistency --- Francesco.Recipes.World/Views/Recipe/Details.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Views/Recipe/Details.cshtml b/Francesco.Recipes.World/Views/Recipe/Details.cshtml index e30b936..777ebce 100644 --- a/Francesco.Recipes.World/Views/Recipe/Details.cshtml +++ b/Francesco.Recipes.World/Views/Recipe/Details.cshtml @@ -24,7 +24,7 @@

Cooking Time: @Model.CookingTime

- + @await Html.PartialAsync("_AdjustableIngredientsPartial", Model) @await Html.PartialAsync("_RecipeInstructionGridPartial", new Francesco.Recipes.World.Models.InstructionViewModel { From f89cf27be46b57ced2bd8fde7e1b80c55c1da224 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 20 May 2025 14:26:14 +0200 Subject: [PATCH 137/183] Add LIst instead of use an 2 dimensional array --- .../Views/Shared/_RecipeInstructionGridPartial.cshtml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Francesco.Recipes.World/Views/Shared/_RecipeInstructionGridPartial.cshtml b/Francesco.Recipes.World/Views/Shared/_RecipeInstructionGridPartial.cshtml index 2a7b44b..ed4f682 100644 --- a/Francesco.Recipes.World/Views/Shared/_RecipeInstructionGridPartial.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_RecipeInstructionGridPartial.cshtml @@ -6,12 +6,16 @@
@{ var groupedInstructions = Model.Instructions.OrderBy(i => i.Number).ToList(); - var rows = (int)Math.Ceiling(groupedInstructions.Count / 3.0); + var instructionRows = groupedInstructions + .Select((instruction, index) => new { Instruction = instruction, Index = index }) + .GroupBy(x => x.Index / 3) + .Select(g => g.Select(x => x.Instruction).ToList()) + .ToList(); - for (int row = 0; row < rows; row++) + foreach (var row in instructionRows) {
- @for (int col = 0; col < 3 && (row * 3 + col < groupedInstructions.Count); col++) + @for (var instruction in row) { var instruction = groupedInstructions[row * 3 + col];
From 924b896a9a168811183d73678a8948408f3e007a Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 20 May 2025 14:26:42 +0200 Subject: [PATCH 138/183] Remove empty line --- Francesco.Recipes.World/Views/Shared/_Layout.cshtml | 1 - 1 file changed, 1 deletion(-) diff --git a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml index 8eae480..55fbcc0 100644 --- a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml @@ -8,7 +8,6 @@ -
From e59b11b32f688cf890b6a4e703bed568fdc36859 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 20 May 2025 15:51:18 +0200 Subject: [PATCH 139/183] Make a Method to avoid reduntant code and add a similar method as Getting one Shoppinglist but this time gets All Shoppinglists --- .../ShoppingList/IShoppingListRepository.cs | 2 ++ .../ShoppingList/ShoppingListRepository.cs | 35 +++++++++++++------ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs b/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs index 3b30be5..65e438a 100644 --- a/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs +++ b/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs @@ -21,5 +21,7 @@ Task GetByIdAsync(Guid shoppingListId); Task CountAllRecipeShoppinglistsAsync(); + + Task> GetAllShoppingListsAsync(); } } diff --git a/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs b/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs index f49bfce..d101931 100644 --- a/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs +++ b/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs @@ -111,20 +111,17 @@ public async Task GetByIdAsync(Guid shoppingListId) { - return await _context.ShoppingLists - .Include(sl => sl.RecipeShoppingList) - .ThenInclude(rsl => rsl.Recipe) - .Include(sl => sl.RecipeShoppingList) - .ThenInclude(rsl => rsl.SelectedIngredients) - .ThenInclude(si => si.RecipeIngredient) - .ThenInclude(ri => ri.Ingredient) - .Include(sl => sl.RecipeShoppingList) - .ThenInclude(rsl => rsl.SelectedIngredients) - .ThenInclude(si => si.RecipeIngredient) - .ThenInclude(ri => ri.Unit) + return await GetShoppingListQuery() .FirstOrDefaultAsync(sl => sl.Id == shoppingListId); } + public async Task> GetAllShoppingListsAsync() + { + return await GetShoppingListQuery() + .OrderByDescending(sl => sl.CreatedAt) + .ToListAsync(); + } + public async Task> GetIngredientsOfRecipeInListAsync(Guid shoppingListRecipeId) { return await _context.RecipeIngredientsShoppingLists @@ -189,5 +186,21 @@ return await _context.RecipeShoppingLists .CountAsync(); } + + private IQueryable GetShoppingListQuery() + { + return _context.ShoppingLists + .Include(sl => sl.RecipeShoppingList) + .ThenInclude(rsl => rsl.Recipe) + .ThenInclude(r => r.MediaFiles) + .Include(sl => sl.RecipeShoppingList) + .ThenInclude(rsl => rsl.SelectedIngredients) + .ThenInclude(si => si.RecipeIngredient) + .ThenInclude(ri => ri.Ingredient) + .Include(sl => sl.RecipeShoppingList) + .ThenInclude(rsl => rsl.SelectedIngredients) + .ThenInclude(si => si.RecipeIngredient) + .ThenInclude(ri => ri.Unit); + } } } From 6600c9ab139586022bbc3981a7ddba95edee1073 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 20 May 2025 15:53:15 +0200 Subject: [PATCH 140/183] Add AntiForgeryToken with them stuff on js request --- Francesco.Recipes.World/Views/Recipe/Details.cshtml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Francesco.Recipes.World/Views/Recipe/Details.cshtml b/Francesco.Recipes.World/Views/Recipe/Details.cshtml index 4ea25c7..06fe787 100644 --- a/Francesco.Recipes.World/Views/Recipe/Details.cshtml +++ b/Francesco.Recipes.World/Views/Recipe/Details.cshtml @@ -27,6 +27,7 @@

Ingredients

+ @Html.AntiForgeryToken()
    @foreach (var ingredient in Model.RecipeIngredients) { @@ -48,10 +49,14 @@ var formData = new FormData(form); var selectedIngredientIds = formData.getAll('ingredientIds'); + + var token = document.querySelector('input[name="__RequestVerificationToken"]').value; + var response = await fetch('/ShoppingList/CreateOrAddIngredients', { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'RequestVerificationToken': token }, body: JSON.stringify({ recipeId: '@Model.Id', ingredientIds: selectedIngredientIds }) }); @@ -59,10 +64,11 @@ if (response.ok) { var result = await response.json(); localStorage.setItem('shoppingListId', result.shoppingListId); - alert('Shopping list updated.'); + alert('Zutaten wurden zur Einkaufsliste hinzugefügt.'); } else { - alert('Failed to update shopping list.'); + alert('Fehler beim Hinzufügen zur Einkaufsliste.'); } } + } From 222afcd84fbc2faa27c8fc511f473f0ba8e0c208 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 20 May 2025 15:59:19 +0200 Subject: [PATCH 141/183] Add Caroussel to Shoppinglistdetail Site --- .../Views/ShoppingList/Details.cshtml | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml b/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml index 22b6c11..04250be 100644 --- a/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml +++ b/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml @@ -8,6 +8,56 @@ Anzahl Rezepte: @Model.RecipeCount
+
+ +
+ @foreach (var recipe in Model.RecipesInAnyShoppingList) + { + var mediaFile = recipe.MediaFiles?.FirstOrDefault(); + var imageData = mediaFile?.Data; + var mimeType = mediaFile?.MimeType; + +
+
+
+ +
+
+ @if (imageData != null && mimeType != null) + { + @recipe.Name + } + else + { +
+ +
+ } +
+
+
@recipe.Name
+
+
+
+
+ } + + } + +
+ +
+ + + @section Scripts { } \ No newline at end of file From 69affbdaed0ca726a261b4fb285a46848abfe603 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 20 May 2025 16:04:01 +0200 Subject: [PATCH 142/183] Add a List of Recipes that contains a Shoppinglist to show on ShoppingLIst detail site --- .../Models/ShoppingListDetailsViewModel.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Models/ShoppingListDetailsViewModel.cs b/Francesco.Recipes.World/Models/ShoppingListDetailsViewModel.cs index 757f792..3f168a6 100644 --- a/Francesco.Recipes.World/Models/ShoppingListDetailsViewModel.cs +++ b/Francesco.Recipes.World/Models/ShoppingListDetailsViewModel.cs @@ -1,4 +1,5 @@ -using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; +using Francesco.Recipes.World.Models.BackendModels.Recipe; +using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; namespace Francesco.Recipes.World.Models { @@ -7,5 +8,7 @@ namespace Francesco.Recipes.World.Models public ShoppingList ShoppingList { get; set; } = new ShoppingList(); public int RecipeCount { get; set; } + + public List RecipesInAnyShoppingList { get; set; } = new List(); } } From b1a0a4120202a5fa224d4bc12dd8d91434336fc0 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 20 May 2025 16:05:03 +0200 Subject: [PATCH 143/183] Add Methodf to get all Shoopinglists on endpoint of the Detail Site of Shoppinglist --- .../Controller/ShoppingList/ShoppingListController.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs index 8fe8fca..7557edd 100644 --- a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs +++ b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs @@ -60,10 +60,15 @@ return NotFound(); } + var recipeInAnyShoppingList = (await _shoppingListRepository.GetAllShoppingListsAsync()) + .SelectMany(sl => sl.RecipeShoppingList.Select(rsl => rsl.Recipe)) + .ToList(); + var viewModel = new ShoppingListDetailsViewModel { ShoppingList = shoppingList, RecipeCount = recipeCount, + RecipesInAnyShoppingList = recipeInAnyShoppingList, }; return View(viewModel); From b21f4ea9cd12318403a45722430ee7dead874bf7 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 20 May 2025 16:12:06 +0200 Subject: [PATCH 144/183] - --- Francesco.Recipes.World/Views/Shared/_Layout.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml index a06f9bd..d5f80a7 100644 --- a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml @@ -6,7 +6,7 @@ @ViewData["Title"] - Francesco.Recipes.World - +
From 963d9f4ab1d88baf3dd58bf7c0b3903251587538 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 27 May 2025 11:56:26 +0200 Subject: [PATCH 145/183] Change from HttpPost to HttpDelete --- .../Controller/Recipe/RecipeController.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs index e19eea3..bf3d5c4 100644 --- a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs +++ b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs @@ -305,8 +305,8 @@ return View(); } - // POST: /Recipe/{recipeId}/RemoveIngredient/{ingredientId} - [HttpPost("{recipeId}/RemoveIngredient/{ingredientId}")] + // DELETE: /Recipe/{recipeId}/RemoveIngredient/{ingredientId} + [HttpDelete("{recipeId}/RemoveIngredient/{ingredientId}")] [ValidateAntiForgeryToken] public async Task RemoveIngredientConfirmed(Guid recipeId, Guid ingredientId) { @@ -338,8 +338,8 @@ return View(); } - // POST: /Recipe/{recipeId}/RemoveInstruction/{instructionId} - [HttpPost("{recipeId}/RemoveInstruction/{instructionId}")] + // DELETE: /Recipe/{recipeId}/RemoveInstruction/{instructionId} + [HttpDelete("{recipeId}/RemoveInstruction/{instructionId}")] [ValidateAntiForgeryToken] public async Task RemoveInstructionConfirmed(Guid recipeId, Guid instructionId) { From 5a5b3c5d8718e2fd0ecad86bd0f520674b347298 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 27 May 2025 11:58:18 +0200 Subject: [PATCH 146/183] Add some Endpoint for DeleteOperation and update API Endpoint (Detail) --- .../ShoppingList/ShoppingListController.cs | 89 +++++++++++++++++-- 1 file changed, 83 insertions(+), 6 deletions(-) diff --git a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs index 7557edd..21b9973 100644 --- a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs +++ b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs @@ -4,6 +4,7 @@ using Francesco.Recipes.World.Models; using Francesco.Recipes.World.Repositories.ShoppingList; using Microsoft.AspNetCore.Mvc; + using Microsoft.EntityFrameworkCore; [Route("ShoppingList")] public class ShoppingListController : Controller @@ -49,15 +50,28 @@ } // GET: /ShoppingList/Details/{id} - [HttpGet("Details/{id}")] - public async Task Details(Guid id) + [HttpGet("Details")] + public async Task Details() { - var shoppingList = await _shoppingListRepository.GetByIdAsync(id); var recipeCount = await _shoppingListRepository.CountAllRecipeShoppinglistsAsync(); - if (shoppingList == null) + var recipeIngredientToShoppingListMap = new Dictionary(); + var allEntries = await _context.RecipeIngredientsShoppingLists + .Where(risl => risl.RecipeIngredient != null) + .Select(risl => new + { + RecipeIngredientId = risl.RecipeIngredient.Id, + ShoppingListId = risl.Id, + }) + .ToListAsync(); + + foreach (var entry in allEntries) { - return NotFound(); + if (entry.RecipeIngredientId != Guid.Empty && + !recipeIngredientToShoppingListMap.ContainsKey(entry.RecipeIngredientId)) + { + recipeIngredientToShoppingListMap.Add(entry.RecipeIngredientId, entry.ShoppingListId); + } } var recipeInAnyShoppingList = (await _shoppingListRepository.GetAllShoppingListsAsync()) @@ -66,12 +80,75 @@ var viewModel = new ShoppingListDetailsViewModel { - ShoppingList = shoppingList, RecipeCount = recipeCount, RecipesInAnyShoppingList = recipeInAnyShoppingList, + RecipeIngredientToShoppingListMap = recipeIngredientToShoppingListMap, }; return View(viewModel); } + + [HttpDelete("RemoveIngredients")] + [ValidateAntiForgeryToken] + public async Task RemoveIngredients([FromBody] List recipeIngredientShoppingListIds) + { + if (recipeIngredientShoppingListIds == null || !recipeIngredientShoppingListIds.Any()) + { + return BadRequest("Keine Zutaten zum Entfernen angegeben."); + } + + try + { + var affectedRecipeIds = await _context.RecipeIngredientsShoppingLists + .Where(risl => recipeIngredientShoppingListIds.Contains(risl.Id)) + .Select(risl => risl.RecipeShoppingList.Recipe.Id) + .Distinct() + .ToListAsync(); + + await _shoppingListRepository.RemoveIngredientsFromShoppingListAsync(recipeIngredientShoppingListIds); + + var remainingRecipeIds = await _context.RecipeShoppingLists + .Select(rsl => rsl.Recipe.Id) + .ToListAsync(); + + var removedRecipeIds = affectedRecipeIds + .Where(id => !remainingRecipeIds.Contains(id)) + .ToList(); + + return Json(new { success = true, removedRecipeIds }); + } + catch (Exception ex) + { + return BadRequest(new { error = ex.Message }); + } + } + + [HttpDelete("RemoveRecipeFromList/{recipeId}")] + [ValidateAntiForgeryToken] + public async Task RemoveRecipeFromList(Guid recipeId) + { + try + { + var recipeShoppingLists = await _context.RecipeShoppingLists + .Where(rsl => rsl.Recipe.Id == recipeId) + .ToListAsync(); + + if (!recipeShoppingLists.Any()) + { + return NotFound($"Kein Einkaufslisten-Eintrag für Rezept mit ID {recipeId} gefunden."); + } + + foreach (var recipeShoppingList in recipeShoppingLists) + { + await _shoppingListRepository.RemoveRecipeFromShoppingListAsync(recipeShoppingList.Id); + } + + return Ok(new { success = true }); + } + catch (Exception ex) + { + return BadRequest(new { success = false, error = ex.Message }); + } + } } } From 203b8db1808068e0e79cd8755b1f02f4db2c3027 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 27 May 2025 12:01:06 +0200 Subject: [PATCH 147/183] Delete unnecessary property ShoppingList --- .../Models/ShoppingListDetailsViewModel.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Francesco.Recipes.World/Models/ShoppingListDetailsViewModel.cs b/Francesco.Recipes.World/Models/ShoppingListDetailsViewModel.cs index 3f168a6..10f5152 100644 --- a/Francesco.Recipes.World/Models/ShoppingListDetailsViewModel.cs +++ b/Francesco.Recipes.World/Models/ShoppingListDetailsViewModel.cs @@ -1,14 +1,13 @@ using Francesco.Recipes.World.Models.BackendModels.Recipe; -using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; namespace Francesco.Recipes.World.Models { public class ShoppingListDetailsViewModel { - public ShoppingList ShoppingList { get; set; } = new ShoppingList(); - public int RecipeCount { get; set; } public List RecipesInAnyShoppingList { get; set; } = new List(); + + public Dictionary RecipeIngredientToShoppingListMap { get; set; } = new Dictionary(); } } From ba0e35e52657b40212ebdfa6d26707705fef1a6d Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 27 May 2025 12:07:19 +0200 Subject: [PATCH 148/183] Delete unnecessary Methods and add some better Methods that improves the process --- .../ShoppingList/IShoppingListRepository.cs | 16 +-- .../ShoppingList/ShoppingListRepository.cs | 110 ++++++++++-------- 2 files changed, 68 insertions(+), 58 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs b/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs index 65e438a..63085e4 100644 --- a/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs +++ b/Francesco.Recipes.World/Repositories/ShoppingList/IShoppingListRepository.cs @@ -1,27 +1,19 @@ namespace Francesco.Recipes.World.Repositories.ShoppingList { - using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; - using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; public interface IShoppingListRepository { Task AddIngredientsToShoppingListAsync(Guid recipeId, List ingredientIds); - Task> GetIngredientsOfRecipeInListAsync(Guid shoppingListRecipeId); - - Task UpdateIngredientCheckedAsync(Guid shoppingListRecipeId, Guid recipeIngredientId, bool isChecked); - Task RemoveRecipeIfEmptyAsync(Guid shoppingListRecipeId); - Task DeleteShoppingListAsync(Guid shoppingListId); - - Task GetRecipeByNameAndImageAsync(string recipeName, string imageFileName); - - Task GetByIdAsync(Guid shoppingListId); - Task CountAllRecipeShoppinglistsAsync(); Task> GetAllShoppingListsAsync(); + + Task RemoveIngredientsFromShoppingListAsync(List recipeIngredientShoppngListIds); + + Task RemoveRecipeFromShoppingListAsync(Guid recipeShoppingListId); } } diff --git a/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs b/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs index d101931..be8bba1 100644 --- a/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs +++ b/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs @@ -109,12 +109,6 @@ return shoppingList; } - public async Task GetByIdAsync(Guid shoppingListId) - { - return await GetShoppingListQuery() - .FirstOrDefaultAsync(sl => sl.Id == shoppingListId); - } - public async Task> GetAllShoppingListsAsync() { return await GetShoppingListQuery() @@ -122,63 +116,57 @@ .ToListAsync(); } - public async Task> GetIngredientsOfRecipeInListAsync(Guid shoppingListRecipeId) - { - return await _context.RecipeIngredientsShoppingLists - .Include(i => i.RecipeIngredient) - .ThenInclude(ri => ri.Ingredient) - .Include(i => i.RecipeIngredient.Unit) - .Where(i => i.RecipeShoppingList.Id == shoppingListRecipeId) - .ToListAsync(); - } - - public async Task UpdateIngredientCheckedAsync(Guid shoppingListRecipeId, Guid recipeIngredientId, bool isChecked) - { - var item = await _context.RecipeIngredientsShoppingLists - .FirstOrDefaultAsync(i => - i.RecipeShoppingList.Id == shoppingListRecipeId && - i.RecipeIngredient.Id == recipeIngredientId); - - if (item == null) - { - throw new Exception("Zutat nicht gefunden."); - } - - item.IsChecked = isChecked; - await _context.SaveChangesAsync(); - } - public async Task RemoveRecipeIfEmptyAsync(Guid shoppingListRecipeId) { var recipeEntry = await _context.RecipeShoppingLists .Include(r => r.SelectedIngredients) + .Include(r => r.ShoppingList) + .ThenInclude(sl => sl.RecipeShoppingList) .FirstOrDefaultAsync(r => r.Id == shoppingListRecipeId); if (recipeEntry != null && !recipeEntry.SelectedIngredients.Any()) { + var shoppingList = recipeEntry.ShoppingList; + _context.RecipeShoppingLists.Remove(recipeEntry); await _context.SaveChangesAsync(); + + if (shoppingList != null && (shoppingList.RecipeShoppingList == null || !shoppingList.RecipeShoppingList.Any())) + { + _context.ShoppingLists.Remove(shoppingList); + await _context.SaveChangesAsync(); + } } } - public async Task DeleteShoppingListAsync(Guid shoppingListId) + public async Task RemoveRecipeFromShoppingListAsync(Guid recipeShoppingListId) { - var list = await _context.ShoppingLists - .FirstOrDefaultAsync(sl => sl.Id == shoppingListId); + var recipeEntry = await _context.RecipeShoppingLists + .Include(r => r.SelectedIngredients) + .Include(r => r.ShoppingList) + .ThenInclude(sl => sl.RecipeShoppingList) + .FirstOrDefaultAsync(r => r.Id == recipeShoppingListId); - if (list != null) + if (recipeEntry != null) { - _context.ShoppingLists.Remove(list); - await _context.SaveChangesAsync(); - } - } + var shoppingList = recipeEntry.ShoppingList; - public async Task GetRecipeByNameAndImageAsync(string recipeName, string imageFileName) - { - return await _context.Recipes - .Where(r => r.Name == recipeName && r.MediaFiles.Any(mf => mf.FileName == imageFileName)) - .Include(r => r.MediaFiles.Where(mf => mf.FileName == imageFileName)) - .FirstOrDefaultAsync(); + _context.RecipeIngredientsShoppingLists.RemoveRange(recipeEntry.SelectedIngredients); + + _context.RecipeShoppingLists.Remove(recipeEntry); + await _context.SaveChangesAsync(); + + if (shoppingList != null) + { + await _context.Entry(shoppingList).Collection(sl => sl.RecipeShoppingList).LoadAsync(); + + if (!shoppingList.RecipeShoppingList.Any()) + { + _context.ShoppingLists.Remove(shoppingList); + await _context.SaveChangesAsync(); + } + } + } } public async Task CountAllRecipeShoppinglistsAsync() @@ -187,6 +175,36 @@ .CountAsync(); } + public async Task RemoveIngredientsFromShoppingListAsync(List recipeIngredientShoppingListIds) + { + if (recipeIngredientShoppingListIds == null || !recipeIngredientShoppingListIds.Any()) + { + throw new ArgumentNullException(nameof(recipeIngredientShoppingListIds)); + } + + var affectedRecipeShoppingListIds = await _context.RecipeIngredientsShoppingLists + .Where(risl => recipeIngredientShoppingListIds.Contains(risl.Id)) + .Select(risl => risl.RecipeShoppingList.Id) + .Distinct() + .ToListAsync(); + + foreach (var id in recipeIngredientShoppingListIds) + { + var entry = await _context.RecipeIngredientsShoppingLists.FindAsync(id); + if (entry != null) + { + _context.RecipeIngredientsShoppingLists.Remove(entry); + } + } + + await _context.SaveChangesAsync(); + + foreach (var recipeShoppingListId in affectedRecipeShoppingListIds) + { + await RemoveRecipeIfEmptyAsync(recipeShoppingListId); + } + } + private IQueryable GetShoppingListQuery() { return _context.ShoppingLists From f2b978f35f8687e7ef76c03151b44ce95654413c Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 27 May 2025 12:10:39 +0200 Subject: [PATCH 149/183] Remove unnecessary link arround the recipeCard and improve the Recipe Detail link --- Francesco.Recipes.World/Views/Home/Index.cshtml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Francesco.Recipes.World/Views/Home/Index.cshtml b/Francesco.Recipes.World/Views/Home/Index.cshtml index af2e530..0292082 100644 --- a/Francesco.Recipes.World/Views/Home/Index.cshtml +++ b/Francesco.Recipes.World/Views/Home/Index.cshtml @@ -39,7 +39,6 @@ var mimeType = mediaFile?.MimeType; -
} From 8fe20c32dbfd4fcdbc4f73aed71ad0848d23253c Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 27 May 2025 12:12:36 +0200 Subject: [PATCH 150/183] Add to the FAV button @Html.AntiForgeryToken --- .../Views/Shared/_FavoriteButton.cshtml | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/Francesco.Recipes.World/Views/Shared/_FavoriteButton.cshtml b/Francesco.Recipes.World/Views/Shared/_FavoriteButton.cshtml index 5cd045a..24104af 100644 --- a/Francesco.Recipes.World/Views/Shared/_FavoriteButton.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_FavoriteButton.cshtml @@ -1,25 +1,26 @@ @model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe - + + @Html.AntiForgeryToken() + + + From 3b1d9fa4bc333ff6ccd1d21ca0cf1d930dcd0cb1 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 27 May 2025 12:14:02 +0200 Subject: [PATCH 151/183] Delete unnecessary footer and add JS-Library of SweetAlert --- Francesco.Recipes.World/Views/Shared/_Layout.cshtml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml index d5f80a7..b0646a3 100644 --- a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml @@ -7,6 +7,7 @@ +
@@ -53,15 +54,11 @@
-
-
- © 2024 - Francesco.Recipes.World - Privacy -
-
+ + } \ No newline at end of file From dba048620b0cd575bcab9f0fff0b12428aad2f37 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 27 May 2025 12:19:31 +0200 Subject: [PATCH 153/183] Add some style for Detail.cshtml --- Francesco.Recipes.World/wwwroot/css/site.css | 51 +++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/wwwroot/css/site.css b/Francesco.Recipes.World/wwwroot/css/site.css index a505610..6410704 100644 --- a/Francesco.Recipes.World/wwwroot/css/site.css +++ b/Francesco.Recipes.World/wwwroot/css/site.css @@ -84,4 +84,53 @@ textarea.form-control { border-radius: 4px; cursor: pointer; margin-top: 10px; -} \ No newline at end of file +} + +.recipe-card { + cursor: pointer; + transition: all 0.3s ease; +} + +.active-recipe-card { + border-bottom: 4px solid #000 !important; + box-shadow: 0 4px 8px rgba(0,0,0,0.2); +} + +.selected-ingredient .btn-outline-danger { + background-color: #dc3545 !important; + color: white !important; + border-color: #dc3545 !important; +} + +#removeSelectedButton { + display: none; + margin-top: 15px; + background-color: #FFA500; + color: white; + border: none; + padding: 8px 15px; + border-radius: 4px; + font-weight: bold; + transition: all 0.3s; +} + + #removeSelectedButton:hover { + background-color: #FF8C00; + } + +.ingredient-counter { + position: absolute; + top: -8px; + right: -8px; + background-color: #dc3545; + color: white; + border-radius: 50%; + width: 22px; + height: 22px; + display: flex; + justify-content: center; + align-items: center; + font-size: 0.8rem; + font-weight: bold; +} + From 6290ceaaf01d3484137087fa62e074d6e5e40f2e Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 27 May 2025 12:21:20 +0200 Subject: [PATCH 154/183] Delete unnecessary using statemens --- ...4459_CreateNewTableRecipeIngredientShoppinglist.Designer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Francesco.Recipes.World/Migrations/20250324124459_CreateNewTableRecipeIngredientShoppinglist.Designer.cs b/Francesco.Recipes.World/Migrations/20250324124459_CreateNewTableRecipeIngredientShoppinglist.Designer.cs index 6807402..e98a564 100644 --- a/Francesco.Recipes.World/Migrations/20250324124459_CreateNewTableRecipeIngredientShoppinglist.Designer.cs +++ b/Francesco.Recipes.World/Migrations/20250324124459_CreateNewTableRecipeIngredientShoppinglist.Designer.cs @@ -1,11 +1,8 @@ // -using System; using Francesco.Recipes.World.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable From f2bb057a9bb54b43ce944f8da8c4e75f5ea23be6 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 27 May 2025 13:47:13 +0200 Subject: [PATCH 155/183] Change from HttpPost to HttpDelete --- Francesco.Recipes.World/Controller/Recipe/RecipeController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs index 9d31328..46177ee 100644 --- a/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs +++ b/Francesco.Recipes.World/Controller/Recipe/RecipeController.cs @@ -497,7 +497,7 @@ return View(recipe); } - // POST: /Recipe/{recipeId}/Delete + // DELETE: /Recipe/{recipeId}/Delete [HttpDelete("{recipeId}/Delete")] [ValidateAntiForgeryToken] public async Task DeleteConfirmed(Guid recipeId) From 5c9f6c812069c9af55567171bebc764e39bd5be1 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 27 May 2025 13:47:51 +0200 Subject: [PATCH 156/183] add comment for a specification of this code --- ...4459_CreateNewTableRecipeIngredientShoppinglist.Designer.cs | 3 --- Francesco.Recipes.World/Views/Recipe/Details.cshtml | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Francesco.Recipes.World/Migrations/20250324124459_CreateNewTableRecipeIngredientShoppinglist.Designer.cs b/Francesco.Recipes.World/Migrations/20250324124459_CreateNewTableRecipeIngredientShoppinglist.Designer.cs index 6807402..e98a564 100644 --- a/Francesco.Recipes.World/Migrations/20250324124459_CreateNewTableRecipeIngredientShoppinglist.Designer.cs +++ b/Francesco.Recipes.World/Migrations/20250324124459_CreateNewTableRecipeIngredientShoppinglist.Designer.cs @@ -1,11 +1,8 @@ // -using System; using Francesco.Recipes.World.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable diff --git a/Francesco.Recipes.World/Views/Recipe/Details.cshtml b/Francesco.Recipes.World/Views/Recipe/Details.cshtml index 777ebce..545b9f3 100644 --- a/Francesco.Recipes.World/Views/Recipe/Details.cshtml +++ b/Francesco.Recipes.World/Views/Recipe/Details.cshtml @@ -123,6 +123,8 @@ finalQuantity = Math.round(adjustedQuantity); } } + // Convert small unit "msp" (pinch) to teaspoons (TL) for a better estimate + // 4 pinches = about 1 TL → show that as a note, rounded to 1 decimal else if (specialUnits.smallUnits.includes(unitSymbol)) { if (adjustedQuantity > 3) { finalQuantity = Math.round(adjustedQuantity / 4 * 10) / 10; From 907e19e3983681d282cc67dc31e2eef1a6ec6417 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 27 May 2025 13:48:39 +0200 Subject: [PATCH 157/183] Make the code more readable and easier with some style --- .../_RecipeInstructionGridPartial.cshtml | 52 ++++++---------- Francesco.Recipes.World/wwwroot/css/site.css | 59 ++++++++++--------- 2 files changed, 48 insertions(+), 63 deletions(-) diff --git a/Francesco.Recipes.World/Views/Shared/_RecipeInstructionGridPartial.cshtml b/Francesco.Recipes.World/Views/Shared/_RecipeInstructionGridPartial.cshtml index ed4f682..c814599 100644 --- a/Francesco.Recipes.World/Views/Shared/_RecipeInstructionGridPartial.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_RecipeInstructionGridPartial.cshtml @@ -4,44 +4,28 @@

Instructions

- @{ - var groupedInstructions = Model.Instructions.OrderBy(i => i.Number).ToList(); - var instructionRows = groupedInstructions - .Select((instruction, index) => new { Instruction = instruction, Index = index }) - .GroupBy(x => x.Index / 3) - .Select(g => g.Select(x => x.Instruction).ToList()) - .ToList(); - - foreach (var row in instructionRows) - { -
- @for (var instruction in row) + @foreach (var instruction in Model.Instructions.OrderBy(i => i.Number)) + { +
+
+ @if (instruction.MediaFiles != null && instruction.MediaFiles.Any()) { - var instruction = groupedInstructions[row * 3 + col]; -
-
- @if (instruction.MediaFiles != null && instruction.MediaFiles.Any()) - { - var mediaFile = instruction.MediaFiles.First(); - @if (mediaFile.Data != null && mediaFile.Data.Length > 0) - { - Step @instruction.Number - } - } - - else - { -
- -
- } - @instruction.Number -
-

@instruction.Description

+ var mediaFile = instruction.MediaFiles.First(); + if (mediaFile.Data != null && mediaFile.Data.Length > 0) + { + Step @instruction.Number + } + } + else + { +
+
} + @instruction.Number
- } +

@instruction.Description

+
}
diff --git a/Francesco.Recipes.World/wwwroot/css/site.css b/Francesco.Recipes.World/wwwroot/css/site.css index b8d4e75..c895bb3 100644 --- a/Francesco.Recipes.World/wwwroot/css/site.css +++ b/Francesco.Recipes.World/wwwroot/css/site.css @@ -89,38 +89,36 @@ textarea.form-control { /* Recipe Instructions Grid Layout */ .instructions-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; margin-top: 2rem; margin-bottom: 2rem; } -.instruction-row { - display: flex; - justify-content: space-between; - margin-bottom: 2.5rem; - flex-wrap: wrap; -} - -.instruction-card { - width: calc(33.333% - 20px); - margin-bottom: 1.5rem; -} - @media (max-width: 992px) { - .instruction-card { - width: calc(50% - 15px); + .instructions-grid { + grid-template-columns: repeat(2, 1fr); } } @media (max-width: 576px) { - .instruction-card { - width: 100%; + .instructions-grid { + grid-template-columns: 1fr; } } +.instruction-card { + border: 1px solid #ccc; + padding: 1rem; + border-radius: 8px; + background-color: white; +} + .instruction-image { position: relative; width: 100%; - aspect-ratio: 1/1; + aspect-ratio: 1 / 1; border: 1px solid #ddd; border-radius: 4px; overflow: hidden; @@ -129,36 +127,38 @@ textarea.form-control { align-items: center; justify-content: center; background-color: #f8f9fa; + text-align: center; } .instruction-image img { width: 100%; height: 100%; object-fit: cover; + border-radius: 4px; } .placeholder-image { - display: flex; - align-items: center; - justify-content: center; width: 100%; height: 100%; - color: #aaa; + display: flex; + justify-content: center; + align-items: center; + font-size: 2rem; + color: #999; + background-color: #f0f0f0; + border-radius: 4px; } - .placeholder-image i { - font-size: 2rem; - } - .step-number { position: absolute; - bottom: 5px; - right: 10px; - background-color: rgba(0, 0, 0, 0.5); + bottom: 8px; + right: 8px; + background: rgba(0, 0, 0, 0.6); color: white; - font-weight: bold; padding: 3px 8px; border-radius: 50%; + font-size: 0.875rem; + font-weight: bold; } .instruction-text { @@ -183,3 +183,4 @@ textarea.form-control { background-color: #007bff; } + From c503e008a7653473fb7b3e3267b8e1a1f05d91bd Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 27 May 2025 14:46:59 +0200 Subject: [PATCH 158/183] - remove blank line --- .../Views/Shared/_RecipeInstructionGridPartial.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Views/Shared/_RecipeInstructionGridPartial.cshtml b/Francesco.Recipes.World/Views/Shared/_RecipeInstructionGridPartial.cshtml index c814599..c9b7df8 100644 --- a/Francesco.Recipes.World/Views/Shared/_RecipeInstructionGridPartial.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_RecipeInstructionGridPartial.cshtml @@ -28,4 +28,4 @@
}
- + \ No newline at end of file From 2af526d62364aff2a616b064af6b9919d45c7a04 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 2 Jun 2025 12:31:18 +0200 Subject: [PATCH 159/183] Remove unnecessary id parameter --- .../Controller/ShoppingList/ShoppingListController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs index 21b9973..a1494a7 100644 --- a/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs +++ b/Francesco.Recipes.World/Controller/ShoppingList/ShoppingListController.cs @@ -49,7 +49,7 @@ return Json(new { count }); } - // GET: /ShoppingList/Details/{id} + // GET: /ShoppingList/Details [HttpGet("Details")] public async Task Details() { From 6aeb7edc2bd9256224337352de6316eb6e51ef16 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 2 Jun 2025 12:31:50 +0200 Subject: [PATCH 160/183] Build transaction on Method --- .../ShoppingList/ShoppingListRepository.cs | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs b/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs index be8bba1..fd814b1 100644 --- a/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs +++ b/Francesco.Recipes.World/Repositories/ShoppingList/ShoppingListRepository.cs @@ -2,7 +2,6 @@ { using Francesco.Recipes.World.Data; using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList; - using Francesco.Recipes.World.Models.BackendModels.Recipe; using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList; using Francesco.Recipes.World.Models.BackendModels.Shoppinglist; using Microsoft.EntityFrameworkCore; @@ -182,26 +181,38 @@ throw new ArgumentNullException(nameof(recipeIngredientShoppingListIds)); } - var affectedRecipeShoppingListIds = await _context.RecipeIngredientsShoppingLists - .Where(risl => recipeIngredientShoppingListIds.Contains(risl.Id)) - .Select(risl => risl.RecipeShoppingList.Id) - .Distinct() - .ToListAsync(); + using var transaction = await _context.Database.BeginTransactionAsync(); - foreach (var id in recipeIngredientShoppingListIds) + try { - var entry = await _context.RecipeIngredientsShoppingLists.FindAsync(id); - if (entry != null) + var affectedRecipeShoppingListIds = await _context.RecipeIngredientsShoppingLists + .Where(risl => recipeIngredientShoppingListIds.Contains(risl.Id)) + .Select(risl => risl.RecipeShoppingList.Id) + .Distinct() + .ToListAsync(); + + foreach (var id in recipeIngredientShoppingListIds) { - _context.RecipeIngredientsShoppingLists.Remove(entry); + var entry = await _context.RecipeIngredientsShoppingLists.FindAsync(id); + if (entry != null) + { + _context.RecipeIngredientsShoppingLists.Remove(entry); + } } + + await _context.SaveChangesAsync(); + + foreach (var recipeShoppingListId in affectedRecipeShoppingListIds) + { + await RemoveRecipeIfEmptyAsync(recipeShoppingListId); + } + + await transaction.CommitAsync(); } - - await _context.SaveChangesAsync(); - - foreach (var recipeShoppingListId in affectedRecipeShoppingListIds) + catch { - await RemoveRecipeIfEmptyAsync(recipeShoppingListId); + await transaction.RollbackAsync(); + throw; } } From 258ffb782f47ed8f586c54ce4217d0e540b2904f Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 2 Jun 2025 12:34:35 +0200 Subject: [PATCH 161/183] Move all methods from ShoppingListDetail to main JS and improve JS usage --- Francesco.Recipes.World/wwwroot/js/site.js | 580 ++++++++++++++------- 1 file changed, 402 insertions(+), 178 deletions(-) diff --git a/Francesco.Recipes.World/wwwroot/js/site.js b/Francesco.Recipes.World/wwwroot/js/site.js index 12bdab8..3639c5e 100644 --- a/Francesco.Recipes.World/wwwroot/js/site.js +++ b/Francesco.Recipes.World/wwwroot/js/site.js @@ -1,191 +1,415 @@ -htmx.on('htmx:afterSwap', (event) => { - if (event.target.id === 'instructions-container') { - console.log('Instructions reloaded.'); - } - if (event.target.id === 'ingredients-container') { - console.log('Ingredients reloaded.'); - } -}); +(function (window, document) { + const selectedIngredients = new Set(); + const recipeIngredients = window.recipeIngredients || {}; + let activeRecipeId = Object.keys(recipeIngredients)[0]; + let recipeId; -let recipeId; -function setRecipeId(id) { - recipeId = id; -} + htmx && htmx.on('htmx:afterSwap', (event) => { + if (event.target.id === 'instructions-container') { + console.log('Instructions reloaded.'); + } + if (event.target.id === 'ingredients-container') { + console.log('Ingredients reloaded.'); + } + }); - -async function moveInstructionUp(instructionId, recipeIdParam) { - const idToUse = recipeIdParam || recipeId; - - if (!idToUse) { - alert('Recipe ID is not set. Please select a recipe first.'); - return; + function setRecipeId(id) { + recipeId = id; } - try { - const response = await fetch(`/Recipe/${idToUse}/Instruction/${instructionId}/move-up`, { - method: 'POST', - headers: { - 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value - } + + async function updateRecipeCount() { + try { + const response = await fetch('/ShoppingList/RecipeCount'); + if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); + const data = await response.json(); + document.getElementById('recipeCount').textContent = data.count; + } catch (err) { + console.error("Fehler beim Aktualisieren der Rezeptanzahl:", err); + } + } + + const countUpdateInterval = setInterval(updateRecipeCount, 5000); + + document.addEventListener('shopping-list-changed', updateRecipeCount); + + window.addEventListener('beforeunload', function () { + clearInterval(countUpdateInterval); + }); + + document.addEventListener('DOMContentLoaded', function () { + document.getElementById('carouselLeft')?.addEventListener('click', function () { + document.getElementById('recipeCarousel').scrollBy({ left: -200, behavior: 'smooth' }); + }); + document.getElementById('carouselRight')?.addEventListener('click', function () { + document.getElementById('recipeCarousel').scrollBy({ left: 200, behavior: 'smooth' }); }); - if (response.ok) { - htmx.ajax('GET', `/Recipe/${idToUse}/Instructions`, '#instructions-container'); - } else { - const error = await response.json(); - alert(error.Error || 'Failed to move instruction up.'); - } - } catch (error) { - console.error('Error moving instruction up:', error); - } -} + if (activeRecipeId) setActiveCard(activeRecipeId); - -async function moveInstructionDown(instructionId, recipeIdParam) { - const idToUse = recipeIdParam || recipeId; - - if (!idToUse) { - alert('Recipe ID is not set. Please select a recipe first.'); - return; - } - - try { - const response = await fetch(`/Recipe/${idToUse}/Instruction/${instructionId}/move-down`, { - method: 'POST', - headers: { - 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value - } - }); - - if (response.ok) { - htmx.ajax('GET', `/Recipe/${idToUse}/Instructions`, '#instructions-container'); - } else { - const error = await response.json(); - alert(error.Error || 'Failed to move instruction down.'); - } - } catch (error) { - console.error('Error moving instruction down:', error); - } -} - - -async function removeInstruction(instructionId, recipeIdParam) { - const idToUse = recipeIdParam || recipeId; - - if (!idToUse) { - alert('Recipe ID is not set. Please select a recipe first.'); - return; - } - - if (!confirm('Möchtest du diesen Schritt wirklich löschen?')) return; - - try { - const response = await fetch(`/${idToUse}/RemoveInstruction/${instructionId}`, { - method: 'POST', - headers: { - 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value - } - }); - - if (response.ok) { - const element = document.getElementById(`instruction-${instructionId}`); - if (element) { - element.remove(); - } - } else { - const error = await response.json(); - alert(error.Error || 'Fehler beim Löschen der Anweisung.'); - } - } catch (error) { - console.error('Fehler beim Löschen:', error); - } -} - -function addInstruction() { - const container = document.getElementById('instructions-container'); - const index = document.querySelectorAll('.instruction-item').length; - - const newInstructionHtml = ` -
-
- - - -
-
- - -
-
- `; - container.insertAdjacentHTML('beforeend', newInstructionHtml); -} - - -async function removeIngredient(ingredientId) { - if (!confirm('Möchtest du diese Zutat wirklich löschen?')) return; - - try { - const response = await fetch(`/${recipeId}/RemoveIngredient/${ingredientId}`, { - method: 'POST', - headers: { - 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value - } - }); - - if (response.ok) { - const element = document.getElementById(`ingredient-${ingredientId}`); - if (element) { - element.remove(); - } - } else { - const error = await response.json(); - alert(error.Error || 'Fehler beim Löschen der Zutat.'); - } - } catch (error) { - console.error('Fehler beim Löschen:', error); - } -} - - -async function addIngredient() { - const container = document.getElementById('ingredients-container'); - const index = document.querySelectorAll('.ingredient-item').length; - - const newIngredientHtml = ` -
-
- - - - -
-
- `; - container.insertAdjacentHTML('beforeend', newIngredientHtml); - - const addedElement = document.getElementById(`ingredient-new-${index}`); - const unitSelect = addedElement.querySelector('.unit-select'); - - try { - const response = await fetch('/Unit/GetAllUnits'); - if (response.ok) { - const units = await response.json(); - console.log('Fetched units:', units); - unitSelect.innerHTML = ''; - units.forEach(unit => { - const option = new Option(unit.name, unit.id); - unitSelect.add(option); + document.querySelectorAll('.recipe-card').forEach(card => { + card.addEventListener('click', function () { + setActiveCard(this.dataset.recipeId); }); + }); + }); + + function renderIngredientList(recipeId) { + const list = document.getElementById('ingredientList'); + list.innerHTML = ''; + const ingredients = recipeIngredients[recipeId] || []; + if (ingredients.length === 0) { + list.innerHTML = '
  • Keine Zutaten vorhanden.
  • '; + return; + } + ingredients.forEach(ingredient => { + const isSelected = selectedIngredients.has(ingredient.id); + const li = document.createElement('li'); + li.className = 'list-group-item d-flex justify-content-between align-items-center'; + if (isSelected) li.classList.add('selected-ingredient'); + li.innerHTML = ` + + ${ingredient.name} + ${ingredient.amount} ${ingredient.unit} + + + `; + list.appendChild(li); + }); + updateRemoveSelectedButton(); + } + + function setActiveCard(recipeId) { + document.querySelectorAll('.recipe-card').forEach(card => { + card.classList.toggle('active-recipe-card', card.dataset.recipeId === recipeId); + }); + activeRecipeId = recipeId; + renderIngredientList(recipeId); + } + + function toggleIngredientSelection(ingredientId, button) { + const listItem = button.closest('li'); + if (selectedIngredients.has(ingredientId)) { + selectedIngredients.delete(ingredientId); + listItem.classList.remove('selected-ingredient'); } else { - console.error('Failed to fetch units'); + selectedIngredients.add(ingredientId); + listItem.classList.add('selected-ingredient'); + } + updateRemoveSelectedButton(); + } + + function updateRemoveSelectedButton() { + document.getElementById('removeSelectedButton').style.display = + selectedIngredients.size > 0 ? 'inline-block' : 'none'; + } + + async function removeSelectedIngredients() { + if (selectedIngredients.size === 0) return; + + const shoppingListIds = []; + selectedIngredients.forEach(ingredientId => { + for (const recipeId in recipeIngredients) { + const ingredients = recipeIngredients[recipeId]; + const ingredient = ingredients.find(ing => ing.id === ingredientId); + if (ingredient && ingredient.shoppingListId && + ingredient.shoppingListId !== "00000000-0000-0000-0000-000000000000") { + shoppingListIds.push(ingredient.shoppingListId); + } + } + }); + + if (shoppingListIds.length === 0) { + console.warn("Keine gültigen Shopping List IDs gefunden"); + return; + } + + try { + const response = await fetch('/ShoppingList/RemoveIngredients', { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value, + 'Cache-Control': 'no-cache', + 'Pragma': 'no-cache' + }, + body: JSON.stringify(shoppingListIds) + }); + + if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); + + const result = await response.json(); + + if (result.success) { + window.location.reload(); + } else { + console.error("Fehler beim Entfernen der Zutaten:", result.error); + alert("Beim Entfernen der Zutaten ist ein Fehler aufgetreten."); + } + } catch (err) { + console.error("Fehler beim Entfernen der Zutaten:", err); + alert("Beim Entfernen der Zutaten ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut."); + } + } + + async function removeRecipe(recipeId) { + let confirmed = false; + + if (window.Swal) { + const result = await Swal.fire({ + title: 'Rezept entfernen?', + text: 'Möchten Sie dieses Rezept wirklich aus der Einkaufsliste entfernen?', + icon: 'warning', + showCancelButton: true, + confirmButtonText: 'Ja, entfernen', + cancelButtonText: 'Abbrechen', + confirmButtonColor: '#d33', + cancelButtonColor: '#3085d6' + }); + confirmed = result.isConfirmed; + } else { + confirmed = confirm('Möchten Sie dieses Rezept wirklich aus der Einkaufsliste entfernen?'); + } + + if (!confirmed) return; + + try { + const tokenElement = document.querySelector('input[name="__RequestVerificationToken"]'); + if (!tokenElement) { + console.error('Anti-forgery token not found'); + alert('Fehler: Anti-Forgery Token nicht gefunden'); + return; + } + + const response = await fetch(`/ShoppingList/RemoveRecipeFromList/${recipeId}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'RequestVerificationToken': tokenElement.value + } + }); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const result = await response.json(); + + if (result.success) { + if (window.Swal) { + await Swal.fire({ + title: 'Erfolgreich!', + text: 'Das Rezept wurde entfernt.', + icon: 'success', + timer: 1500, + showConfirmButton: false + }); + } else { + alert('Das Rezept wurde erfolgreich entfernt.'); + } + window.location.reload(); + } else { + const errorMsg = result.error || 'Unbekannter Fehler'; + console.error('Fehler beim Entfernen:', errorMsg); + if (window.Swal) { + await Swal.fire({ + title: 'Fehler', + text: errorMsg, + icon: 'error' + }); + } else { + alert('Fehler: ' + errorMsg); + } + } + } catch (err) { + console.error('Fehler beim Entfernen des Rezepts:', err); + if (window.Swal) { + await Swal.fire({ + title: 'Fehler', + text: 'Beim Löschen ist ein Fehler aufgetreten.', + icon: 'error' + }); + } else { + alert('Beim Löschen ist ein Fehler aufgetreten.'); + } + } + } + + + async function moveInstructionUp(instructionId, recipeIdParam) { + const idToUse = recipeIdParam || recipeId; + if (!idToUse) { + alert('Recipe ID is not set. Please select a recipe first.'); + return; + } + + try { + const response = await fetch(`/Recipe/${idToUse}/Instruction/${instructionId}/move-up`, { + method: 'POST', + headers: { + 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value + } + }); + + if (response.ok) { + htmx.ajax('GET', `/Recipe/${idToUse}/Instructions`, '#instructions-container'); + } else { + const error = await response.json(); + alert(error.Error || 'Failed to move instruction up.'); + } + } catch (error) { + console.error('Error moving instruction up:', error); + } + } + + async function moveInstructionDown(instructionId, recipeIdParam) { + const idToUse = recipeIdParam || recipeId; + if (!idToUse) { + alert('Recipe ID is not set. Please select a recipe first.'); + return; + } + + try { + const response = await fetch(`/Recipe/${idToUse}/Instruction/${instructionId}/move-down`, { + method: 'POST', + headers: { + 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value + } + }); + + if (response.ok) { + htmx.ajax('GET', `/Recipe/${idToUse}/Instructions`, '#instructions-container'); + } else { + const error = await response.json(); + alert(error.Error || 'Failed to move instruction down.'); + } + } catch (error) { + console.error('Error moving instruction down:', error); + } + } + + async function removeInstruction(instructionId, recipeIdParam) { + const idToUse = recipeIdParam || recipeId; + if (!idToUse) { + alert('Recipe ID is not set. Please select a recipe first.'); + return; + } + if (!confirm('Möchtest du diesen Schritt wirklich löschen?')) return; + + try { + const response = await fetch(`/${idToUse}/RemoveInstruction/${instructionId}`, { + method: 'POST', + headers: { + 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value + } + }); + + if (response.ok) { + const element = document.getElementById(`instruction-${instructionId}`); + if (element) element.remove(); + } else { + const error = await response.json(); + alert(error.Error || 'Fehler beim Löschen der Anweisung.'); + } + } catch (error) { + console.error('Fehler beim Löschen:', error); + } + } + + function addInstruction() { + const container = document.getElementById('instructions-container'); + const index = document.querySelectorAll('.instruction-item').length; + + const newInstructionHtml = ` +
    +
    + + + +
    +
    + + +
    +
    + `; + container.insertAdjacentHTML('beforeend', newInstructionHtml); + } + + async function removeIngredient(ingredientId) { + if (!confirm('Möchtest du diese Zutat wirklich löschen?')) return; + + try { + const response = await fetch(`/${recipeId}/RemoveIngredient/${ingredientId}`, { + method: 'POST', + headers: { + 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value + } + }); + + if (response.ok) { + const element = document.getElementById(`ingredient-${ingredientId}`); + if (element) element.remove(); + } else { + const error = await response.json(); + alert(error.Error || 'Fehler beim Löschen der Zutat.'); + } + } catch (error) { + console.error('Fehler beim Löschen:', error); + } + } + + async function addIngredient() { + const container = document.getElementById('ingredients-container'); + const index = document.querySelectorAll('.ingredient-item').length; + + const newIngredientHtml = ` +
    +
    + + + + +
    +
    + `; + container.insertAdjacentHTML('beforeend', newIngredientHtml); + + const addedElement = document.getElementById(`ingredient-new-${index}`); + const unitSelect = addedElement.querySelector('.unit-select'); + + try { + const response = await fetch('/Unit/GetAllUnits'); + if (response.ok) { + const units = await response.json(); + unitSelect.innerHTML = ''; + units.forEach(unit => { + const option = new Option(unit.name, unit.id); + unitSelect.add(option); + }); + } else { + unitSelect.innerHTML = ''; + } + } catch (error) { unitSelect.innerHTML = ''; } - } catch (error) { - console.error('Error fetching units:', error); - unitSelect.innerHTML = ''; } -} + window.Francesco = { + setRecipeId, + moveInstructionUp, + moveInstructionDown, + removeInstruction, + addInstruction, + removeIngredient, + addIngredient, + toggleIngredientSelection, + removeSelectedIngredients, + removeRecipe, + }; + +})(window, document); From 006ffc5ba31e684574dd77035ccf4435cd7fbd1e Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 2 Jun 2025 12:35:04 +0200 Subject: [PATCH 162/183] Use exportet js functions --- .../Views/Shared/_GetInstructions.cshtml | 8 +- .../Views/Shared/_IngredientsPartial.cshtml | 6 +- .../Views/ShoppingList/Details.cshtml | 370 ++++-------------- 3 files changed, 80 insertions(+), 304 deletions(-) diff --git a/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml b/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml index 206818b..ca395d8 100644 --- a/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_GetInstructions.cshtml @@ -19,11 +19,11 @@ - +
    - - + +
    } @@ -32,7 +32,7 @@ - + diff --git a/Francesco.Recipes.World/Views/Shared/_IngredientsPartial.cshtml b/Francesco.Recipes.World/Views/Shared/_IngredientsPartial.cshtml index ee780e2..b465acd 100644 --- a/Francesco.Recipes.World/Views/Shared/_IngredientsPartial.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_IngredientsPartial.cshtml @@ -20,12 +20,14 @@ } } - + + } - + + diff --git a/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml b/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml index 4833282..a816f60 100644 --- a/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml +++ b/Francesco.Recipes.World/Views/ShoppingList/Details.cshtml @@ -24,309 +24,83 @@ }
    -
    -

    Einkaufsliste Details

    -
    - Anzahl Rezepte: @Model.RecipeCount -
    -
    +
    +

    Einkaufsliste Details

    +
    + Anzahl Rezepte: @Model.RecipeCount +
    +
    -
    - -
    - @foreach (var recipe in Model.RecipesInAnyShoppingList) - { - var mediaFile = recipe.MediaFiles?.FirstOrDefault(); - var imageData = mediaFile?.Data; - var mimeType = mediaFile?.MimeType; - var ingredientCount = recipe.RecipeIngredients?.Count() ?? 0; +
    + +
    + @foreach (var recipe in Model.RecipesInAnyShoppingList) + { + var mediaFile = recipe.MediaFiles?.FirstOrDefault(); + var imageData = mediaFile?.Data; + var mimeType = mediaFile?.MimeType; + var ingredientCount = recipe.RecipeIngredients?.Count() ?? 0; -
    -
    -
    - -
    -
    - @if (imageData != null && mimeType != null) - { - @recipe.Name - } - else - { -
    - -
    - } -
    -
    -
    @recipe.Name
    -
    -
    -
    - } -
    - -
    - -
    -
    -
    -

    Zutaten

    -
    -
    -
      -
      - -
      -
      -
      -
      +
      +
      +
      + +
      +
      + @if (imageData != null && mimeType != null) + { + @recipe.Name + } + else + { +
      + +
      + } +
      +
      +
      @recipe.Name
      +
      +
      +
      + } +
      + +
      + +
      +
      +
      +

      Zutaten

      +
      +
      +
        +
        + +
        +
        +
        +
        + @section Scripts { @Html.AntiForgeryToken() -} \ No newline at end of file + +} From bb049e674a2cb7ea41eddf82dea6a5b91400f2f1 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 2 Jun 2025 08:59:35 +0200 Subject: [PATCH 163/183] Make an Endpoint for sort favorit to newsest and oldest --- .../Controller/Favorite/FavoriteController.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 Francesco.Recipes.World/Controller/Favorite/FavoriteController.cs diff --git a/Francesco.Recipes.World/Controller/Favorite/FavoriteController.cs b/Francesco.Recipes.World/Controller/Favorite/FavoriteController.cs new file mode 100644 index 0000000..0198842 --- /dev/null +++ b/Francesco.Recipes.World/Controller/Favorite/FavoriteController.cs @@ -0,0 +1,34 @@ +namespace Francesco.Recipes.World.Controller.Favorite +{ + using Francesco.Recipes.World.Models; + using Francesco.Recipes.World.Repositories.Favorit; + using Microsoft.AspNetCore.Mvc; + + [Route("Favorite")] + public class FavoriteController : Controller + { + private readonly IFavoriteRepository _favoriteRepository; + + public FavoriteController(IFavoriteRepository favoriteRepository) + { + _favoriteRepository = favoriteRepository; + } + + public async Task Index(string sortOrder = "newest") + { + var favoriteRecipes = await _favoriteRepository.GetFavoriteRecipesAsync(); + + var sortedRecipes = sortOrder == "oldest" + ? favoriteRecipes.OrderBy(r => r.Favorit.CreatedAt) + : favoriteRecipes.OrderByDescending(r => r.Favorit.CreatedAt); + + var viewModel = new FavoriteViewModel + { + FavoriteRecipes = sortedRecipes, + SortOrder = sortOrder, + }; + + return View(viewModel); + } + } +} From c9f3f2b3436f5ed889d1d269c000be25f825a4b9 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 2 Jun 2025 09:03:19 +0200 Subject: [PATCH 164/183] Make an viewmodel for the favorit sort --- .../Models/FavoriteViewModel.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Francesco.Recipes.World/Models/FavoriteViewModel.cs diff --git a/Francesco.Recipes.World/Models/FavoriteViewModel.cs b/Francesco.Recipes.World/Models/FavoriteViewModel.cs new file mode 100644 index 0000000..d6aa10b --- /dev/null +++ b/Francesco.Recipes.World/Models/FavoriteViewModel.cs @@ -0,0 +1,15 @@ +using Francesco.Recipes.World.Models.BackendModels.Recipe; + +namespace Francesco.Recipes.World.Models +{ + public class FavoriteViewModel + { + public IEnumerable FavoriteRecipes { get; set; } = new List(); + + public string SortOrder { get; set; } = "newest"; + + public bool HasFavorites => FavoriteRecipes.Any(); + + public string SortOrderDisplayText => SortOrder == "oldest" ? "Älteste Favorits" : "Neueste Favorits"; + } +} From 967542a0f1d30e7541a473bc83a7c44df7c79a90 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 2 Jun 2025 09:06:14 +0200 Subject: [PATCH 165/183] Make a viewmodel for the main search --- .../Models/SearchViewModel.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Francesco.Recipes.World/Models/SearchViewModel.cs diff --git a/Francesco.Recipes.World/Models/SearchViewModel.cs b/Francesco.Recipes.World/Models/SearchViewModel.cs new file mode 100644 index 0000000..c148d5e --- /dev/null +++ b/Francesco.Recipes.World/Models/SearchViewModel.cs @@ -0,0 +1,21 @@ +namespace Francesco.Recipes.World.Models +{ + public class SearchViewModel + { + public Guid Id { get; set; } + + public string Name { get; set; } = string.Empty; + + public string? Description { get; set; } + + public bool IsFavorite { get; set; } + + public byte[]? ImageData { get; set; } + + public string? MimeType { get; set; } + + public List Ingredients { get; set; } = new (); + + public TimeSpan TotalTime { get; set; } + } +} From fcf03d0f6219e57ed96e3216e73085a7fe35695d Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 2 Jun 2025 09:07:27 +0200 Subject: [PATCH 166/183] Use AsSplitQuery for optimize loading related data --- .../Repositories/Category/CategoryRepository.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs b/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs index 821f14f..501b240 100644 --- a/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs +++ b/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs @@ -47,6 +47,7 @@ return await _context.Categories .Include(c => c.Recipes) .ThenInclude(r => r.MediaFiles) + .AsSplitQuery() // Use AsSplitQuery to optimize loading related data .ToListAsync(); } } From 9ab1efde0e0c47914cb79c6781fe490d540e0cfe Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 2 Jun 2025 09:08:34 +0200 Subject: [PATCH 167/183] Use AsSplitQuery to optimize loading related data --- .../Repositories/Category/CategoryRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs b/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs index 501b240..e3e6d11 100644 --- a/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs +++ b/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs @@ -47,7 +47,7 @@ return await _context.Categories .Include(c => c.Recipes) .ThenInclude(r => r.MediaFiles) - .AsSplitQuery() // Use AsSplitQuery to optimize loading related data + .AsSplitQuery() .ToListAsync(); } } From ba619888403580b5974cd2e92cab074e196f3807 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 2 Jun 2025 09:18:38 +0200 Subject: [PATCH 168/183] Ensure that all favorites are retrieved, and when a recipe is added to favorites, include the creation date and time. --- .../Repositories/Favorit/FavoritRepository.cs | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs b/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs index eb8cae2..9993adc 100644 --- a/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs +++ b/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs @@ -16,8 +16,10 @@ public async Task> GetFavoriteRecipesAsync() { return await _context.Recipes - .Where(r => r.IsFavorite) - .ToListAsync(); + .Where(r => r.IsFavorite) + .Include(r => r.Favorit) + .Include(r => r.MediaFiles) + .ToListAsync(); } public async Task IsFavoriteAsync(Guid recipeId) @@ -28,10 +30,27 @@ public async Task AddFavoriteAsync(Guid recipeId) { - var recipe = await _context.Recipes.FindAsync(recipeId); + var recipe = await _context.Recipes + .Include(r => r.Favorit) + .FirstOrDefaultAsync(r => r.Id == recipeId); + if (recipe != null && !recipe.IsFavorite) { recipe.IsFavorite = true; + + if (recipe.Favorit == null || recipe.Favorit.Id == Guid.Empty) + { + recipe.Favorit = new Models.BackendModels.Favorit.Favorit + { + Id = Guid.NewGuid(), + CreatedAt = DateTime.Now, + }; + } + else + { + recipe.Favorit.CreatedAt = DateTime.Now; + } + await _context.SaveChangesAsync(); } } From e1dd694fb70d71cfa27d8cf827777f74a2713178 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 2 Jun 2025 09:20:49 +0200 Subject: [PATCH 169/183] Optimize Searchfilter method --- .../Repositories/Recipe/IRecipeRepository.cs | 4 +- .../Repositories/Recipe/RecipeRepository.cs | 71 +++++++++++++++---- 2 files changed, 61 insertions(+), 14 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs index 5afb2f0..ebbc8c7 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs @@ -1,5 +1,6 @@ namespace Francesco.Recipes.World.Repositories.Recipe { + using Francesco.Recipes.World.Models; using Francesco.Recipes.World.Models.BackendModels.Category; using Francesco.Recipes.World.Models.BackendModels.Recipe; @@ -17,8 +18,7 @@ Task CreateRecipeForCategoryAsync(Category category, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime); - Task> SearchInRecipesAndIngredients(string searchterm); - Task DeleteRecipeAsync(Guid recipeId); + Task> SearchInRecipesAndIngredients(string searchTerm); } } diff --git a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs index 73c76a7..d097f7d 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs @@ -1,6 +1,7 @@ namespace Francesco.Recipes.World.Repositories.Recipe { using Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Models; using Francesco.Recipes.World.Models.BackendModels.Category; using Francesco.Recipes.World.Models.BackendModels.Ingredient; using Francesco.Recipes.World.Models.BackendModels.Recipe; @@ -148,21 +149,67 @@ await _context.SaveChangesAsync(); } - public async Task> SearchInRecipesAndIngredients(string searchTerm) + public async Task> SearchInRecipesAndIngredients(string searchTerm) { - var queryable = _context.Recipes - .Include(r => r.RecipeIngredients) - .ThenInclude(ri => ri.Ingredient) - .AsQueryable(); - - if (!string.IsNullOrWhiteSpace(searchTerm)) + try { - searchTerm = searchTerm.ToLower(); - queryable = queryable.Where(r => r.Name.ToLower().Contains(searchTerm) || - r.RecipeIngredients.Any(ri => ri.Ingredient.Name.ToLower().Contains(searchTerm))); - } + if (string.IsNullOrWhiteSpace(searchTerm)) + { + return await _context.Recipes + .OrderByDescending(r => r.CreatedAt) + .Take(20) + .Select(r => new SearchViewModel + { + Id = r.Id, + Name = r.Name, + Description = r.Description, + IsFavorite = r.IsFavorite, + ImageData = r.MediaFiles + .Where(m => m.MimeType != null && m.MimeType.StartsWith("image/")) + .Select(m => m.Data) + .FirstOrDefault(), + MimeType = r.MediaFiles + .Where(m => m.MimeType != null && m.MimeType.StartsWith("image/")) + .Select(m => m.MimeType) + .FirstOrDefault(), + Ingredients = r.RecipeIngredients.Select(ri => ri.Ingredient.Name).ToList(), + TotalTime = r.PreparationTime.Add(r.CookingTime), + }) + .ToListAsync(); + } - return await queryable.ToListAsync(); + var normalizedSearchTerm = searchTerm.ToLower(); + + return await _context.Recipes + .Where(r => EF.Functions.Like(r.Name.ToLower(), $"%{normalizedSearchTerm}%") || + (r.Description != null && EF.Functions.Like(r.Description.ToLower(), $"%{normalizedSearchTerm}%")) || + r.RecipeIngredients.Any(ri => EF.Functions.Like(ri.Ingredient.Name.ToLower(), $"%{normalizedSearchTerm}%"))) + .OrderByDescending(r => r.CreatedAt) + .Take(100) + .Select(r => new SearchViewModel + { + Id = r.Id, + Name = r.Name, + Description = r.Description, + IsFavorite = r.IsFavorite, + ImageData = r.MediaFiles + .Where(m => m.MimeType != null && m.MimeType.StartsWith("image/")) + .Select(m => m.Data) + .FirstOrDefault(), + MimeType = r.MediaFiles + .Where(m => m.MimeType != null && m.MimeType.StartsWith("image/")) + .Select(m => m.MimeType) + .FirstOrDefault(), + Ingredients = r.RecipeIngredients.Select(ri => ri.Ingredient.Name).ToList(), + TotalTime = r.PreparationTime.Add(r.CookingTime), + }) + .ToListAsync(); + } + catch (Exception ex) + { + Console.WriteLine($"Error in SearchInRecipesAndIngredientsOptimized: {ex.Message}"); + return new List(); + } } public async Task> GetRecipesByDifficultyAsync(Difficulty? difficulty) From 294f6010854bc1848c65ae5854e38e8fcf54eff0 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 2 Jun 2025 09:21:50 +0200 Subject: [PATCH 170/183] Make some simple improvments --- .../Views/Home/Index.cshtml | 84 +++++++++---------- 1 file changed, 40 insertions(+), 44 deletions(-) diff --git a/Francesco.Recipes.World/Views/Home/Index.cshtml b/Francesco.Recipes.World/Views/Home/Index.cshtml index 0c9e60c..031bd3b 100644 --- a/Francesco.Recipes.World/Views/Home/Index.cshtml +++ b/Francesco.Recipes.World/Views/Home/Index.cshtml @@ -1,45 +1,44 @@ @model IEnumerable -@Html.AntiForgeryToken() + @Html.AntiForgeryToken() -
        - Willkommen -

        Willkommen in der Rezept-App

        -
        +
        + Willkommen +

        Willkommen in der Rezept-App

        +
        -
        - - +
        + + -
        +
        -@foreach (var category in Model) -{ -
        -
        -

        @category.Category.Name

        - Alle @category.Category.Name-Rezepte anzeigen -
        + @foreach (var category in Model) + { +
        +
        +

        @category.Category.Name

        + Alle @category.Category.Name-Rezepte anzeigen +
        -
        - @foreach (var recipe in category.Recipes) - { - var mediaFile = recipe.MediaFiles?.FirstOrDefault(); - var imageData = mediaFile?.Data; - var mimeType = mediaFile?.MimeType; +
        + @foreach (var recipe in category.Recipes) + { + var mediaFile = recipe.MediaFiles?.FirstOrDefault(); + var imageData = mediaFile?.Data; + var mimeType = mediaFile?.MimeType; - + } - -} - - + } From 28e60b79fa74644c817a6cc7ea238725156a22de Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 2 Jun 2025 09:25:26 +0200 Subject: [PATCH 171/183] Refactor favorite button: wrap in form for better HTMX and AntiForgeryToken support --- .../Views/Shared/_FavoriteButton.cshtml | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/Francesco.Recipes.World/Views/Shared/_FavoriteButton.cshtml b/Francesco.Recipes.World/Views/Shared/_FavoriteButton.cshtml index 5cd045a..7db2e11 100644 --- a/Francesco.Recipes.World/Views/Shared/_FavoriteButton.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_FavoriteButton.cshtml @@ -1,25 +1,26 @@ @model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe - +
        + @Html.AntiForgeryToken() + + + From c20ed984c22aa4ae4854ea2638b1632c2fbb53ee Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 2 Jun 2025 09:36:55 +0200 Subject: [PATCH 172/183] Replaced direct access to DB model properties with a dedicated ViewModel. Added per-recipe HTMX form including AntiForgeryToken and dynamic star icon based on IsFavorite status. --- .../Views/Home/_SearchResultsPartial.cshtml | 115 ++++++++++-------- 1 file changed, 66 insertions(+), 49 deletions(-) diff --git a/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml b/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml index 4edbb64..c03a7fb 100644 --- a/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml +++ b/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml @@ -1,58 +1,75 @@ -@model IEnumerable +@model IEnumerable @{ if (!Model.Any()) -{ -

        Keine Ergebnisse gefunden.

        -} -else -{ -
        - @foreach (var recipe in Model) - { -
        -
        -
        - @{ - var mediaFile = recipe.MediaFiles.FirstOrDefault(); - - if (mediaFile?.Data != null) - { - @recipe.Name - } - else - { - Platzhalter - } - } -
        - -
        -
        -
        @recipe.Name
        -

        - - @recipe.PreparationTime.Hours h @recipe.PreparationTime.Minutes min -

        + { +

        Keine Ergebnisse gefunden.

        + } + else + { +
        + @foreach (var recipe in Model) + { +
        +
        +
        + @if (recipe.ImageData != null && recipe.MimeType != null) + { + @recipe.Name + } + else + { + Platzhalter + }
        -
        - +
        +
        +
        @recipe.Name
        +

        + + @recipe.TotalTime.Hours h @recipe.TotalTime.Minutes min +

        +
        + +
        +
        +
        + @Html.AntiForgeryToken() + + + +
        + + Details +
        -
        - } -
        + } +
        + } } -} - From 075aefc97dd98af7ffdc1f92546991bfa5807273 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 2 Jun 2025 09:37:19 +0200 Subject: [PATCH 173/183] Make Favorite Site --- .../Views/Favorite/Index.cshtml | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 Francesco.Recipes.World/Views/Favorite/Index.cshtml diff --git a/Francesco.Recipes.World/Views/Favorite/Index.cshtml b/Francesco.Recipes.World/Views/Favorite/Index.cshtml new file mode 100644 index 0000000..b1b84af --- /dev/null +++ b/Francesco.Recipes.World/Views/Favorite/Index.cshtml @@ -0,0 +1,77 @@ +@model Francesco.Recipes.World.Models.FavoriteViewModel +@{ + ViewData["Title"] = "Favoriten"; +} + +

        Favoriten

        + +
        + +
        + +@if (!Model.HasFavorites) +{ +
        + Keine Favoriten vorhanden. Füge Rezepte zu deinen Favoriten hinzu, indem du auf den Stern klickst. +
        +} +else +{ +
        + @foreach (var recipe in Model.FavoriteRecipes) + { +
        +
        + @{ + var mediaFile = recipe.MediaFiles?.FirstOrDefault(m => m.MimeType != null && m.MimeType.StartsWith("image/")); + var imageData = mediaFile?.Data; + var mimeType = mediaFile?.MimeType; + } + +
        + @if (imageData != null && mimeType != null) + { + @recipe.Name + } + else + { +
        Kein Bild
        + } +
        + +
        +
        @recipe.Name
        +
        + + @await Html.PartialAsync("_FavoriteButton", recipe) + + + @(recipe.PreparationTime.TotalMinutes + recipe.CookingTime.TotalMinutes)min + +
        +
        + +
        +
        + } +
        +} From e468a4778290efe1d1aec826a540816e89cb9a74 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 2 Jun 2025 09:46:12 +0200 Subject: [PATCH 174/183] Replaced DbContext registration to use SplitQuery mode --- Francesco.Recipes.World/Program.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Program.cs b/Francesco.Recipes.World/Program.cs index 1caf8cf..60f7325 100644 --- a/Francesco.Recipes.World/Program.cs +++ b/Francesco.Recipes.World/Program.cs @@ -20,7 +20,8 @@ var connectionString = builder.Configuration.GetConnectionString("FrancescosReci ?? throw new InvalidOperationException("Connection string 'FrancescosRecipesWorldDbContextConnection' not found."); services.AddDbContext(options => - options.UseSqlServer(connectionString)); + options.UseSqlServer(connectionString, sqlOptions => + sqlOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery))); // Add services to the container. builder.Services.AddControllersWithViews(); From 40690304fa0c998a738ef4238b0479d916bcbae3 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 3 Jun 2025 09:17:21 +0200 Subject: [PATCH 175/183] Make const folder with all necessary constants --- Francesco.Recipes.World/Constants/ContentType.cs | 7 +++++++ Francesco.Recipes.World/Constants/SortOrders.cs | 8 ++++++++ 2 files changed, 15 insertions(+) create mode 100644 Francesco.Recipes.World/Constants/ContentType.cs create mode 100644 Francesco.Recipes.World/Constants/SortOrders.cs diff --git a/Francesco.Recipes.World/Constants/ContentType.cs b/Francesco.Recipes.World/Constants/ContentType.cs new file mode 100644 index 0000000..8f7d090 --- /dev/null +++ b/Francesco.Recipes.World/Constants/ContentType.cs @@ -0,0 +1,7 @@ +namespace Francesco.Recipes.World.Constants +{ + public class ContentType + { + public const string Image = "image/"; + } +} diff --git a/Francesco.Recipes.World/Constants/SortOrders.cs b/Francesco.Recipes.World/Constants/SortOrders.cs new file mode 100644 index 0000000..479261c --- /dev/null +++ b/Francesco.Recipes.World/Constants/SortOrders.cs @@ -0,0 +1,8 @@ +namespace Francesco.Recipes.World.Constants +{ + public class SortOrders + { + public const string Newest = "newest"; + public const string Oldest = "oldest"; + } +} From febcdc1cedd5093d358736b5e47df19f27e8ecd3 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 3 Jun 2025 09:17:48 +0200 Subject: [PATCH 176/183] Change all magic strings with the consts --- .../Controller/Favorite/FavoriteController.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Francesco.Recipes.World/Controller/Favorite/FavoriteController.cs b/Francesco.Recipes.World/Controller/Favorite/FavoriteController.cs index 0198842..9cd9c7a 100644 --- a/Francesco.Recipes.World/Controller/Favorite/FavoriteController.cs +++ b/Francesco.Recipes.World/Controller/Favorite/FavoriteController.cs @@ -1,5 +1,6 @@ namespace Francesco.Recipes.World.Controller.Favorite { + using Francesco.Recipes.World.Constants; using Francesco.Recipes.World.Models; using Francesco.Recipes.World.Repositories.Favorit; using Microsoft.AspNetCore.Mvc; @@ -14,13 +15,13 @@ _favoriteRepository = favoriteRepository; } - public async Task Index(string sortOrder = "newest") + public async Task Index(string sortOrder = SortOrders.Newest) { var favoriteRecipes = await _favoriteRepository.GetFavoriteRecipesAsync(); - var sortedRecipes = sortOrder == "oldest" - ? favoriteRecipes.OrderBy(r => r.Favorit.CreatedAt) - : favoriteRecipes.OrderByDescending(r => r.Favorit.CreatedAt); + var sortedRecipes = sortOrder == SortOrders.Oldest + ? favoriteRecipes.OrderBy(r => r.Favorite.CreatedAt) + : favoriteRecipes.OrderByDescending(r => r.Favorite.CreatedAt); var viewModel = new FavoriteViewModel { From 17726a4b6302250a76845b255cc9ffcf07d6d34b Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 3 Jun 2025 09:18:26 +0200 Subject: [PATCH 177/183] Make new migration for renaming Favorit columns to Favorite --- ...602133408_RenameFavoriteColumn.Designer.cs | 571 ++++++++++++++++++ .../20250602133408_RenameFavoriteColumn.cs | 72 +++ ...escosRecipesWorldDbContextModelSnapshot.cs | 10 +- 3 files changed, 648 insertions(+), 5 deletions(-) create mode 100644 Francesco.Recipes.World/Migrations/20250602133408_RenameFavoriteColumn.Designer.cs create mode 100644 Francesco.Recipes.World/Migrations/20250602133408_RenameFavoriteColumn.cs diff --git a/Francesco.Recipes.World/Migrations/20250602133408_RenameFavoriteColumn.Designer.cs b/Francesco.Recipes.World/Migrations/20250602133408_RenameFavoriteColumn.Designer.cs new file mode 100644 index 0000000..b7ba81b --- /dev/null +++ b/Francesco.Recipes.World/Migrations/20250602133408_RenameFavoriteColumn.Designer.cs @@ -0,0 +1,571 @@ +// +using System; +using Francesco.Recipes.World.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Francesco.Recipes.World.Migrations +{ + [DbContext(typeof(FrancescosRecipesWorldDbContext))] + [Migration("20250602133408_RenameFavoriteColumn")] + partial class RenameFavoriteColumn + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + + b.HasData( + new + { + Id = new Guid("b248244f-f21c-4555-a14d-5dd49a2717cf"), + Name = "Vorspeisen & Snacks" + }, + new + { + Id = new Guid("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"), + Name = "Erste Gänge" + }, + new + { + Id = new Guid("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"), + Name = "Hauptgerichte" + }, + new + { + Id = new Guid("90deec39-dcd0-422d-9018-ac8389e332e1"), + Name = "Desserts & Süßspeisen" + }, + new + { + Id = new Guid("28e39168-701a-4084-81da-d96c987c462f"), + Name = "Beilagen & Salate" + }, + new + { + Id = new Guid("20585b74-4805-4aff-a6df-aa6b7af04ff1"), + Name = "Kuchen" + }, + new + { + Id = new Guid("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"), + Name = "Hefegebäck & Brot" + }, + new + { + Id = new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"), + Name = "Soßen & Saucen" + }, + new + { + Id = new Guid("23b1c740-e427-44f9-a6ea-d33d3f30f05a"), + Name = "Marmeladen & Eingemachtes" + }, + new + { + Id = new Guid("0a91a200-dc76-4e00-b38c-b38cab5b69d7"), + Name = "Getränke" + }); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Favorits"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Ingredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("IngredientId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsChecked") + .HasColumnType("bit"); + + b.Property("RecipeIngredientId") + .HasColumnType("uniqueidentifier"); + + b.Property("RecipeShoppingListId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IngredientId"); + + b.HasIndex("RecipeIngredientId"); + + b.HasIndex("RecipeShoppingListId"); + + b.ToTable("RecipeIngredientsShoppingLists"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Number") + .HasColumnType("int"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RecipeId"); + + b.ToTable("Instructions"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Data") + .HasColumnType("varbinary(max)"); + + b.Property("FileName") + .HasColumnType("nvarchar(max)"); + + b.Property("InstructionId") + .HasColumnType("uniqueidentifier"); + + b.Property("MimeType") + .HasColumnType("nvarchar(max)"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("InstructionId"); + + b.HasIndex("RecipeId"); + + b.ToTable("MediaFiles"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("CookingTime") + .HasColumnType("time"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Difficulty") + .HasColumnType("int"); + + b.Property("FavoriteId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsFavorite") + .HasColumnType("bit"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PreparationTime") + .HasColumnType("time"); + + b.Property("Servings") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("FavoriteId"); + + b.ToTable("Recipes"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("IngredientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.Property("UnitId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("IngredientId"); + + b.HasIndex("RecipeId"); + + b.HasIndex("UnitId"); + + b.ToTable("RecipeIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("RecipeId") + .HasColumnType("uniqueidentifier"); + + b.Property("ShoppingListId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RecipeId"); + + b.HasIndex("ShoppingListId"); + + b.ToTable("RecipeShoppingLists"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ModifiedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("ShoppingLists"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Symbol") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Units"); + + b.HasData( + new + { + Id = new Guid("62eb2c11-8768-46fb-9db0-c77f940bb4aa"), + Name = "liter", + Symbol = "l" + }, + new + { + Id = new Guid("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"), + Name = "gramm", + Symbol = "g" + }, + new + { + Id = new Guid("7e3d1b86-ac48-45d6-814e-d3492a86db1d"), + Name = "kilogramm", + Symbol = "kg" + }, + new + { + Id = new Guid("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"), + Name = "stücke", + Symbol = "stk" + }, + new + { + Id = new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"), + Name = "blatt", + Symbol = "blatt" + }, + new + { + Id = new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"), + Name = "messerspitze", + Symbol = "msp" + }, + new + { + Id = new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"), + Name = "stange", + Symbol = "stange" + }, + new + { + Id = new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"), + Name = "bund", + Symbol = "bund" + }, + new + { + Id = new Guid("66556e0e-eb2b-4bc3-9a56-135dd508ed09"), + Name = "zehe", + Symbol = "zehe" + }, + new + { + Id = new Guid("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"), + Name = "teelöffel", + Symbol = "TL" + }, + new + { + Id = new Guid("24a91b89-3389-4465-89e4-2f70d2ea6fd7"), + Name = "esslöffel", + Symbol = "EL" + }); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", null) + .WithMany("IngredientShoppingLists") + .HasForeignKey("IngredientId"); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", "RecipeIngredient") + .WithMany() + .HasForeignKey("RecipeIngredientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", "RecipeShoppingList") + .WithMany("SelectedIngredients") + .HasForeignKey("RecipeShoppingListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RecipeIngredient"); + + b.Navigation("RecipeShoppingList"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany("Instructions") + .HasForeignKey("RecipeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Recipe"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", "Instruction") + .WithMany("MediaFiles") + .HasForeignKey("InstructionId"); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany("MediaFiles") + .HasForeignKey("RecipeId"); + + b.Navigation("Instruction"); + + b.Navigation("Recipe"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", "Category") + .WithMany("Recipes") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorite") + .WithMany("Recipe") + .HasForeignKey("FavoriteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Favorite"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient") + .WithMany("RecipeIngredients") + .HasForeignKey("IngredientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany("RecipeIngredients") + .HasForeignKey("RecipeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit") + .WithMany("RecipeIngredient") + .HasForeignKey("UnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ingredient"); + + b.Navigation("Recipe"); + + b.Navigation("Unit"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => + { + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe") + .WithMany() + .HasForeignKey("RecipeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList") + .WithMany("RecipeShoppingList") + .HasForeignKey("ShoppingListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Recipe"); + + b.Navigation("ShoppingList"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b => + { + b.Navigation("Recipes"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b => + { + b.Navigation("Recipe"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b => + { + b.Navigation("IngredientShoppingLists"); + + b.Navigation("RecipeIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b => + { + b.Navigation("MediaFiles"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b => + { + b.Navigation("Instructions"); + + b.Navigation("MediaFiles"); + + b.Navigation("RecipeIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b => + { + b.Navigation("SelectedIngredients"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b => + { + b.Navigation("RecipeShoppingList"); + }); + + modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b => + { + b.Navigation("RecipeIngredient"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Francesco.Recipes.World/Migrations/20250602133408_RenameFavoriteColumn.cs b/Francesco.Recipes.World/Migrations/20250602133408_RenameFavoriteColumn.cs new file mode 100644 index 0000000..79582f6 --- /dev/null +++ b/Francesco.Recipes.World/Migrations/20250602133408_RenameFavoriteColumn.cs @@ -0,0 +1,72 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Francesco.Recipes.World.Migrations +{ + /// + public partial class RenameFavoriteColumn : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + if (migrationBuilder is null) + { + throw new ArgumentNullException(nameof(migrationBuilder)); + } + + migrationBuilder.DropForeignKey( + name: "FK_Recipes_Favorits_FavoritId", + table: "Recipes"); + + migrationBuilder.RenameColumn( + name: "FavoritId", + table: "Recipes", + newName: "FavoriteId"); + + migrationBuilder.RenameIndex( + name: "IX_Recipes_FavoritId", + table: "Recipes", + newName: "IX_Recipes_FavoriteId"); + + migrationBuilder.AddForeignKey( + name: "FK_Recipes_Favorits_FavoriteId", + table: "Recipes", + column: "FavoriteId", + principalTable: "Favorits", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + if (migrationBuilder is null) + { + throw new ArgumentNullException(nameof(migrationBuilder)); + } + + migrationBuilder.DropForeignKey( + name: "FK_Recipes_Favorits_FavoriteId", + table: "Recipes"); + + migrationBuilder.RenameColumn( + name: "FavoriteId", + table: "Recipes", + newName: "FavoritId"); + + migrationBuilder.RenameIndex( + name: "IX_Recipes_FavoriteId", + table: "Recipes", + newName: "IX_Recipes_FavoritId"); + + migrationBuilder.AddForeignKey( + name: "FK_Recipes_Favorits_FavoritId", + table: "Recipes", + column: "FavoritId", + principalTable: "Favorits", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs b/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs index 5def152..67067f0 100644 --- a/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs +++ b/Francesco.Recipes.World/Migrations/FrancescosRecipesWorldDbContextModelSnapshot.cs @@ -221,7 +221,7 @@ namespace Francesco.Recipes.World.Migrations b.Property("Difficulty") .HasColumnType("int"); - b.Property("FavoritId") + b.Property("FavoriteId") .HasColumnType("uniqueidentifier"); b.Property("IsFavorite") @@ -244,7 +244,7 @@ namespace Francesco.Recipes.World.Migrations b.HasIndex("CategoryId"); - b.HasIndex("FavoritId"); + b.HasIndex("FavoriteId"); b.ToTable("Recipes"); }); @@ -460,15 +460,15 @@ namespace Francesco.Recipes.World.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorit") + b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorite") .WithMany("Recipe") - .HasForeignKey("FavoritId") + .HasForeignKey("FavoriteId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.Navigation("Category"); - b.Navigation("Favorit"); + b.Navigation("Favorite"); }); modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b => From 0f274613debe1fb0258a135b5a87e7a49ae7026f Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 3 Jun 2025 09:19:04 +0200 Subject: [PATCH 178/183] Change magic strings with the consts --- Francesco.Recipes.World/Models/FavoriteViewModel.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Francesco.Recipes.World/Models/FavoriteViewModel.cs b/Francesco.Recipes.World/Models/FavoriteViewModel.cs index d6aa10b..b3412c6 100644 --- a/Francesco.Recipes.World/Models/FavoriteViewModel.cs +++ b/Francesco.Recipes.World/Models/FavoriteViewModel.cs @@ -1,4 +1,5 @@ -using Francesco.Recipes.World.Models.BackendModels.Recipe; +using Francesco.Recipes.World.Constants; +using Francesco.Recipes.World.Models.BackendModels.Recipe; namespace Francesco.Recipes.World.Models { @@ -6,10 +7,10 @@ namespace Francesco.Recipes.World.Models { public IEnumerable FavoriteRecipes { get; set; } = new List(); - public string SortOrder { get; set; } = "newest"; + public string SortOrder { get; set; } = SortOrders.Newest; public bool HasFavorites => FavoriteRecipes.Any(); - public string SortOrderDisplayText => SortOrder == "oldest" ? "Älteste Favorits" : "Neueste Favorits"; + public string SortOrderDisplayText => SortOrder == SortOrders.Oldest ? "Älteste Favorits" : "Neueste Favorits"; } } From 3ae229b4f342faee6b60c3ede238798092334767 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 3 Jun 2025 09:19:44 +0200 Subject: [PATCH 179/183] ChangeFavorit to Favorite and change some method logic --- .../Repositories/Favorit/FavoritRepository.cs | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs b/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs index 9993adc..5ca4fa3 100644 --- a/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs +++ b/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs @@ -17,7 +17,7 @@ { return await _context.Recipes .Where(r => r.IsFavorite) - .Include(r => r.Favorit) + .Include(r => r.Favorite) .Include(r => r.MediaFiles) .ToListAsync(); } @@ -31,28 +31,31 @@ public async Task AddFavoriteAsync(Guid recipeId) { var recipe = await _context.Recipes - .Include(r => r.Favorit) + .Include(r => r.Favorite) .FirstOrDefaultAsync(r => r.Id == recipeId); - if (recipe != null && !recipe.IsFavorite) + if (recipe == null) { - recipe.IsFavorite = true; - - if (recipe.Favorit == null || recipe.Favorit.Id == Guid.Empty) - { - recipe.Favorit = new Models.BackendModels.Favorit.Favorit - { - Id = Guid.NewGuid(), - CreatedAt = DateTime.Now, - }; - } - else - { - recipe.Favorit.CreatedAt = DateTime.Now; - } - - await _context.SaveChangesAsync(); + throw new InvalidOperationException("Recipe not found."); } + + if (recipe.IsFavorite) + { + throw new InvalidOperationException("Recipe is already a favorite."); + } + + recipe.IsFavorite = true; + + if (recipe.Favorite == null || recipe.Favorite.Id == Guid.Empty) + { + recipe.Favorite = new Models.BackendModels.Favorit.Favorit + { + Id = Guid.NewGuid(), + CreatedAt = DateTime.Now, + }; + } + + await _context.SaveChangesAsync(); } public async Task RemoveFavoriteAsync(Guid recipeId) From a0e9e8311813f66b7fb080c53587afd9a1b4f457 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 3 Jun 2025 09:20:14 +0200 Subject: [PATCH 180/183] Change magic strings with consts --- .../Repositories/MediaFile/MediaFileRepository.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs b/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs index d4a8d9a..d929250 100644 --- a/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs +++ b/Francesco.Recipes.World/Repositories/MediaFile/MediaFileRepository.cs @@ -1,5 +1,6 @@ namespace Francesco.Recipes.World.Repositories.MediaFile { + using Francesco.Recipes.World.Constants; using Francesco.Recipes.World.Data; using Francesco.Recipes.World.Models.BackendModels.MediaFile; using Francesco.Recipes.World.Models.BackendModels.Recipe; @@ -83,7 +84,7 @@ { var recipe = await _recipeRepository.GetRecipeAsync(recipeId); - var isImage = mediaFile.ContentType.StartsWith("image/"); + var isImage = mediaFile.ContentType.StartsWith(ContentType.Image); var isVideo = mediaFile.ContentType.StartsWith("video/"); if (!isImage && !isVideo) @@ -93,7 +94,7 @@ if (isImage) { - await RemoveExistingMediaAsync(recipe, "image/"); + await RemoveExistingMediaAsync(recipe, ContentType.Image); } else { From 9d90a54830ef67a5aff285472329e2d3f8b5e85e Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 3 Jun 2025 09:20:41 +0200 Subject: [PATCH 181/183] Refactor some code in one method --- .../Repositories/Recipe/RecipeRepository.cs | 74 +++++++++---------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs index d097f7d..3f8c683 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs @@ -1,5 +1,7 @@ namespace Francesco.Recipes.World.Repositories.Recipe { + using System.Linq.Expressions; + using Francesco.Recipes.World.Constants; using Francesco.Recipes.World.Data; using Francesco.Recipes.World.Models; using Francesco.Recipes.World.Models.BackendModels.Category; @@ -158,52 +160,17 @@ return await _context.Recipes .OrderByDescending(r => r.CreatedAt) .Take(20) - .Select(r => new SearchViewModel - { - Id = r.Id, - Name = r.Name, - Description = r.Description, - IsFavorite = r.IsFavorite, - ImageData = r.MediaFiles - .Where(m => m.MimeType != null && m.MimeType.StartsWith("image/")) - .Select(m => m.Data) - .FirstOrDefault(), - MimeType = r.MediaFiles - .Where(m => m.MimeType != null && m.MimeType.StartsWith("image/")) - .Select(m => m.MimeType) - .FirstOrDefault(), - Ingredients = r.RecipeIngredients.Select(ri => ri.Ingredient.Name).ToList(), - TotalTime = r.PreparationTime.Add(r.CookingTime), - }) + .Select(SearchViewModelSelector()) .ToListAsync(); } var normalizedSearchTerm = searchTerm.ToLower(); - return await _context.Recipes - .Where(r => EF.Functions.Like(r.Name.ToLower(), $"%{normalizedSearchTerm}%") || - (r.Description != null && EF.Functions.Like(r.Description.ToLower(), $"%{normalizedSearchTerm}%")) || - r.RecipeIngredients.Any(ri => EF.Functions.Like(ri.Ingredient.Name.ToLower(), $"%{normalizedSearchTerm}%"))) + return await ApplyRecipeSearchFilter(_context.Recipes, normalizedSearchTerm) .OrderByDescending(r => r.CreatedAt) .Take(100) - .Select(r => new SearchViewModel - { - Id = r.Id, - Name = r.Name, - Description = r.Description, - IsFavorite = r.IsFavorite, - ImageData = r.MediaFiles - .Where(m => m.MimeType != null && m.MimeType.StartsWith("image/")) - .Select(m => m.Data) - .FirstOrDefault(), - MimeType = r.MediaFiles - .Where(m => m.MimeType != null && m.MimeType.StartsWith("image/")) - .Select(m => m.MimeType) - .FirstOrDefault(), - Ingredients = r.RecipeIngredients.Select(ri => ri.Ingredient.Name).ToList(), - TotalTime = r.PreparationTime.Add(r.CookingTime), - }) - .ToListAsync(); + .Select(SearchViewModelSelector()) + .ToListAsync(); } catch (Exception ex) { @@ -231,6 +198,35 @@ .ToListAsync(); } + private static Expression> SearchViewModelSelector() + { + return r => new SearchViewModel + { + Id = r.Id, + Name = r.Name, + Description = r.Description, + IsFavorite = r.IsFavorite, + ImageData = r.MediaFiles + .Where(m => m.MimeType != null && m.MimeType.StartsWith(ContentType.Image)) + .Select(m => m.Data) + .FirstOrDefault(), + MimeType = r.MediaFiles + .Where(m => m.MimeType != null && m.MimeType.StartsWith(ContentType.Image)) + .Select(m => m.MimeType) + .FirstOrDefault(), + Ingredients = r.RecipeIngredients.Select(ri => ri.Ingredient.Name).ToList(), + TotalTime = r.PreparationTime.Add(r.CookingTime), + }; + } + + private static IQueryable ApplyRecipeSearchFilter(IQueryable query, string normalizedSearchTerm) + { + return query.Where(r => + EF.Functions.Like(r.Name.ToLower(), $"%{normalizedSearchTerm}%") || + (r.Description != null && EF.Functions.Like(r.Description.ToLower(), $"%{normalizedSearchTerm}%")) || + r.RecipeIngredients.Any(ri => EF.Functions.Like(ri.Ingredient.Name.ToLower(), $"%{normalizedSearchTerm}%"))); + } + public async Task DeleteRecipeAsync(Guid recipeId) { var recipe = await GetRecipeByIdAsync(recipeId); From 78dbfa20f1b0c95bab552057f09dfa31db308607 Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Tue, 3 Jun 2025 09:21:07 +0200 Subject: [PATCH 182/183] Change Favorit to Favorite --- Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs b/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs index 977b9ad..9e688a8 100644 --- a/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs +++ b/Francesco.Recipes.World/Models/BackendModels/Recipe/Recipe.cs @@ -34,7 +34,7 @@ public class Recipe : ITimeStampedEntity public virtual ICollection MediaFiles { get; set; } = new List(); - public virtual Favorit Favorit { get; set; } = new (); + public virtual Favorit Favorite { get; set; } = new (); public virtual Category Category { get; set; } = new (); } From b546d8946ec929a9de85a71959b7b0ee32e5570b Mon Sep 17 00:00:00 2001 From: Francesco Lorenzo D'Amico Date: Mon, 2 Mar 2026 16:35:32 +0100 Subject: [PATCH 183/183] Finished Project --- Francesco.Recipes.World.sln | 6 + .../Francesco.Recipes.World.csproj | 1 - .../Category/CategoryRepository.cs | 43 ++++- .../Category/ICategoryRepository.cs | 3 + .../Repositories/Favorit/FavoritRepository.cs | 1 + .../Repositories/Recipe/IRecipeRepository.cs | 1 + .../Repositories/Recipe/RecipeRepository.cs | 62 +++---- .../Category/CategoryRecipesViewModel.cs | 4 +- .../Views/Home/Index.cshtml | 58 +++---- .../Views/Home/_SearchResultsPartial.cshtml | 2 +- .../Views/Recipe/CategoryRecipes.cshtml | 98 ----------- .../Views/Recipe/Create.cshtml | 2 +- .../Views/Recipe/Details.cshtml | 55 +++--- .../Views/Shared/_FavoriteButton.cshtml | 2 +- .../Views/Shared/_GetInstructions.cshtml | 1 + .../Views/Shared/_IngredientsPartial.cshtml | 4 - .../Views/Shared/_Layout.cshtml | 11 +- Francesco.Recipes.World/wwwroot/js/site.js | 1 + .../FrancescosRecipeWorld Mock.csproj | 26 +++ FrancescosRecipeWorld Mock/MSTestSettings.cs | 1 + FrancescosRecipeWorld Mock/Test1.cs | 156 ++++++++++++++++++ 21 files changed, 333 insertions(+), 205 deletions(-) delete mode 100644 Francesco.Recipes.World/Views/Recipe/CategoryRecipes.cshtml create mode 100644 FrancescosRecipeWorld Mock/FrancescosRecipeWorld Mock.csproj create mode 100644 FrancescosRecipeWorld Mock/MSTestSettings.cs create mode 100644 FrancescosRecipeWorld Mock/Test1.cs diff --git a/Francesco.Recipes.World.sln b/Francesco.Recipes.World.sln index 793bcdf..998bb15 100644 --- a/Francesco.Recipes.World.sln +++ b/Francesco.Recipes.World.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.11.35222.181 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Francesco.Recipes.World", "Francesco.Recipes.World\Francesco.Recipes.World.csproj", "{4D1BBCF4-8E06-4584-A383-B14BEC558408}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrancescosRecipeWorld Mock", "FrancescosRecipeWorld Mock\FrancescosRecipeWorld Mock.csproj", "{04FD3555-3497-4F6B-B2C3-1B22EEFA676B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {4D1BBCF4-8E06-4584-A383-B14BEC558408}.Debug|Any CPU.Build.0 = Debug|Any CPU {4D1BBCF4-8E06-4584-A383-B14BEC558408}.Release|Any CPU.ActiveCfg = Release|Any CPU {4D1BBCF4-8E06-4584-A383-B14BEC558408}.Release|Any CPU.Build.0 = Release|Any CPU + {04FD3555-3497-4F6B-B2C3-1B22EEFA676B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04FD3555-3497-4F6B-B2C3-1B22EEFA676B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {04FD3555-3497-4F6B-B2C3-1B22EEFA676B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04FD3555-3497-4F6B-B2C3-1B22EEFA676B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Francesco.Recipes.World/Francesco.Recipes.World.csproj b/Francesco.Recipes.World/Francesco.Recipes.World.csproj index e28537c..28aea44 100644 --- a/Francesco.Recipes.World/Francesco.Recipes.World.csproj +++ b/Francesco.Recipes.World/Francesco.Recipes.World.csproj @@ -50,5 +50,4 @@ - diff --git a/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs b/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs index e3e6d11..627df0d 100644 --- a/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs +++ b/Francesco.Recipes.World/Repositories/Category/CategoryRepository.cs @@ -4,8 +4,10 @@ using System.Collections.Generic; using System.Threading.Tasks; using Francesco.Recipes.World.Data; + using Francesco.Recipes.World.Models; using Francesco.Recipes.World.Models.BackendModels.Category; using Francesco.Recipes.World.Models.BackendModels.Recipe; + using Francesco.Recipes.World.Views.Category; using Microsoft.EntityFrameworkCore; public class CategoryRepository : ICategoryRepository @@ -44,11 +46,44 @@ public async Task> GetAllCategoriesWithRecipesAsync() { - return await _context.Categories - .Include(c => c.Recipes) - .ThenInclude(r => r.MediaFiles) - .AsSplitQuery() + var categories = await _context.Categories + .Include(c => c.Recipes.OrderByDescending(r => r.CreatedAt).Take(3)) + .ThenInclude(r => r.MediaFiles.Take(2)) + .AsSplitQuery() + .ToListAsync(); + + return categories; + } + + public async Task> GetAllCategoriesWithRecipesViewModelAsync() + { + var categories = await _context.Categories + .Select(c => new CategoryRecipesViewModel + { + Category = c, + Recipes = c.Recipes + .OrderByDescending(r => r.CreatedAt) + .Take(3) + .Select(r => new RecipeCardViewModel + { + Id = r.Id, + Name = r.Name, + CookingTime = r.CookingTime, + IsFavorite = r.IsFavorite, + ImageData = r.MediaFiles + .OrderBy(m => m.Id) + .Select(m => m.Data) + .FirstOrDefault(), + MimeType = r.MediaFiles + .OrderBy(m => m.Id) + .Select(m => m.MimeType) + .FirstOrDefault(), + }), + }) + .AsSplitQuery() .ToListAsync(); + + return categories; } } } diff --git a/Francesco.Recipes.World/Repositories/Category/ICategoryRepository.cs b/Francesco.Recipes.World/Repositories/Category/ICategoryRepository.cs index f449de0..3978132 100644 --- a/Francesco.Recipes.World/Repositories/Category/ICategoryRepository.cs +++ b/Francesco.Recipes.World/Repositories/Category/ICategoryRepository.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Francesco.Recipes.World.Models.BackendModels.Category; using Francesco.Recipes.World.Models.BackendModels.Recipe; + using Francesco.Recipes.World.Views.Category; public interface ICategoryRepository { @@ -15,5 +16,7 @@ Task> GetRecipesByCategoryAsync(Guid categoryId); Task> GetAllCategoriesWithRecipesAsync(); + + Task> GetAllCategoriesWithRecipesViewModelAsync(); } } diff --git a/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs b/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs index 5ca4fa3..9c996c1 100644 --- a/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs +++ b/Francesco.Recipes.World/Repositories/Favorit/FavoritRepository.cs @@ -19,6 +19,7 @@ .Where(r => r.IsFavorite) .Include(r => r.Favorite) .Include(r => r.MediaFiles) + .Take(6) .ToListAsync(); } diff --git a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs index ebbc8c7..4fccc61 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/IRecipeRepository.cs @@ -19,6 +19,7 @@ Task CreateRecipeForCategoryAsync(Category category, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime); Task DeleteRecipeAsync(Guid recipeId); + Task> SearchInRecipesAndIngredients(string searchTerm); } } diff --git a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs index 3f8c683..2dd0a2b 100644 --- a/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs +++ b/Francesco.Recipes.World/Repositories/Recipe/RecipeRepository.cs @@ -198,35 +198,6 @@ .ToListAsync(); } - private static Expression> SearchViewModelSelector() - { - return r => new SearchViewModel - { - Id = r.Id, - Name = r.Name, - Description = r.Description, - IsFavorite = r.IsFavorite, - ImageData = r.MediaFiles - .Where(m => m.MimeType != null && m.MimeType.StartsWith(ContentType.Image)) - .Select(m => m.Data) - .FirstOrDefault(), - MimeType = r.MediaFiles - .Where(m => m.MimeType != null && m.MimeType.StartsWith(ContentType.Image)) - .Select(m => m.MimeType) - .FirstOrDefault(), - Ingredients = r.RecipeIngredients.Select(ri => ri.Ingredient.Name).ToList(), - TotalTime = r.PreparationTime.Add(r.CookingTime), - }; - } - - private static IQueryable ApplyRecipeSearchFilter(IQueryable query, string normalizedSearchTerm) - { - return query.Where(r => - EF.Functions.Like(r.Name.ToLower(), $"%{normalizedSearchTerm}%") || - (r.Description != null && EF.Functions.Like(r.Description.ToLower(), $"%{normalizedSearchTerm}%")) || - r.RecipeIngredients.Any(ri => EF.Functions.Like(ri.Ingredient.Name.ToLower(), $"%{normalizedSearchTerm}%"))); - } - public async Task DeleteRecipeAsync(Guid recipeId) { var recipe = await GetRecipeByIdAsync(recipeId); @@ -259,9 +230,9 @@ _context.MediaFiles.RemoveRange(recipe.MediaFiles); } - if (recipe.Favorit != null && recipe.Favorit.Id != Guid.Empty) + if (recipe.Favorite != null && recipe.Favorite.Id != Guid.Empty) { - _context.Remove(recipe.Favorit); + _context.Remove(recipe.Favorite); } _context.Recipes.Remove(recipe); @@ -269,5 +240,34 @@ await _context.SaveChangesAsync(); return true; } + + private static Expression> SearchViewModelSelector() + { + return r => new SearchViewModel + { + Id = r.Id, + Name = r.Name, + Description = r.Description, + IsFavorite = r.IsFavorite, + ImageData = r.MediaFiles + .Where(m => m.MimeType != null && m.MimeType.StartsWith(ContentType.Image)) + .Select(m => m.Data) + .FirstOrDefault(), + MimeType = r.MediaFiles + .Where(m => m.MimeType != null && m.MimeType.StartsWith(ContentType.Image)) + .Select(m => m.MimeType) + .FirstOrDefault(), + Ingredients = r.RecipeIngredients.Select(ri => ri.Ingredient.Name).ToList(), + TotalTime = r.PreparationTime.Add(r.CookingTime), + }; + } + + private static IQueryable ApplyRecipeSearchFilter(IQueryable query, string normalizedSearchTerm) + { + return query.Where(r => + EF.Functions.Like(r.Name.ToLower(), $"%{normalizedSearchTerm}%") || + (r.Description != null && EF.Functions.Like(r.Description.ToLower(), $"%{normalizedSearchTerm}%")) || + r.RecipeIngredients.Any(ri => EF.Functions.Like(ri.Ingredient.Name.ToLower(), $"%{normalizedSearchTerm}%"))); + } } } diff --git a/Francesco.Recipes.World/Views/Category/CategoryRecipesViewModel.cs b/Francesco.Recipes.World/Views/Category/CategoryRecipesViewModel.cs index 1559dd8..1b2fe4c 100644 --- a/Francesco.Recipes.World/Views/Category/CategoryRecipesViewModel.cs +++ b/Francesco.Recipes.World/Views/Category/CategoryRecipesViewModel.cs @@ -1,12 +1,12 @@ namespace Francesco.Recipes.World.Views.Category { + using Francesco.Recipes.World.Models; using Francesco.Recipes.World.Models.BackendModels.Category; - using Francesco.Recipes.World.Models.BackendModels.Recipe; public class CategoryRecipesViewModel { public Category Category { get; set; } = new (); - public IEnumerable Recipes { get; set; } = new List(); + public IEnumerable Recipes { get; set; } = new List(); } } diff --git a/Francesco.Recipes.World/Views/Home/Index.cshtml b/Francesco.Recipes.World/Views/Home/Index.cshtml index 7c24500..55d12a5 100644 --- a/Francesco.Recipes.World/Views/Home/Index.cshtml +++ b/Francesco.Recipes.World/Views/Home/Index.cshtml @@ -32,39 +32,41 @@
        - @foreach (var recipe in category.Recipes) - { - var mediaFile = recipe.MediaFiles?.FirstOrDefault(); - var imageData = mediaFile?.Data; - var mimeType = mediaFile?.MimeType; -
        -
        -
        - @if (imageData != null && mimeType != null) - { - @recipe.Name - } - else - { - @recipe.Name - } +@foreach (var recipe in category.Recipes) +{ + var imageData = recipe.ImageData; + var mimeType = recipe.MimeType; + +
        +
        +
        + @if (imageData != null && mimeType != null) + { + @recipe.Name + } + else + { + @recipe.Name + } +
        + +
        +
        @recipe.Name
        +

        @recipe.CookingTime

        +
        + @await Html.PartialAsync("_FavoriteButton", recipe) +
        -
        -
        @recipe.Name
        -

        @recipe.CookingTime

        -
        - @await Html.PartialAsync("_FavoriteButton", recipe) -
        + Details +
        +
        +
        +} - Details -
        -
        -
        - }
        diff --git a/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml b/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml index c03a7fb..e8e7a13 100644 --- a/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml +++ b/Francesco.Recipes.World/Views/Home/_SearchResultsPartial.cshtml @@ -64,7 +64,7 @@
        - Details + Details
        diff --git a/Francesco.Recipes.World/Views/Recipe/CategoryRecipes.cshtml b/Francesco.Recipes.World/Views/Recipe/CategoryRecipes.cshtml deleted file mode 100644 index df06c6e..0000000 --- a/Francesco.Recipes.World/Views/Recipe/CategoryRecipes.cshtml +++ /dev/null @@ -1,98 +0,0 @@ -@model IEnumerable - -@{ - ViewData["Title"] = "Category Recipes"; -} - -

        Category Recipes

        - -@foreach (var categoryRecipes in Model) -{ -
        -

        @categoryRecipes.Category.Name

        - Rezept erstellen -
        - @foreach (var recipe in categoryRecipes.Recipes) - { -
        -
        - @if (recipe.MediaFiles.Any() && recipe.MediaFiles.First().Data != null) - { - var mediaFile = recipe.MediaFiles.First(); - if (mediaFile.Data != null) - { - @recipe.Name - } - } -
        -
        -

        @recipe.Name

        -

        @recipe.Description

        -

        Difficulty: @recipe.Difficulty

        -

        Servings: @recipe.Servings

        -

        Preparation Time: @recipe.PreparationTime

        -

        Cooking Time: @recipe.CookingTime

        -
        - @if (recipe.IsFavorite) - { -
        - - - - } - else - { -
        - - - - } -
        -
        -
        - } - -
        -
        -} - - - diff --git a/Francesco.Recipes.World/Views/Recipe/Create.cshtml b/Francesco.Recipes.World/Views/Recipe/Create.cshtml index b10907b..55ff934 100644 --- a/Francesco.Recipes.World/Views/Recipe/Create.cshtml +++ b/Francesco.Recipes.World/Views/Recipe/Create.cshtml @@ -107,7 +107,7 @@
        - Abbrechen + Abbrechen
        diff --git a/Francesco.Recipes.World/Views/Recipe/Details.cshtml b/Francesco.Recipes.World/Views/Recipe/Details.cshtml index 9f606cb..65a7001 100644 --- a/Francesco.Recipes.World/Views/Recipe/Details.cshtml +++ b/Francesco.Recipes.World/Views/Recipe/Details.cshtml @@ -32,7 +32,7 @@ Instructions = Model.Instructions.ToList() }) -
        @Html.AntiForgeryToken() @@ -173,34 +173,37 @@ return Math.round(quantity * 10) / 10; } + window.addSelectedIngredientsToShoppingList = function(recipeIdParam) { + const idToUse = recipeIdParam || recipeId; - window.addSelectedIngredientsToShoppingList = function(recipeIdParam) { - const idToUse = recipeIdParam || recipeId; + var form = document.getElementById('ingredient-form'); + var formData = new FormData(form); + var selectedIngredientIds = formData.getAll('ingredientIds'); - var form = document.getElementById('ingredient-form'); - var formData = new FormData(form); - var selectedIngredientIds = formData.getAll('ingredientIds'); + fetch('/ShoppingList/CreateOrAddIngredients', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value + }, + body: JSON.stringify({ recipeId: idToUse, ingredientIds: selectedIngredientIds }) + }) + .then(response => { + if (response.ok) { + return response.json(); + } + throw new Error('Failed to update shopping list'); + }) + .then(result => { + localStorage.setItem('shoppingListId', result.shoppingListId); + alert('Einkaufsliste aktualisiert.'); + }) + .catch(error => { + alert('Fehler beim Aktualisieren der Einkaufsliste.'); + }); + }; + }); - var token = document.querySelector('input[name="__RequestVerificationToken"]').value; - - var response = await fetch('/ShoppingList/CreateOrAddIngredients', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'RequestVerificationToken': token - }, - body: JSON.stringify({ recipeId: '@Model.Id', ingredientIds: selectedIngredientIds }) - }); - - if (response.ok) { - var result = await response.json(); - localStorage.setItem('shoppingListId', result.shoppingListId); - alert('Zutaten wurden zur Einkaufsliste hinzugefügt.'); - } else { - alert('Fehler beim Hinzufügen zur Einkaufsliste.'); - } - } - } diff --git a/Francesco.Recipes.World/Views/Shared/_FavoriteButton.cshtml b/Francesco.Recipes.World/Views/Shared/_FavoriteButton.cshtml index 24104af..e67f795 100644 --- a/Francesco.Recipes.World/Views/Shared/_FavoriteButton.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_FavoriteButton.cshtml @@ -1,4 +1,4 @@ -@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe +@model Francesco.Recipes.World.Models.IFavoritable diff --git a/Francesco.Recipes.World/Views/Shared/_IngredientsPartial.cshtml b/Francesco.Recipes.World/Views/Shared/_IngredientsPartial.cshtml index b465acd..5ef25b0 100644 --- a/Francesco.Recipes.World/Views/Shared/_IngredientsPartial.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_IngredientsPartial.cshtml @@ -21,13 +21,9 @@ } -
        }
        - - - diff --git a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml index b0646a3..c95c6c8 100644 --- a/Francesco.Recipes.World/Views/Shared/_Layout.cshtml +++ b/Francesco.Recipes.World/Views/Shared/_Layout.cshtml @@ -23,17 +23,12 @@ + - -
        diff --git a/Francesco.Recipes.World/wwwroot/js/site.js b/Francesco.Recipes.World/wwwroot/js/site.js index 3639c5e..f9ccb78 100644 --- a/Francesco.Recipes.World/wwwroot/js/site.js +++ b/Francesco.Recipes.World/wwwroot/js/site.js @@ -413,3 +413,4 @@ }; })(window, document); +console.log("Francesco-Objekt initialisiert:", window.Francesco); \ No newline at end of file diff --git a/FrancescosRecipeWorld Mock/FrancescosRecipeWorld Mock.csproj b/FrancescosRecipeWorld Mock/FrancescosRecipeWorld Mock.csproj new file mode 100644 index 0000000..f8f46d2 --- /dev/null +++ b/FrancescosRecipeWorld Mock/FrancescosRecipeWorld Mock.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + FrancescosRecipeWorld_Mock + latest + enable + enable + + + + + + + + + + + + + + + + + + diff --git a/FrancescosRecipeWorld Mock/MSTestSettings.cs b/FrancescosRecipeWorld Mock/MSTestSettings.cs new file mode 100644 index 0000000..aaf278c --- /dev/null +++ b/FrancescosRecipeWorld Mock/MSTestSettings.cs @@ -0,0 +1 @@ +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/FrancescosRecipeWorld Mock/Test1.cs b/FrancescosRecipeWorld Mock/Test1.cs new file mode 100644 index 0000000..e185433 --- /dev/null +++ b/FrancescosRecipeWorld Mock/Test1.cs @@ -0,0 +1,156 @@ +using Francesco.Recipes.World.Controller.Recipe; +using Francesco.Recipes.World.Data; +using Francesco.Recipes.World.Models; +using Francesco.Recipes.World.Models.BackendModels.Instruction; +using Francesco.Recipes.World.Models.BackendModels.MediaFile; +using Francesco.Recipes.World.Models.BackendModels.Recipe; +using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient; +using Francesco.Recipes.World.Repositories.Category; +using Francesco.Recipes.World.Repositories.Favorit; +using Francesco.Recipes.World.Repositories.Ingredient; +using Francesco.Recipes.World.Repositories.Instruction; +using Francesco.Recipes.World.Repositories.MediaFile; +using Francesco.Recipes.World.Repositories.Recipe; +using Francesco.Recipes.World.Repositories.Unit; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Moq; + +namespace FrancescosRecipeWorld_Mock +{ + [TestClass] + public sealed class FrancescoDamicoUnittest2 + { + private Mock _mockFavoriteRepository; + private Mock _mockRecipeRepository; + private RecipeController _recipeController; + + [TestInitialize] + public void Setup() + { + _mockFavoriteRepository = new Mock(); + _mockRecipeRepository = new Mock(); + + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + + _recipeController = new RecipeController( + _mockRecipeRepository.Object, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + _mockFavoriteRepository.Object, + new FrancescosRecipesWorldDbContext(options) + ); + } + + /// + /// Test 1: AddFavorite(Guid recipeId) -> PartialViewResult mit FavoriteButtonViewModel + /// Parameter: recipeId (Guid) + /// Rückgabewert: PartialViewResult + /// + [TestMethod] + public async Task FrancescoDamico_UnitTest1() + { + // Arrange + var recipeId = Guid.NewGuid(); + _mockFavoriteRepository + .Setup(x => x.AddFavoriteAsync(recipeId)) + .Returns(Task.CompletedTask); + + // Act + var result = await _recipeController.AddFavorite(recipeId); + + // Assert + Assert.IsNotNull(result); + Assert.IsInstanceOfType(result, typeof(PartialViewResult)); + + var partialViewResult = result as PartialViewResult; + Assert.AreEqual("_FavoriteButton", partialViewResult.ViewName); + Assert.IsInstanceOfType(partialViewResult.Model, typeof(FavoritButtonViewModel)); + + var model = partialViewResult.Model as FavoritButtonViewModel; + Assert.AreEqual(recipeId, model.Id); + Assert.IsTrue(model.IsFavorite); + } + + /// + /// Test 2: RemoveFavorite(Guid recipeId) -> PartialViewResult mit FavoriteButtonViewModel + /// Parameter: recipeId (Guid) + /// Rückgabewert: PartialViewResult + /// + [TestMethod] + public async Task FrancescoDamico_UnitTest2() + { + // Arrange + var recipeId = Guid.NewGuid(); + _mockFavoriteRepository + .Setup(x => x.RemoveFavoriteAsync(recipeId)) + .Returns(Task.CompletedTask); + + // Act + var result = await _recipeController.RemoveFavorite(recipeId); + + // Assert + Assert.IsNotNull(result); + Assert.IsInstanceOfType(result, typeof(PartialViewResult)); + + var partialViewResult = result as PartialViewResult; + Assert.AreEqual("_FavoriteButton", partialViewResult.ViewName); + Assert.IsInstanceOfType(partialViewResult.Model, typeof(FavoritButtonViewModel)); + + var model = partialViewResult.Model as FavoritButtonViewModel; + Assert.AreEqual(recipeId, model.Id); + Assert.IsFalse(model.IsFavorite); + } + + /// + /// Test 3: Details(Guid recipeId) -> ViewResult mit Recipe Modell + /// Parameter: recipeId (Guid) + /// Rückgabewert: ViewResult mit Recipe-Objekt + /// + [TestMethod] + public async Task FrancescoDamico_UnitTest3() + { + // Arrange + var recipeId = Guid.NewGuid(); + var expectedRecipe = new Recipe + { + Id = recipeId, + Name = "Spaghetti Carbonara", + Description = "Klassisches italienisches Pasta-Gericht", + Difficulty = Difficulty.Easy, + Servings = 4, + PreparationTime = new TimeSpan(0, 10, 0), + CookingTime = new TimeSpan(0, 20, 0), + IsFavorite = false, + CreatedAt = DateTime.UtcNow, + RecipeIngredients = new List(), + Instructions = new List(), + MediaFiles = new List() + }; + + _mockRecipeRepository + .Setup(x => x.GetRecipeByIdAsync(recipeId)) + .ReturnsAsync(expectedRecipe); + + // Act + var result = await _recipeController.Details(recipeId); + + // Assert + Assert.IsNotNull(result); + Assert.IsInstanceOfType(result, typeof(ViewResult)); + + var viewResult = result as ViewResult; + Assert.IsInstanceOfType(viewResult.Model, typeof(Recipe)); + + var model = viewResult.Model as Recipe; + Assert.AreEqual(recipeId, model.Id); + Assert.AreEqual("Spaghetti Carbonara", model.Name); + Assert.AreEqual(Difficulty.Easy, model.Difficulty); + } + } +}
        RezeptnameZutaten
        @recipeShoppingList.Recipe.Name -
          - @foreach (var ingredient in recipeShoppingList.SelectedIngredients) - { -
        • @ingredient.RecipeIngredient.Ingredient.Name - @ingredient.RecipeIngredient.Quantity @ingredient.RecipeIngredient.Unit.Name
        • - } -
        -