Merge branch 'feature/Implement_All_Repositories' into 'develop'

Repository Stuff

See merge request francesco.damico/francescos.recipes.world!5
This commit is contained in:
Francesco D'Amico
2025-04-14 11:15:17 +00:00
54 changed files with 3518 additions and 75 deletions
@@ -0,0 +1,39 @@
namespace Francesco.Recipes.World.Controller.Category
{
using Francesco.Recipes.World.Repositories.Category;
using Microsoft.AspNetCore.Mvc;
public class CategoryController : Controller
{
private readonly ICategoryRepository _categoryRepository;
public CategoryController(ICategoryRepository categoryRepository)
{
_categoryRepository = categoryRepository;
}
// GET: /Category
[HttpGet]
public async Task<IActionResult> Index()
{
var categories = await _categoryRepository.GetAllCategoriesAsync();
return View(categories);
}
// GET: /Category/{id}
[HttpGet("{id:guid}")]
public async Task<IActionResult> Details(Guid id)
{
var category = await _categoryRepository.GetCategoryByIdAsync(id);
return View(category);
}
// GET: /Category/{id}/recipes
[HttpGet("{id:guid}/recipes")]
public async Task<IActionResult> GetRecipesByCategory(Guid id)
{
var recipes = await _categoryRepository.GetRecipesByCategoryAsync(id);
return Ok(recipes);
}
}
}
@@ -0,0 +1,6 @@
namespace Francesco.Recipes.World.Controller.Ingredient
{
public class IngredientController
{
}
}
@@ -0,0 +1,6 @@
namespace Francesco.Recipes.World.Controller.Instruction
{
public class InstructionController
{
}
}
@@ -0,0 +1,74 @@
namespace Francesco.Recipes.World.Controller.MediaFile
{
using Francesco.Recipes.World.Data;
using Francesco.Recipes.World.Repositories.MediaFile;
using Microsoft.AspNetCore.Mvc;
[Route("Category/{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")]
[AutoValidateAntiforgeryToken]
public async Task<IActionResult> 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();
}
[HttpPost("ReplaceInstructionImage")]
[AutoValidateAntiforgeryToken]
public async Task<IActionResult> 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}");
}
}
}
}
}
@@ -0,0 +1,263 @@
namespace Francesco.Recipes.World.Controller.Recipe
{
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;
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 IFavoriteRepository _favoriteRepository;
public IReadOnlyCollection<Recipe> Recipes { get; set; }
public RecipeController(
IRecipeRepository recipeRepository,
IUnitRepository unitRepository,
ICategoryRepository categoryRepository,
IIngredientRepository ingredientRepository,
IMediaFileRepository mediaFileRepository,
IInstructionRepository instructionRepository,
IFavoriteRepository favoriteRepository)
{
_recipeRepository = recipeRepository;
_unitRepository = unitRepository;
_categoryRepository = categoryRepository;
_ingredientRepository = ingredientRepository;
Recipes = new List<Recipe>();
_mediaFileRepository = mediaFileRepository;
_instructionRepository = instructionRepository;
_favoriteRepository = favoriteRepository;
}
// GET: /Recipe/{recipeId}/AddOrCreateIngredient
[HttpGet("{recipeId}/AddOrCreateIngredient")]
public async Task<IActionResult> AddOrCreateIngredient(Guid recipeId)
{
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<IActionResult> 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<IActionResult> CategoryRecipes()
{
var categories = await _categoryRepository.GetAllCategoriesWithRecipesAsync();
var viewModel = categories.Select(c => new CategoryRecipesViewModel
{
Category = c,
Recipes = c.Recipes,
}).ToList();
return View(viewModel);
}
// POST: /Recipe/{recipeId}/AddOrCreateIngredient
[HttpPost("{recipeId}/AddOrCreateIngredient")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> 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: /Recipe/Create/{categoryId}
[HttpGet("Create/{categoryId}")]
public async Task<IActionResult> 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: /Recipe/Create/{categoryId}
[HttpPost("Create/{categoryId}")]
[AutoValidateAntiforgeryToken]
public async Task<IActionResult> Create(Guid categoryId, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime, IFormFile? photo)
{
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);
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: /Recipe/{recipeId}/RemoveIngredient/{ingredientId}
[HttpGet("{recipeId}/RemoveIngredient/{ingredientId}")]
public async Task<IActionResult> RemoveIngredient(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;
return View();
}
// POST: /Recipe/{recipeId}/RemoveIngredient/{ingredientId}
[HttpPost("{recipeId}/RemoveIngredient/{ingredientId}")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveIngredientConfirmed(Guid recipeId, Guid ingredientId)
{
await _recipeRepository.RemoveIngredientFromRecipeAsync(recipeId, ingredientId);
TempData["SuccessMessage"] = "Ingredient removed successfully.";
return RedirectToAction("Details", new { id = recipeId });
}
// GET: /Recipe/FilterByDifficulty
[HttpGet("FilterByDifficulty")]
public async Task<IActionResult> FilterByDifficulty(Difficulty? selectedDifficulty)
{
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<IActionResult> 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")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> 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<IActionResult> Favorites()
{
var favoriteRecipes = await _favoriteRepository.GetFavoriteRecipesAsync();
return View(favoriteRecipes);
}
// POST: /Recipe/AddFavorite
[HttpPost("AddFavorite")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddFavorite(Guid recipeId)
{
await _favoriteRepository.AddFavoriteAsync(recipeId);
return RedirectToAction("Details", new { recipeId });
}
// POST: /Recipe/RemoveFavorite
[HttpPost("RemoveFavorite")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveFavorite(Guid recipeId)
{
await _favoriteRepository.RemoveFavoriteAsync(recipeId);
return RedirectToAction("Details", new { recipeId });
}
}
}
@@ -0,0 +1,43 @@
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;
public class ShoppingListController : Controller
{
private readonly IShoppingListRepository _shoppingListRepository;
private readonly FrancescosRecipesWorldDbContext _context;
public ShoppingListController(IShoppingListRepository shoppingListRepository, FrancescosRecipesWorldDbContext context)
{
_shoppingListRepository = shoppingListRepository;
_context = context;
}
[HttpPost("CreateOrAddIngredients")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CreateOrAddIngredients([FromBody] CreateOrAddIngredientRequestModel request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (request == null || request.IngredientIds == null || !request.IngredientIds.Any())
{
return BadRequest("No ingredients provided.");
}
var shoppingList = await _shoppingListRepository.AddIngredientsToShoppingListAsync(request.RecipeId, request.IngredientIds);
if (shoppingList == null)
{
return BadRequest("Error creating shopping list.");
}
return Json(new { shoppingListId = shoppingList.Id });
}
}
}
@@ -0,0 +1,6 @@
namespace Francesco.Recipes.World.Controller.Unit
{
public class UnitController
{
}
}
@@ -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;
@@ -34,7 +35,9 @@
public DbSet<Favorit> Favorits => Set<Favorit>();
public DbSet<IngredientsShoppingList> IngredientsShoppingLists => Set<IngredientsShoppingList>();
public DbSet<RecipeIngredientShoppingList> RecipeIngredientsShoppingLists => Set<RecipeIngredientShoppingList>();
public DbSet<RecipeShoppingList> RecipeShoppingLists => Set<RecipeShoppingList>();
public DbSet<ShoppingList> ShoppingLists => Set<ShoppingList>();
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
@@ -29,6 +29,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.7" />
<PackageReference Include="SecurityCodeScan.VS2019" Version="5.6.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -41,12 +42,16 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Controller\" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include=".\stylecop.json" />
</ItemGroup>
<ItemGroup>
<Folder Include="Services\Category\" />
<Folder Include="Services\MediaFile\" />
<Folder Include="Services\Ingredient\" />
<Folder Include="Services\Instruction\" />
<Folder Include="Services\ShoppingList\" />
<Folder Include="Services\Recipe\" />
</ItemGroup>
</Project>
@@ -0,0 +1,521 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.ToTable("Favorits");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Ingredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("IngredientId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("RecipeIngredientId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("Number")
.HasColumnType("int");
b.Property<Guid>("RecipeId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("RecipeId");
b.ToTable("Instructions");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<byte[]>("Data")
.HasColumnType("varbinary(max)");
b.Property<string>("FileName")
.HasColumnType("nvarchar(max)");
b.Property<Guid>("InstructionId")
.HasColumnType("uniqueidentifier");
b.Property<string>("MimeType")
.HasColumnType("nvarchar(max)");
b.Property<Guid?>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("CategoryId")
.HasColumnType("uniqueidentifier");
b.Property<TimeSpan>("CookingTime")
.HasColumnType("time");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Description")
.HasColumnType("nvarchar(max)");
b.Property<int>("Difficulty")
.HasColumnType("int");
b.Property<Guid>("FavoritId")
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsFavorite")
.HasColumnType("bit");
b.Property<DateTime?>("ModifiedAt")
.HasColumnType("datetime2");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<TimeSpan>("PreparationTime")
.HasColumnType("time");
b.Property<int>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("IngredientId")
.HasColumnType("uniqueidentifier");
b.Property<int>("Quantity")
.HasColumnType("int");
b.Property<Guid>("RecipeId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<DateTime?>("ModifiedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.ToTable("ShoppingLists");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("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
}
}
}
@@ -0,0 +1,131 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Francesco.Recipes.World.Migrations
{
/// <inheritdoc />
public partial class CreateNewTableRecipeIngredientShoppinglist : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder is null)
{
throw new ArgumentNullException(nameof(migrationBuilder));
}
migrationBuilder.DropTable(
name: "IngredientsShoppingLists");
migrationBuilder.AlterColumn<int>(
name: "Number",
table: "Instructions",
type: "int",
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");
migrationBuilder.CreateTable(
name: "RecipeIngredientsShoppingLists",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ShoppingListId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
RecipeIngredientId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
IngredientId = table.Column<Guid>(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");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
if (migrationBuilder is null)
{
throw new ArgumentNullException(nameof(migrationBuilder));
}
migrationBuilder.DropTable(
name: "RecipeIngredientsShoppingLists");
migrationBuilder.AlterColumn<string>(
name: "Number",
table: "Instructions",
type: "nvarchar(max)",
nullable: false,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.CreateTable(
name: "IngredientsShoppingLists",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
IngredientId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ShoppinglistId = table.Column<Guid>(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");
}
}
}
@@ -0,0 +1,572 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.ToTable("Favorits");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Ingredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("IngredientId")
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsChecked")
.HasColumnType("bit");
b.Property<Guid>("RecipeIngredientId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("Number")
.HasColumnType("int");
b.Property<Guid>("RecipeId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("RecipeId");
b.ToTable("Instructions");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<byte[]>("Data")
.HasColumnType("varbinary(max)");
b.Property<string>("FileName")
.HasColumnType("nvarchar(max)");
b.Property<Guid>("InstructionId")
.HasColumnType("uniqueidentifier");
b.Property<string>("MimeType")
.HasColumnType("nvarchar(max)");
b.Property<Guid?>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("CategoryId")
.HasColumnType("uniqueidentifier");
b.Property<TimeSpan>("CookingTime")
.HasColumnType("time");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Description")
.HasColumnType("nvarchar(max)");
b.Property<int>("Difficulty")
.HasColumnType("int");
b.Property<Guid>("FavoritId")
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsFavorite")
.HasColumnType("bit");
b.Property<DateTime?>("ModifiedAt")
.HasColumnType("datetime2");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<TimeSpan>("PreparationTime")
.HasColumnType("time");
b.Property<int>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("IngredientId")
.HasColumnType("uniqueidentifier");
b.Property<int>("Quantity")
.HasColumnType("int");
b.Property<Guid>("RecipeId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("RecipeId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<DateTime?>("ModifiedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.ToTable("ShoppingLists");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("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
}
}
}
@@ -0,0 +1,162 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Francesco.Recipes.World.Migrations
{
/// <inheritdoc />
public partial class UpdateShoppingLIstLogic : Migration
{
/// <inheritdoc />
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<Guid>(
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<bool>(
name: "IsChecked",
table: "RecipeIngredientsShoppingLists",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.CreateTable(
name: "RecipeShoppingLists",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ShoppingListId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
RecipeId = table.Column<Guid>(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);
}
/// <inheritdoc />
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<Guid>(
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");
}
}
}
@@ -1,9 +1,10 @@
// <auto-generated />
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
@@ -117,25 +118,33 @@ 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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("IngredientId")
b.Property<Guid?>("IngredientId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("ShoppinglistId")
b.Property<bool>("IsChecked")
.HasColumnType("bit");
b.Property<Guid>("RecipeIngredientId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("RecipeShoppingListId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("IngredientId");
b.HasIndex("ShoppinglistId");
b.HasIndex("RecipeIngredientId");
b.ToTable("IngredientsShoppingLists");
b.HasIndex("RecipeShoppingListId");
b.ToTable("RecipeIngredientsShoppingLists");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
@@ -148,9 +157,8 @@ namespace Francesco.Recipes.World.Migrations
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Number")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("Number")
.HasColumnType("int");
b.Property<Guid>("RecipeId")
.HasColumnType("uniqueidentifier");
@@ -198,7 +206,7 @@ namespace Francesco.Recipes.World.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("CategoryId")
b.Property<Guid>("CategoryId")
.HasColumnType("uniqueidentifier");
b.Property<TimeSpan>("CookingTime")
@@ -270,6 +278,27 @@ namespace Francesco.Recipes.World.Migrations
b.ToTable("RecipeIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("RecipeId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("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<Guid>("Id")
@@ -374,23 +403,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.RecipeShoppingList.RecipeShoppingList", "RecipeShoppingList")
.WithMany("SelectedIngredients")
.HasForeignKey("RecipeShoppingListId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ingredient");
b.Navigation("RecipeIngredient");
b.Navigation("Shoppinglist");
b.Navigation("RecipeShoppingList");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
@@ -423,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")
@@ -433,6 +468,8 @@ namespace Francesco.Recipes.World.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Category");
b.Navigation("Favorit");
});
@@ -463,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");
@@ -494,9 +550,14 @@ 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("IngredientsShoppingLists");
b.Navigation("RecipeIngredientShoppingLists");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
@@ -11,6 +11,6 @@
public virtual ICollection<RecipeIngredient> RecipeIngredients { get; set; } = new List<RecipeIngredient>();
public virtual ICollection<IngredientsShoppingList> IngredientShoppingLists { get; set; } = new List<IngredientsShoppingList>();
public virtual ICollection<RecipeIngredientShoppingList> IngredientShoppingLists { get; set; } = new List<RecipeIngredientShoppingList>();
}
}
@@ -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 ();
}
}
@@ -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
{
VeryEasy = 1,
[Display(Name = "Sehr einfach")]
VeryEasy = 0,
Easy = 2,
[Display(Name = "Einfach")]
Easy = 1,
Medium = 3,
[Display(Name = "Mittel")]
Medium = 2,
Hard = 4,
[Display(Name = "Schwer")]
Hard = 3,
Expert = 5,
[Display(Name = "Experte")]
Expert = 4,
}
}
@@ -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<MediaFile> MediaFiles { get; set; } = new List<MediaFile>();
public virtual Favorit Favorit { get; set; } = new ();
public virtual Category Category { get; set; } = new ();
}
@@ -0,0 +1,16 @@
namespace Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList
{
using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient;
using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList;
public class RecipeIngredientShoppingList
{
public Guid Id { get; set; }
public virtual RecipeShoppingList RecipeShoppingList { get; set; } = new ();
public virtual RecipeIngredient RecipeIngredient { get; set; } = new ();
public bool IsChecked { get; set; } = false;
}
}
@@ -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 Recipe();
public virtual ICollection<RecipeIngredientShoppingList> SelectedIngredients { get; set; } = new List<RecipeIngredientShoppingList>();
}
}
@@ -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<IngredientsShoppingList> IngredientsShoppingLists { get; set; } = new List<IngredientsShoppingList>();
public virtual ICollection<RecipeShoppingList> RecipeShoppingList { get; set; } = new List<RecipeShoppingList>();
}
}
@@ -0,0 +1,9 @@
namespace Francesco.Recipes.World.Models
{
public class CreateOrAddIngredientRequestModel
{
public Guid RecipeId { get; set; }
public List<Guid> IngredientIds { get; set; } = new ();
}
}
+19 -15
View File
@@ -1,11 +1,12 @@
using Francesco.Recipes.World.Data;
using Francesco.Recipes.World.Repositories;
using FrancescoRecipesWorld.Repositories;
using Microsoft.AspNetCore.Identity;
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.ShoppingList;
using Francesco.Recipes.World.Repositories.Unit;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
@@ -20,9 +21,6 @@ var connectionString = builder.Configuration.GetConnectionString("FrancescosReci
services.AddDbContext<FrancescosRecipesWorldDbContext>(options =>
options.UseSqlServer(connectionString));
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<FrancescosRecipesWorldDbContext>();
// Add services to the container.
builder.Services.AddControllersWithViews();
@@ -34,6 +32,14 @@ builder.Services.AddScoped<IUnitRepository, UnitRepository>();
builder.Services.AddScoped<ICategoryRepository, CategoryRepository>();
builder.Services.AddScoped<IShoppingListRepository, ShoppingListRepository>();
builder.Services.AddScoped<IMediaFileRepository, MediaFileRepository>();
builder.Services.AddScoped<IInstructionRepository, InstructionRepository>();
builder.Services.AddScoped<IFavoriteRepository, FavoritRepository>();
var app = builder.Build();
// Configure the HTTP request pipeline.
@@ -45,20 +51,18 @@ if (!app.Environment.IsDevelopment())
app.UseHsts();
}
Console.WriteLine("Standard Numeric Format Specifiers");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
pattern: "{controller=Category}/{action=Index}/{id?}");
app.Run();
@@ -0,0 +1,52 @@
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<Category> GetCategoryByIdAsync(Guid categoryId)
{
var category = await _context.Categories.FindAsync(categoryId);
return category ?? throw new InvalidDataException($"Category {categoryId} not found.");
}
public async Task<IEnumerable<Category>> GetAllCategoriesAsync()
{
return await _context.Categories.ToListAsync();
}
public async Task<IEnumerable<Recipe>> 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;
}
public async Task<IEnumerable<Category>> GetAllCategoriesWithRecipesAsync()
{
return await _context.Categories
.Include(c => c.Recipes)
.ToListAsync();
}
}
}
@@ -0,0 +1,19 @@
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<Category> GetCategoryByIdAsync(Guid categoryId);
Task<IEnumerable<Category>> GetAllCategoriesAsync();
Task<IEnumerable<Recipe>> GetRecipesByCategoryAsync(Guid categoryId);
Task<IEnumerable<Category>> GetAllCategoriesWithRecipesAsync();
}
}
@@ -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 : IFavoriteRepository
{
private readonly FrancescosRecipesWorldDbContext _context;
public FavoritRepository(FrancescosRecipesWorldDbContext context)
{
_context = context;
}
public async Task<IEnumerable<Recipe>> GetFavoriteRecipesAsync()
{
return await _context.Recipes
.Where(r => r.IsFavorite)
.ToListAsync();
}
public async Task<bool> 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();
}
}
}
}
@@ -0,0 +1,15 @@
namespace Francesco.Recipes.World.Repositories.Favorit
{
using Francesco.Recipes.World.Models.BackendModels.Recipe;
public interface IFavoriteRepository
{
Task<IEnumerable<Recipe>> GetFavoriteRecipesAsync();
Task<bool> IsFavoriteAsync(Guid recipeId);
Task AddFavoriteAsync(Guid recipeId);
Task RemoveFavoriteAsync(Guid recipeId);
}
}
@@ -0,0 +1,16 @@
namespace Francesco.Recipes.World.Repositories.Ingredient
{
using Francesco.Recipes.World.Models.BackendModels.Ingredient;
using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient;
public interface IIngredientRepository
{
Task UpdateIngredientAsync(Ingredient ingredient);
Task<List<RecipeIngredient>> GetIngredientsByRecipeIdAsync(Guid recipeId);
Task<List<Ingredient>> GetIngredientsByNameAsync(string name);
Task<Ingredient> GetIngredientByIdAsync(Guid ingredientId);
}
}
@@ -0,0 +1,88 @@
namespace Francesco.Recipes.World.Repositories.Ingredient
{
using Francesco.Recipes.World.Data;
using Francesco.Recipes.World.Models.BackendModels.Ingredient;
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 UpdateRecipeIngredientAsync(RecipeIngredient recipeIngredient)
{
if (recipeIngredient == null)
{
throw new ArgumentNullException(nameof(recipeIngredient));
}
var existingRecipeIngredient = await _context.RecipeIngredients
.Include(ri => ri.Ingredient)
.Include(ri => ri.Unit)
.FirstOrDefaultAsync(ri => ri.Id == recipeIngredient.Id);
if (existingRecipeIngredient == null)
{
throw new InvalidOperationException($"RecipeIngredient with ID {recipeIngredient.Id} not found.");
}
existingRecipeIngredient.Quantity = recipeIngredient.Quantity;
existingRecipeIngredient.Ingredient = recipeIngredient.Ingredient;
existingRecipeIngredient.Unit = recipeIngredient.Unit;
await _context.SaveChangesAsync();
}
public async Task<List<Ingredient>> 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<List<RecipeIngredient>> 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;
await _context.SaveChangesAsync();
}
public async Task<Ingredient> GetIngredientByIdAsync(Guid ingredientId)
{
var ingredient = await _context.Ingredients.FindAsync(ingredientId);
return ingredient ?? throw new InvalidDataException($"Address {ingredientId} not found.");
}
}
}
@@ -0,0 +1,15 @@
namespace Francesco.Recipes.World.Repositories.Instruction
{
using Francesco.Recipes.World.Models.BackendModels.Instruction;
public interface IInstructionRepository
{
Task<Instruction> GetInstructionAsync(Guid instructionId);
Task<Instruction> CreateInstructionToRecipeAsync(Guid recipeId, string description, int number);
Task<List<Instruction>> GetInstructionsByRecipeIdAsync(Guid recipeId);
Task RemoveInstructionFromRecipeAsync(Guid recipeId, Guid instructionId);
}
}
@@ -0,0 +1,81 @@
namespace Francesco.Recipes.World.Repositories.Instruction
{
using Francesco.Recipes.World.Data;
using Francesco.Recipes.World.Models.BackendModels.Instruction;
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, IRecipeRepository recipeRepository)
{
_recipeRepository = recipeRepository;
_context = context;
}
public async Task<Instruction> GetInstructionAsync(Guid instructionId)
{
var instruction = await _context.Instructions.FindAsync(instructionId);
return instruction ?? throw new InvalidDataException($"Instruction {instructionId} not found.");
}
public async Task<Instruction> 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(Guid recipeId, Guid instructionId)
{
var recipe = await _recipeRepository.GetRecipeAsync(recipeId);
if (recipe.Instructions == null || !recipe.Instructions.Any())
{
await _context.Entry(recipe)
.Collection(r => r.Instructions)
.LoadAsync();
}
var instructionToRemove = recipe.Instructions?.FirstOrDefault(i => i.Id == instructionId);
if (instructionToRemove != null)
{
recipe.Instructions?.Remove(instructionToRemove);
await _context.SaveChangesAsync();
}
}
public async Task<List<Instruction>> GetInstructionsByRecipeIdAsync(Guid recipeId)
{
return await _context.Instructions
.Include(i => i.Recipe)
.Where(i => i.Recipe.Id == recipeId)
.ToListAsync();
}
}
}
@@ -0,0 +1,11 @@
namespace Francesco.Recipes.World.Repositories.MediaFile
{
public interface IMediaFileRepository
{
Task ReplaceInstructionImageAsync(Guid instructionId, Guid mediaFileIdToReplace, string fileName, string mimeType, byte[] newMediaData);
Task UploadInstructionImageAsync(Guid instructionId, IFormFile? photo);
Task UploadRecipeMediaAsync(Guid recipeId, IFormFile mediaFile);
}
}
@@ -0,0 +1,126 @@
namespace Francesco.Recipes.World.Repositories.MediaFile
{
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;
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, Guid mediaFileIdToReplace, string fileName, string mimeType, byte[] newMediaData)
{
var instruction = await _instructionRepository.GetInstructionAsync(instructionId);
var mediaToReplace = instruction.MediaFiles.FirstOrDefault(m => m.Id == mediaFileIdToReplace);
if (mediaToReplace == null)
{
throw new InvalidOperationException("The specified media file does not exist.");
}
_context.MediaFiles.Remove(mediaToReplace);
await _context.SaveChangesAsync();
var newMedia = new MediaFile
{
Id = Guid.NewGuid(),
FileName = fileName,
MimeType = mimeType,
Data = newMediaData,
Instruction = instruction,
};
_context.MediaFiles.Add(newMedia);
await _context.SaveChangesAsync();
}
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 UploadRecipeMediaAsync(Guid recipeId, IFormFile mediaFile)
{
if (mediaFile == null)
{
throw new ArgumentNullException(nameof(mediaFile));
}
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,
};
_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();
}
}
}
}
@@ -0,0 +1,22 @@
namespace Francesco.Recipes.World.Repositories.Recipe
{
using Francesco.Recipes.World.Models.BackendModels.Category;
using Francesco.Recipes.World.Models.BackendModels.Recipe;
public interface IRecipeRepository
{
Task<Recipe> GetRecipeAsync(Guid recipeId);
Task<Recipe?> GetRecipeByIdAsync(Guid id);
Task AddOrCreateIngredientToRecipeAsync(Guid recipeId, string ingredientName, int quantity, Guid unitId);
Task RemoveIngredientFromRecipeAsync(Guid recipeId, Guid ingredientId);
Task<IEnumerable<Recipe>> GetRecipesByNameAndIngredientAsync(string name, string ingredient);
Task<IReadOnlyCollection<Recipe>> GetRecipesByDifficultyAsync(Difficulty? difficulty);
Task<Recipe> CreateRecipeForCategoryAsync(Category category, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime);
}
}
@@ -0,0 +1,195 @@
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.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<Recipe> GetRecipeAsync(Guid recipeId)
{
var recipe = await _context.Recipes.FindAsync(recipeId);
return recipe ?? throw new InvalidDataException($"Address {recipeId} not found.");
}
public async Task<Recipe?> 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)
.Include(r => r.Instructions)
.FirstOrDefaultAsync(r => r.Id == recipeId);
}
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);
var exactMatch = ingredients
.FirstOrDefault(i => i.Name.Equals(ingredientName, StringComparison.OrdinalIgnoreCase));
Ingredient ingredient;
if (exactMatch == null)
{
ingredient = new Ingredient
{
Id = Guid.NewGuid(),
Name = ingredientName,
};
_context.Ingredients.Add(ingredient);
await _context.SaveChangesAsync();
}
else
{
ingredient = exactMatch;
}
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<Recipe> 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 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<IEnumerable<Recipe>> GetRecipesByNameAndIngredientAsync(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<IReadOnlyCollection<Recipe>> GetRecipesByDifficultyAsync(Difficulty? difficulty)
{
if (!difficulty.HasValue)
{
return await _context.Recipes
.Include(r => r.Category)
.Include(r => r.RecipeIngredients)
.ThenInclude(ri => ri.Ingredient)
.ToListAsync();
}
return await _context.Recipes
.Where(r => r.Difficulty == difficulty.Value)
.Include(r => r.Category)
.Include(r => r.RecipeIngredients)
.ThenInclude(ri => ri.Ingredient)
.ToListAsync();
}
}
}
@@ -0,0 +1,21 @@
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<ShoppingList> AddIngredientsToShoppingListAsync(Guid recipeId, List<Guid> ingredientIds);
Task<IEnumerable<RecipeIngredientShoppingList>> GetIngredientsOfRecipeInListAsync(Guid shoppingListRecipeId);
Task UpdateIngredientCheckedAsync(Guid shoppingListRecipeId, Guid recipeIngredientId, bool isChecked);
Task RemoveRecipeIfEmptyAsync(Guid shoppingListRecipeId);
Task DeleteShoppingListAsync(Guid shoppingListId);
Task<Recipe?> GetRecipeByNameAndImageAsync(string recipeName, string imageFileName);
}
}
@@ -0,0 +1,171 @@
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.RecipeShoppingList;
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<ShoppingList> AddIngredientsToShoppingListAsync(Guid recipeId, List<Guid> ingredientIds)
{
if (ingredientIds == null || !ingredientIds.Any())
{
throw new ArgumentNullException(nameof(ingredientIds));
}
var recipe = await _context.Recipes
.Include(r => r.RecipeIngredients)
.FirstOrDefaultAsync(r => r.Id == recipeId);
if (recipe == null)
{
throw new Exception("Recipe not found.");
}
var shoppingList = await _context.ShoppingLists
.Include(sl => sl.RecipeShoppingList)
.ThenInclude(rsl => rsl.SelectedIngredients)
.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("No valid ingredients found.");
}
if (shoppingList == null)
{
shoppingList = new ShoppingList
{
Id = Guid.NewGuid(),
CreatedAt = DateTime.UtcNow,
RecipeShoppingList = new List<RecipeShoppingList>(),
};
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<RecipeIngredientShoppingList>(),
};
shoppingList.RecipeShoppingList.Add(existingRecipeList);
}
foreach (var ri in recipeIngredients)
{
if (!existingRecipeList.SelectedIngredients.Any(si => si.RecipeIngredient.Id == ri.Id))
{
existingRecipeList.SelectedIngredients.Add(new RecipeIngredientShoppingList
{
Id = Guid.NewGuid(),
RecipeIngredient = ri,
IsChecked = false,
});
}
}
shoppingList.ModifiedAt = DateTime.UtcNow;
}
await _context.SaveChangesAsync();
return shoppingList;
}
public async Task<IEnumerable<RecipeIngredientShoppingList>> 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)
.FirstOrDefaultAsync(r => r.Id == shoppingListRecipeId);
if (recipeEntry != null && !recipeEntry.SelectedIngredients.Any())
{
_context.RecipeShoppingLists.Remove(recipeEntry);
await _context.SaveChangesAsync();
}
}
public async Task DeleteShoppingListAsync(Guid shoppingListId)
{
var list = await _context.ShoppingLists
.FirstOrDefaultAsync(sl => sl.Id == shoppingListId);
if (list != null)
{
_context.ShoppingLists.Remove(list);
await _context.SaveChangesAsync();
}
}
public async Task<Recipe?> 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();
}
}
}
@@ -0,0 +1,13 @@
namespace Francesco.Recipes.World.Repositories.Unit
{
using Francesco.Recipes.World.Models.BackendModels.Unit;
public interface IUnitRepository
{
Task<Unit> GetUnitByIdAsync(Guid unitId);
Task<Unit> AddUnitAsync(string name, string symbol);
Task<IEnumerable<Unit>> GetAllUnitsAsync();
}
}
@@ -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<Unit> GetUnitByIdAsync(Guid unitId)
{
var unit = await _context.Units.FindAsync(unitId);
return unit ?? throw new InvalidDataException($"Address {unitId} not found.");
}
public async Task<Unit> 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<IEnumerable<Unit>> GetAllUnitsAsync()
{
return await _context.Units.ToListAsync();
}
}
}
@@ -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<Recipe> Recipes { get; set; } = new List<Recipe>();
}
}
@@ -0,0 +1,23 @@
@model Francesco.Recipes.World.Models.BackendModels.Category.Category
@{
ViewData["Title"] = "Category Details";
}
<h2>Category Details</h2>
<div>
<h4>Category</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
Name
</dt>
<dd class="col-sm-10">
@Model.Name
</dd>
</dl>
</div>
<div>
<a asp-action="Index" class="btn btn-primary">Back to List</a>
</div>
@@ -0,0 +1,28 @@
@model IEnumerable<Francesco.Recipes.World.Models.BackendModels.Category.Category>
@{
ViewData["Title"] = "Categories";
}
<h2>Categories</h2>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach (var category in Model)
{
<tr>
<td>@category.Name</td>
<td>
<a asp-action="Details" asp-route-id="@category.Id" class="btn btn-primary">Details</a>
<a asp-action="Recipes" asp-route-id="@category.Id" class="btn btn-secondary">Recipes</a>
</td>
</tr>
}
</tbody>
</table>
@@ -1,8 +0,0 @@
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
@@ -0,0 +1,59 @@
@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe
@{
ViewData["Title"] = "Add Instructions";
}
<h1>Add Instructions to @Model.Name</h1>
<div class="instructions-list">
<h3>Existing Instructions</h3>
<ul>
@foreach (var instruction in Model.Instructions.OrderBy(i => i.Number))
{
<li>@instruction.Number. @instruction.Description</li>
}
</ul>
</div>
<div class="add-instruction-form">
<h3>Add New Instruction</h3>
<form id="instruction-form" method="post" asp-action="AddInstruction" asp-controller="Recipe">
<input type="hidden" name="recipeId" value="@Model.Id" />
<div class="form-group">
<label for="description">Description</label>
<input type="text" class="form-control" id="description" name="description" required />
</div>
<div class="form-group">
<label for="number">Number</label>
<input type="number" class="form-control" id="number" name="number" required min="1" />
</div>
<button type="submit" class="btn btn-primary" onclick="addInstructionToRecipe()">Add Instruction</button>
</form>
</div>
@section Scripts {
<script>
async function addInstructionToRecipe() {
var form = document.getElementById('instruction-form');
var recipeId = form.querySelector('input[name="recipeId"]').value;
var description = form.querySelector('input[name="description"]').value;
var number = form.querySelector('input[name="number"]').value;
var response = await fetch('/Recipe/' + recipeId + '/AddInstruction', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ recipeId: recipeId, description: description, number: number })
});
if (response.ok) {
location.reload();
} else {
alert('Failed to add instruction.');
}
}
</script>
}
@@ -0,0 +1,27 @@
@{
ViewData["Title"] = "Add or Create Ingredient to Recipe";
}
<h2>@ViewData["Title"]</h2>
<form asp-action="AddOrCreateIngredient" method="post">
<input type="hidden" id="RecipeId" name="recipeId" value="@ViewBag.RecipeId" />
<div class="form-group">
<label for="IngredientName" class="control-label">Ingredient Name</label>
<input type="text" id="IngredientName" name="ingredientName" class="form-control" />
</div>
<div class="form-group">
<label for="Quantity" class="control-label">Quantity</label>
<input type="number" id="Quantity" name="quantity" class="form-control" />
</div>
<div class="form-group">
<label for="UnitId" class="control-label">Unit</label>
<select id="UnitId" name="unitId" class="form-control" asp-items="ViewBag.Units"></select>
</div>
<button type="submit" class="btn btn-primary">Add Ingredient</button>
</form>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
@@ -0,0 +1,98 @@
@model IEnumerable<Francesco.Recipes.World.Views.Category.CategoryRecipesViewModel>
@{
ViewData["Title"] = "Category Recipes";
}
<h1>Category Recipes</h1>
@foreach (var categoryRecipes in Model)
{
<div class="category-section">
<h2>@categoryRecipes.Category.Name</h2>
<a asp-action="Create" asp-route-categoryId="@categoryRecipes.Category.Id" class="btn btn-primary">Rezept erstellen</a>
<div class="recipes">
@foreach (var recipe in categoryRecipes.Recipes)
{
<div class="recipe-card">
<div class="recipe-image">
@if (recipe.MediaFiles.Any() && recipe.MediaFiles.First().Data != null)
{
var mediaFile = recipe.MediaFiles.First();
if (mediaFile.Data != null)
{
<img src="data:@mediaFile.MimeType;base64,@Convert.ToBase64String(mediaFile.Data)" alt="@recipe.Name" class="img-fluid" />
}
}
</div>
<div class="recipe-info">
<h3>@recipe.Name</h3>
<p>@recipe.Description</p>
<p><strong>Difficulty:</strong> @recipe.Difficulty</p>
<p><strong>Servings:</strong> @recipe.Servings</p>
<p><strong>Preparation Time:</strong> @recipe.PreparationTime</p>
<p><strong>Cooking Time:</strong> @recipe.CookingTime</p>
<div class="recipe-favorite">
@if (recipe.IsFavorite)
{
<form method="post" asp-action="RemoveFavorite" asp-controller="Recipe">
<input type="hidden" name="recipeId" value="@recipe.Id" />
<button type="submit" class="btn btn-danger">Remove from Favorites</button>
</form>
}
else
{
<form method="post" asp-action="AddFavorite" asp-controller="Recipe">
<input type="hidden" name="recipeId" value="@recipe.Id" />
<button type="submit" class="btn btn-primary">Add to Favorites</button>
</form>
}
</div>
</div>
</div>
}
<div class="recipe-card add-recipe-card">
<a asp-action="Create" asp-route-categoryId="@categoryRecipes.Category.Id" class="btn btn-primary">Rezept hinzufügen</a>
</div>
</div>
</div>
}
<style>
.category-section {
margin-bottom: 2rem;
}
.recipes {
display: flex;
flex-wrap: wrap;
}
.recipe-card {
width: 200px;
margin: 1rem;
padding: 1rem;
border: 1px solid #ccc;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.recipe-image img {
width: 100%;
height: auto;
border-radius: 8px;
}
.recipe-info {
margin-top: 1rem;
}
.add-recipe-card {
display: flex;
align-items: center;
justify-content: center;
background-color: #f8f8f8;
border: 2px dashed #ccc;
}
</style>
@@ -0,0 +1,58 @@
@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe
@using Francesco.Recipes.World.Models.BackendModels.Recipe
@{
ViewData["Title"] = "Create Recipe";
}
<h1>Create Recipe</h1>
<form method="post" enctype="multipart/form-data">
<div class="form-group">
<label asp-for="Name"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Description"></label>
<textarea asp-for="Description" class="form-control"></textarea>
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Difficulty"></label>
<select asp-for="Difficulty" class="form-control">
<option value="">Select Difficulty</option>
@foreach (var difficulty in Enum.GetValues(typeof(Difficulty)))
{
<option value="@difficulty">@difficulty</option>
}
</select>
<span asp-validation-for="Difficulty" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Servings"></label>
<input asp-for="Servings" class="form-control" />
<span asp-validation-for="Servings" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PreparationTime"></label>
<input asp-for="PreparationTime" class="form-control" />
<span asp-validation-for="PreparationTime" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="CookingTime"></label>
<input asp-for="CookingTime" class="form-control" />
<span asp-validation-for="CookingTime" class="text-danger"></span>
</div>
<div class="form-group">
<label for="photo">Photo</label>
<input type="file" name="photo" class="form-control" />
</div>
<button type="submit" class="btn btn-primary">Create</button>
</form>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
}
@@ -0,0 +1,68 @@
@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe
@{
ViewData["Title"] = "Recipe Details";
}
<h1>@Model.Name</h1>
<div class="recipe-details">
<div class="recipe-image">
@if (Model.MediaFiles.Any() && Model.MediaFiles.First().Data != null)
{
var mediaFile = Model.MediaFiles.First();
if (mediaFile.Data != null)
{
<img src="data:@mediaFile.MimeType;base64,@Convert.ToBase64String(mediaFile.Data)" alt="@Model.Name" class="img-fluid" />
}
}
</div>
<div class="recipe-info">
<p><strong>Description:</strong> @Model.Description</p>
<p><strong>Difficulty:</strong> @Model.Difficulty</p>
<p><strong>Servings:</strong> @Model.Servings</p>
<p><strong>Preparation Time:</strong> @Model.PreparationTime</p>
<p><strong>Cooking Time:</strong> @Model.CookingTime</p>
</div>
<div class="recipe-ingredients">
<h3>Ingredients</h3>
<form id="ingredient-form">
<ul id="ingredient-list">
@foreach (var ingredient in Model.RecipeIngredients)
{
<li id="ingredient-@ingredient.Id">
<input type="checkbox" name="ingredientIds" value="@ingredient.Id" />
@ingredient.Ingredient.Name - @ingredient.Quantity @(ingredient.Unit != null ? ingredient.Unit.Symbol : string.Empty)
</li>
}
</ul>
<button type="button" class="btn btn-primary" onclick="addSelectedIngredientsToShoppingList()">Add Selected to Shopping List</button>
</form>
</div>
</div>
@section Scripts {
<script>
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.');
}
}
</script>
}
@@ -0,0 +1,72 @@
@using Francesco.Recipes.World.Models.BackendModels.Recipe
@model Francesco.Recipes.World.Views.Recipe.FilterByDifficultyViewModel
<h1>Rezepte nach Schwierigkeitsgrad</h1>
<div class="mb-3">
<a asp-controller="Recipe" asp-action="Create" class="btn btn-primary">Neues Rezept erstellen</a>
</div>
<div class="row mb-4">
<div class="col-md-6">
<form asp-controller="Recipe" asp-action="FilterByDifficulty" method="get" id="filterForm">
<div class="form-group">
<label asp-for="SelectedDifficulty" class="form-label">Schwierigkeitsgrad</label>
<select asp-for="SelectedDifficulty" asp-items="Html.GetEnumSelectList<Difficulty>()" class="form-select" onchange="submitForm()">
<option value="">Alle Schwierigkeitsgrade</option>
</select>
</div>
</form>
</div>
</div>
<div class="row">
@if (Model?.Recipes != null && Model.Recipes.Any())
{
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Beschreibung</th>
<th>Schwierigkeitsgrad</th>
<th>Portionen</th>
<th>Zubereitungszeit</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
@foreach (var recipe in Model.Recipes)
{
<tr>
<td>@recipe.Name</td>
<td>@(recipe.Description?.Length > 100 ? recipe.Description.Substring(0, 100) + "..." : recipe.Description)</td>
<td>@recipe.?Difficulty</td>
<td>@recipe.Servings</td>
<td>@($"{recipe.PreparationTime.TotalMinutes} Min.")</td>
<td>
<a asp-controller="Recipe" asp-action="Details" asp-route-id="@recipe.Id" class="btn btn-sm btn-info">Details</a>
<a asp-controller="Recipe" asp-action="Edit" asp-route-id="@recipe.Id" class="btn btn-sm btn-primary">Bearbeiten</a>
</td>
</tr>
}
</tbody>
</table>
</div>
}
else
{
<div class="col-12">
<p>Keine Rezepte gefunden.</p>
</div>
}
</div>
@section Scripts {
<script>
function submitForm() {
document.getElementById('filterForm').submit();
}
</script>
}
@@ -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 IReadOnlyCollection<Recipe> Recipes { get; set; } = new List<Recipe>();
}
}
@@ -0,0 +1,23 @@
@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe
@{
ViewData["Title"] = "Remove Ingredient";
}
<h1>Remove Ingredient</h1>
<h3>Are you sure you want to remove the ingredient '@ViewBag.IngredientName' from this recipe?</h3>
<form asp-action="RemoveIngredientConfirmed" asp-route-categoryId="@ViewBag.CategoryId" asp-route-recipeId="@ViewBag.RecipeId" asp-route-ingredientId="@ViewBag.IngredientId" method="post">
<input type="hidden" name="categoryId" value="@ViewBag.CategoryId" />
<input type="hidden" name="recipeId" value="@ViewBag.RecipeId" />
<input type="hidden" name="ingredientId" value="@ViewBag.IngredientId" />
<div class="form-group">
<input type="submit" value="Remove" class="btn btn-danger" />
<a asp-action="Details" asp-route-id="@ViewBag.RecipeId" class="btn btn-secondary">Cancel</a>
</div>
</form>
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
}
@@ -0,0 +1,43 @@
@model IEnumerable<Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient>
<div class="recipe-ingredients">
<h3>Zutaten</h3>
<form id="ingredient-form">
<ul id="ingredient-list">
@foreach (var ingredient in Model)
{
<li id="ingredient-@ingredient.Id">
<input type="checkbox" name="ingredientIds" value="@ingredient.Id" />
@ingredient.Ingredient.Name - @ingredient.Quantity @ingredient.Unit.Symbol
</li>
}
</ul>
<button type="button" class="btn btn-primary" onclick="addSelectedIngredientsToShoppingList()">Ausgewählte zur Einkaufsliste hinzufügen</button>
</form>
</div>
@section Scripts {
<script>
function addSelectedIngredientsToShoppingList() {
var form = document.getElementById('ingredient-form');
var formData = new FormData(form);
var selectedIngredientIds = formData.getAll('ingredientIds');
fetch('/ShoppingList/AddSelectedIngredients', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ ingredientIds: selectedIngredientIds })
}).then(response => {
if (response.ok) {
alert('Ausgewählte Zutaten wurden zur Einkaufsliste hinzugefügt.');
} else {
alert('Fehler beim Hinzufügen der ausgewählten Zutaten zur Einkaufsliste.');
}
}).catch(error => {
alert('Ein Fehler ist aufgetreten: ' + error.message);
});
}
</script>
}
@@ -44,6 +44,7 @@
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
<script src="https://unpkg.com/htmx.org@2.0.4/dist/htmx.js" integrity="sha384-oeUn82QNXPuVkGCkcrInrS1twIxKhkZiFfr2TdiuObZ3n3yIeMiqcRzkIcguaof1" crossorigin="anonymous"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
@@ -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";
}
<h2>Einkaufsliste Details</h2>
@if (TempData["SuccessMessage"] != null)
{
<div class="alert alert-success">
@TempData["SuccessMessage"]
</div>
}
<div>
<h4>Einkaufsliste</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
ID
</dt>
<dd class="col-sm-10">
@Model.Id
</dd>
</dl>
</div>
<h4>Rezepte</h4>
<table class="table">
<thead>
<tr>
<th>Rezeptname</th>
<th>Zutaten</th>
</tr>
</thead>
<tbody>
@foreach (var recipeShoppingList in Model.RecipeShoppingList)
{
<tr>
<td>@recipeShoppingList.Recipe.Name</td>
<td>
<ul>
@foreach (var ingredient in recipeShoppingList.SelectedIngredients)
{
<li>@ingredient.RecipeIngredient.Ingredient.Name - @ingredient.RecipeIngredient.Quantity @ingredient.RecipeIngredient.Unit.Name</li>
}
</ul>
</td>
</tr>
}
</tbody>
</table>