From d248ccb1dbe35ba99d2fb22a8b2ebcc503973484 Mon Sep 17 00:00:00 2001 From: franc Date: Tue, 8 Apr 2025 15:52:32 +0200 Subject: [PATCH] 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
  • + } +
+