Merge pull request #3 from francesco448/feature-copy

Feature copy
This commit is contained in:
francesco448
2026-03-02 16:39:08 +01:00
committed by GitHub
95 changed files with 8667 additions and 571 deletions
+9 -1
View File
@@ -126,7 +126,7 @@ dotnet_diagnostic.SA1601.severity = none
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.DocumentationRules.severity = none
# SA1516: Elements should be separated by blank line
dotnet_diagnostic.SA1516.severity = none
dotnet_diagnostic.SA1516.severity = error
# SA1649: File name should match first type name
dotnet_diagnostic.SA1649.severity = none
@@ -142,3 +142,11 @@ dotnet_diagnostic.SA1309.severity = none
# SA1309: Make sure class members are allowed to call without "this" prefix
dotnet_diagnostic.SA1101.severity = none
# SA1118: parameter spans multiple lines
dotnet_diagnostic.SA1118.severity = none
# SA1011: closing square bracket spacing
dotnet_diagnostic.SA1011.severity = none
# SA1517: no blank lines at start of file
dotnet_diagnostic.SA1517.severity = none
# SA1200: using directive should appear within a namespace
dotnet_diagnostic.SA1200.severity = none
+2 -1
View File
@@ -396,4 +396,5 @@ FodyWeavers.xsd
# JetBrains Rider
*.sln.iml
src/Recruitment.Tool.xml
src/Francesco.Recipes.World.xml
+6
View File
@@ -5,6 +5,8 @@ VisualStudioVersion = 17.11.35222.181
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Francesco.Recipes.World", "Francesco.Recipes.World\Francesco.Recipes.World.csproj", "{4D1BBCF4-8E06-4584-A383-B14BEC558408}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrancescosRecipeWorld Mock", "FrancescosRecipeWorld Mock\FrancescosRecipeWorld Mock.csproj", "{04FD3555-3497-4F6B-B2C3-1B22EEFA676B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -15,6 +17,10 @@ Global
{4D1BBCF4-8E06-4584-A383-B14BEC558408}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4D1BBCF4-8E06-4584-A383-B14BEC558408}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4D1BBCF4-8E06-4584-A383-B14BEC558408}.Release|Any CPU.Build.0 = Release|Any CPU
{04FD3555-3497-4F6B-B2C3-1B22EEFA676B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{04FD3555-3497-4F6B-B2C3-1B22EEFA676B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04FD3555-3497-4F6B-B2C3-1B22EEFA676B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04FD3555-3497-4F6B-B2C3-1B22EEFA676B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -0,0 +1,7 @@
namespace Francesco.Recipes.World.Constants
{
public class ContentType
{
public const string Image = "image/";
}
}
@@ -0,0 +1,8 @@
namespace Francesco.Recipes.World.Constants
{
public class SortOrders
{
public const string Newest = "newest";
public const string Oldest = "oldest";
}
}
@@ -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,35 @@
namespace Francesco.Recipes.World.Controller.Favorite
{
using Francesco.Recipes.World.Constants;
using Francesco.Recipes.World.Models;
using Francesco.Recipes.World.Repositories.Favorit;
using Microsoft.AspNetCore.Mvc;
[Route("Favorite")]
public class FavoriteController : Controller
{
private readonly IFavoriteRepository _favoriteRepository;
public FavoriteController(IFavoriteRepository favoriteRepository)
{
_favoriteRepository = favoriteRepository;
}
public async Task<IActionResult> Index(string sortOrder = SortOrders.Newest)
{
var favoriteRecipes = await _favoriteRepository.GetFavoriteRecipesAsync();
var sortedRecipes = sortOrder == SortOrders.Oldest
? favoriteRecipes.OrderBy(r => r.Favorite.CreatedAt)
: favoriteRecipes.OrderByDescending(r => r.Favorite.CreatedAt);
var viewModel = new FavoriteViewModel
{
FavoriteRecipes = sortedRecipes,
SortOrder = sortOrder,
};
return View(viewModel);
}
}
}
@@ -0,0 +1,40 @@
namespace Francesco.Recipes.World.Controller.HomeController
{
using Francesco.Recipes.World.Repositories.Category;
using Francesco.Recipes.World.Repositories.Recipe;
using Francesco.Recipes.World.Views.Category;
using Microsoft.AspNetCore.Mvc;
public class HomeController : Controller
{
private readonly IRecipeRepository _recipeRepository;
private readonly ICategoryRepository _categoryRepository;
public HomeController(ICategoryRepository categoryRepository, IRecipeRepository recipeRepository)
{
_recipeRepository = recipeRepository;
_categoryRepository = categoryRepository;
}
[HttpGet]
public async Task<IActionResult> Index()
{
var categories = await _categoryRepository.GetAllCategoriesWithRecipesAsync();
var viewModel = categories.Select(category => new CategoryRecipesViewModel
{
Category = category,
Recipes = category.Recipes,
});
return View("Index", viewModel);
}
[HttpGet("/Home/Search")]
public async Task<IActionResult> Search(string term)
{
var recipes = await _recipeRepository.SearchInRecipesAndIngredients(term);
return PartialView("_SearchResultsPartial", recipes);
}
}
}
@@ -0,0 +1,6 @@
namespace Francesco.Recipes.World.Controller.Ingredient
{
public class IngredientController
{
}
}
@@ -0,0 +1,69 @@
namespace Francesco.Recipes.World.Controller.Instruction
{
using Francesco.Recipes.World.Models;
using Francesco.Recipes.World.Repositories.Instruction;
using Francesco.Recipes.World.Services.Instruction;
using Microsoft.AspNetCore.Mvc;
public class InstructionController : Controller
{
private readonly IInstructionService _instructionService;
private readonly IInstructionRepository _instructionRepository;
public InstructionController(IInstructionService instructionService, IInstructionRepository instructionRepository)
{
_instructionService = instructionService;
_instructionRepository = instructionRepository;
}
[HttpPost("Recipe/{recipeId}/Instruction/{instructionId}/move-up")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> MoveUp(Guid recipeId, Guid instructionId)
{
try
{
await _instructionService.MoveInstructionUpAsync(recipeId, instructionId);
return Ok(new { Message = "Instruction moved up successfully." });
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
}
[HttpPost("Recipe/{recipeId}/Instruction/{instructionId}/move-down")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> MoveDown(Guid recipeId, Guid instructionId)
{
try
{
await _instructionService.MoveInstructionDownAsync(recipeId, instructionId);
return Ok(new { Message = "Instruction moved down successfully." });
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
}
[HttpGet("Recipe/{recipeId}/Instructions")]
public async Task<IActionResult> GetInstructions(Guid recipeId)
{
try
{
var sortedInstructions = await _instructionService.GetSortedInstructionsAsync(recipeId);
var viewModel = new InstructionViewModel
{
RecipeId = recipeId,
Instructions = sortedInstructions,
};
return View("~/Views/Shared/_GetInstructions.cshtml", viewModel);
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
}
}
}
@@ -0,0 +1,97 @@
namespace Francesco.Recipes.World.Controller.MediaFile
{
using Francesco.Recipes.World.Repositories.MediaFile;
using Microsoft.AspNetCore.Mvc;
[Route("Category/{categoryId}/Recipe")]
public class MediaFileController : Controller
{
private readonly IMediaFileRepository _mediaFileRepository;
public MediaFileController(IMediaFileRepository mediaFileRepository)
{
_mediaFileRepository = mediaFileRepository;
}
// POST: /UploadImage
[HttpPost("UploadImage")]
[ValidateAntiForgeryToken]
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")]
[ValidateAntiForgeryToken]
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}");
}
}
}
[HttpPost("Recipe/{recipeId}/Instruction/{instructionId}/UploadImage")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadInstructionImage(Guid recipeId, Guid instructionId, IFormFile? photo)
{
if (recipeId == Guid.Empty)
{
return BadRequest("Recipe ID is required.");
}
if (photo == null)
{
return BadRequest("Photo is required.");
}
try
{
await _mediaFileRepository.UploadInstructionImageAsync(instructionId, photo);
return RedirectToAction("GetInstructions", "Instruction", new { recipeId });
}
catch (Exception ex)
{
return StatusCode(500, $"Internal server error: {ex.Message}");
}
}
}
}
@@ -0,0 +1,519 @@
namespace Francesco.Recipes.World.Controller.Recipe
{
using Francesco.Recipes.World.Data;
using Francesco.Recipes.World.Models;
using Francesco.Recipes.World.Models.BackendModels.Ingredient;
using Francesco.Recipes.World.Models.BackendModels.Instruction;
using Francesco.Recipes.World.Models.BackendModels.Recipe;
using Francesco.Recipes.World.Repositories.Category;
using Francesco.Recipes.World.Repositories.Favorit;
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;
private readonly FrancescosRecipesWorldDbContext _context;
public IReadOnlyCollection<Recipe> Recipes { get; set; }
public RecipeController(
IRecipeRepository recipeRepository,
IUnitRepository unitRepository,
ICategoryRepository categoryRepository,
IIngredientRepository ingredientRepository,
IMediaFileRepository mediaFileRepository,
IInstructionRepository instructionRepository,
IFavoriteRepository favoriteRepository,
FrancescosRecipesWorldDbContext context)
{
_recipeRepository = recipeRepository;
_unitRepository = unitRepository;
_categoryRepository = categoryRepository;
_ingredientRepository = ingredientRepository;
Recipes = new List<Recipe>();
_mediaFileRepository = mediaFileRepository;
_instructionRepository = instructionRepository;
_favoriteRepository = favoriteRepository;
_context = context;
}
// GET: /Recipe/{recipeId}/AddOrCreateIngredient
[HttpGet("{recipeId}/AddOrCreateIngredient")]
public async Task<IActionResult> AddOrCreateIngredient(Guid recipeId)
{
if (recipeId == Guid.Empty)
{
return BadRequest("Recipe ID cannot be empty.");
}
var units = await _unitRepository.GetAllUnitsAsync();
var recipeIngredients = await _ingredientRepository.GetIngredientsByRecipeIdAsync(recipeId);
var ingredients = recipeIngredients.Select(ri => ri.Ingredient).ToList();
var viewModel = new IngredientViewModel
{
RecipeId = recipeId,
Ingredients = ingredients,
Units = units.ToList(),
};
return View(viewModel);
}
// 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 (recipeId == Guid.Empty)
{
return BadRequest("Recipe ID cannot be empty.");
}
if (quantity <= 0)
{
ModelState.AddModelError(nameof(quantity), "Die Menge muss größer als 0 sein.");
}
if (!ModelState.IsValid)
{
var units = await _unitRepository.GetAllUnitsAsync();
ViewBag.Units = new SelectList(units, "Id", "Name");
return View();
}
await _recipeRepository.CreateRecipeIngredientAsync(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.");
}
var units = await _unitRepository.GetAllUnitsAsync();
var viewModel = new CreateRecipeViewModel
{
CategoryId = categoryId,
CategoryName = category.Name,
IngredientViewModel = new IngredientViewModel
{
RecipeId = Guid.Empty,
Ingredients = new List<Ingredient>(),
Units = units.ToList(),
},
InstructionViewModel = new InstructionViewModel
{
RecipeId = Guid.Empty,
Instructions = new List<Instruction>(),
},
};
return View(viewModel);
}
// POST: /Recipe/Create/{categoryId}
[HttpPost("Create/{categoryId}")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Guid categoryId, CreateRecipeViewModel model)
{
if (model == null)
{
throw new ArgumentNullException(nameof(model), "CreateRecipeViewModel cannot be null.");
}
if (string.IsNullOrWhiteSpace(model.Name))
{
ModelState.AddModelError(nameof(model.Name), "Name darf nicht leer sein.");
}
if (model.Servings <= 0)
{
ModelState.AddModelError(nameof(model.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.");
}
var units = await _unitRepository.GetAllUnitsAsync();
if (model.IngredientViewModel == null)
{
model.IngredientViewModel = new IngredientViewModel
{
RecipeId = Guid.Empty,
Ingredients = new List<Ingredient>(),
Units = units.ToList(),
};
}
else
{
model.IngredientViewModel.Units = units.ToList();
}
model.CategoryName = category.Name;
return View(model);
}
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
var categoryEntity = await _categoryRepository.GetCategoryByIdAsync(categoryId);
if (categoryEntity == null)
{
return NotFound($"Kategorie mit ID {categoryId} wurde nicht gefunden.");
}
model.PreparationTime = new TimeSpan(model.PrepHours, model.PrepMinutes, 0);
model.CookingTime = new TimeSpan(model.CookHours, model.CookMinutes, 0);
var recipe = await _recipeRepository.CreateRecipeForCategoryAsync(
categoryEntity,
model.Name,
model.Description ?? string.Empty,
model.Difficulty,
model.Servings,
model.PreparationTime,
model.CookingTime);
if (model.Photo != null)
{
await _mediaFileRepository.UploadRecipeMediaAsync(recipe.Id, model.Photo);
}
if (model.Video != null)
{
await _mediaFileRepository.UploadRecipeMediaAsync(recipe.Id, model.Video);
}
if (model.IngredientViewModel?.Ingredients != null)
{
foreach (var ingredient in model.IngredientViewModel.Ingredients)
{
var ri = ingredient.RecipeIngredients?.FirstOrDefault();
if (!string.IsNullOrWhiteSpace(ingredient.Name) && ri?.Quantity > 0 && ri?.Unit?.Id != null)
{
await _recipeRepository.CreateRecipeIngredientAsync(
recipe.Id,
ingredient.Name,
ri.Quantity,
ri.Unit.Id);
}
}
}
if (model.InstructionViewModel?.Instructions != null)
{
for (var i = 0; i < model.InstructionViewModel.Instructions.Count; i++)
{
var instruction = model.InstructionViewModel.Instructions[i];
if (!string.IsNullOrWhiteSpace(instruction.Description))
{
var fileKey = $"InstructionViewModel.Instructions[{i}].MediaFile";
IFormFile? imageFile = null;
if (Request.Form.Files.Any(f => f.Name == fileKey))
{
imageFile = Request.Form.Files[fileKey];
}
await _instructionRepository.CreateInstructionAsync(
recipe.Id,
instruction.Description,
imageFile);
}
}
}
await transaction.CommitAsync();
return RedirectToAction("Details", new { recipeId = recipe.Id });
}
catch (Exception ex)
{
await transaction.RollbackAsync();
return BadRequest(ex.Message);
}
}
// 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();
}
// DELETE: /Recipe/{recipeId}/RemoveIngredient/{ingredientId}
[HttpDelete("{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/{recipeId}/RemoveInstruction/{instructionId}
[HttpGet("{recipeId}/RemoveInstruction/{instructionId}")]
public async Task<IActionResult> RemoveInstruction(Guid recipeId, Guid instructionId)
{
var recipe = await _recipeRepository.GetRecipeAsync(recipeId);
if (recipe == null)
{
return NotFound("Recipe not found.");
}
var instruction = recipe.Instructions?.FirstOrDefault(i => i.Id == instructionId);
if (instruction == null)
{
return NotFound("Instruction not found in the specified recipe.");
}
ViewBag.RecipeId = recipeId;
ViewBag.InstructionId = instructionId;
return View();
}
// DELETE: /Recipe/{recipeId}/RemoveInstruction/{instructionId}
[HttpDelete("{recipeId}/RemoveInstruction/{instructionId}")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveInstructionConfirmed(Guid recipeId, Guid instructionId)
{
try
{
await _instructionRepository.RemoveInstructionFromRecipeAsync(recipeId, instructionId);
TempData["SuccessMessage"] = "Instruction removed successfully.";
return RedirectToAction("Details", new { recipeId });
}
catch (Exception ex)
{
TempData["ErrorMessage"] = $"An error occurred while removing the instruction: {ex.Message}";
return RedirectToAction("Details", new { recipeId });
}
}
// GET: /Recipe/FilterByDifficulty
[HttpGet("FilterByDifficulty")]
public async Task<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.");
}
var instructions = await _instructionRepository.GetInstructionsOfRecipeAsync(recipeId);
var viewModel = new InstructionViewModel
{
RecipeId = recipeId,
Instructions = instructions,
};
return View(viewModel);
}
// POST: /Recipe/{recipeId}/AddInstruction
[HttpPost("{recipeId}/AddInstruction")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddInstruction(Guid recipeId, string description, IFormFile? image)
{
try
{
await _instructionRepository.CreateInstructionAsync(recipeId, description, image);
var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId);
var instructions = recipe?.Instructions?.ToList() ?? new List<Instruction>();
var viewModel = new InstructionViewModel
{
RecipeId = recipeId,
Instructions = instructions,
};
return RedirectToAction("AddInstruction", new { recipeId });
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
// 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);
var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId);
if (recipe == null)
{
return NotFound("Recipe not found.");
}
return PartialView("_FavoriteButton", recipe);
}
// POST: /Recipe/RemoveFavorite
[HttpPost("RemoveFavorite")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveFavorite(Guid recipeId)
{
await _favoriteRepository.RemoveFavoriteAsync(recipeId);
var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId);
if (recipe == null)
{
return NotFound("Recipe not found.");
}
return PartialView("_FavoriteButton", recipe);
}
// GET: /Recipe/{recipeId}/GetIngredients
[HttpGet("{recipeId}/GetIngredients")]
public async Task<IActionResult> GetIngredients(Guid recipeId)
{
var recipeIngredients = (await _ingredientRepository.GetIngredientsByRecipeIdAsync(recipeId)).Select(ri => ri.Ingredient).ToList();
var units = await _unitRepository.GetAllUnitsAsync();
var viewModel = new IngredientViewModel
{
RecipeId = recipeId,
Ingredients = recipeIngredients,
Units = units,
};
return PartialView("_IngredientsPartial", viewModel);
}
// GET: /Recipe/{recipeId}/AdjustableIngredients
[HttpGet("{recipeId}/AdjustableIngredients")]
public async Task<IActionResult> GetAdjustableIngredients(Guid recipeId)
{
var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId);
if (recipe == null)
{
return NotFound("Recipe not found.");
}
return PartialView("_AdjustableIngredientsPartial", recipe);
}
// GET: /Recipe/{recipeId}/Delete
[HttpGet("{recipeId}/Delete")]
public async Task<IActionResult> Delete(Guid recipeId)
{
var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId);
if (recipe == null)
{
return NotFound("Recipe not found.");
}
return View(recipe);
}
// DELETE: /Recipe/{recipeId}/Delete
[HttpDelete("{recipeId}/Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(Guid recipeId)
{
var deleted = await _recipeRepository.DeleteRecipeAsync(recipeId);
if (deleted)
{
TempData["SuccessMessage"] = "Rezept wurde erfolgreich gelöscht.";
return RedirectToAction("Index", "Home");
}
else
{
TempData["ErrorMessage"] = "Rezept nicht gefunden oder konnte nicht gelöscht werden.";
return RedirectToAction("Details", new { recipeId });
}
}
}
}
@@ -0,0 +1,154 @@
namespace Francesco.Recipes.World.Controllers
{
using Francesco.Recipes.World.Data;
using Francesco.Recipes.World.Models;
using Francesco.Recipes.World.Repositories.ShoppingList;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
[Route("ShoppingList")]
public class ShoppingListController : Controller
{
private readonly IShoppingListRepository _shoppingListRepository;
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 });
}
[HttpGet("RecipeCount")]
public async Task<IActionResult> RecipeCount()
{
var count = await _shoppingListRepository.CountAllRecipeShoppinglistsAsync();
return Json(new { count });
}
// GET: /ShoppingList/Details
[HttpGet("Details")]
public async Task<IActionResult> Details()
{
var recipeCount = await _shoppingListRepository.CountAllRecipeShoppinglistsAsync();
var recipeIngredientToShoppingListMap = new Dictionary<Guid, Guid>();
var allEntries = await _context.RecipeIngredientsShoppingLists
.Where(risl => risl.RecipeIngredient != null)
.Select(risl => new
{
RecipeIngredientId = risl.RecipeIngredient.Id,
ShoppingListId = risl.Id,
})
.ToListAsync();
foreach (var entry in allEntries)
{
if (entry.RecipeIngredientId != Guid.Empty &&
!recipeIngredientToShoppingListMap.ContainsKey(entry.RecipeIngredientId))
{
recipeIngredientToShoppingListMap.Add(entry.RecipeIngredientId, entry.ShoppingListId);
}
}
var recipeInAnyShoppingList = (await _shoppingListRepository.GetAllShoppingListsAsync())
.SelectMany(sl => sl.RecipeShoppingList.Select(rsl => rsl.Recipe))
.ToList();
var viewModel = new ShoppingListDetailsViewModel
{
RecipeCount = recipeCount,
RecipesInAnyShoppingList = recipeInAnyShoppingList,
RecipeIngredientToShoppingListMap = recipeIngredientToShoppingListMap,
};
return View(viewModel);
}
[HttpDelete("RemoveIngredients")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveIngredients([FromBody] List<Guid> recipeIngredientShoppingListIds)
{
if (recipeIngredientShoppingListIds == null || !recipeIngredientShoppingListIds.Any())
{
return BadRequest("Keine Zutaten zum Entfernen angegeben.");
}
try
{
var affectedRecipeIds = await _context.RecipeIngredientsShoppingLists
.Where(risl => recipeIngredientShoppingListIds.Contains(risl.Id))
.Select(risl => risl.RecipeShoppingList.Recipe.Id)
.Distinct()
.ToListAsync();
await _shoppingListRepository.RemoveIngredientsFromShoppingListAsync(recipeIngredientShoppingListIds);
var remainingRecipeIds = await _context.RecipeShoppingLists
.Select(rsl => rsl.Recipe.Id)
.ToListAsync();
var removedRecipeIds = affectedRecipeIds
.Where(id => !remainingRecipeIds.Contains(id))
.ToList();
return Json(new { success = true, removedRecipeIds });
}
catch (Exception ex)
{
return BadRequest(new { error = ex.Message });
}
}
[HttpDelete("RemoveRecipeFromList/{recipeId}")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveRecipeFromList(Guid recipeId)
{
try
{
var recipeShoppingLists = await _context.RecipeShoppingLists
.Where(rsl => rsl.Recipe.Id == recipeId)
.ToListAsync();
if (!recipeShoppingLists.Any())
{
return NotFound($"Kein Einkaufslisten-Eintrag für Rezept mit ID {recipeId} gefunden.");
}
foreach (var recipeShoppingList in recipeShoppingLists)
{
await _shoppingListRepository.RemoveRecipeFromShoppingListAsync(recipeShoppingList.Id);
}
return Ok(new { success = true });
}
catch (Exception ex)
{
return BadRequest(new { success = false, error = ex.Message });
}
}
}
}
@@ -0,0 +1,25 @@
namespace Francesco.Recipes.World.Controller.Unit
{
using Francesco.Recipes.World.Repositories.Unit;
using Microsoft.AspNetCore.Mvc;
[Route("Unit")]
public class UnitController : Controller
{
private readonly IUnitRepository _unitRepository;
public UnitController(IUnitRepository unitRepository)
{
_unitRepository = unitRepository;
}
[HttpGet("GetAllUnits")]
public async Task<IActionResult> GetAllUnits()
{
var units = (await _unitRepository.GetAllUnitsAsync())
.Select(u => new { id = u.Id, name = u.Name })
.ToList();
return Json(units);
}
}
}
@@ -1,32 +0,0 @@
namespace Francesco.Recipes.World.Controllers
{
using System.Diagnostics;
using Francesco.Recipes.World.Models;
using Microsoft.AspNetCore.Mvc;
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
@@ -1,25 +1,119 @@
namespace Francesco.Recipes.World.Data
{
namespace Francesco.Recipes.World.Data;
using Francesco.Recipes.World.Models.BackendModels;
using Francesco.Recipes.World.Models.BackendModels.Category;
using Francesco.Recipes.World.Models.BackendModels.Favorit;
using Francesco.Recipes.World.Models.BackendModels.Ingredient;
using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList;
using Francesco.Recipes.World.Models.BackendModels.Instruction;
using Francesco.Recipes.World.Models.BackendModels.MediaFile;
using Francesco.Recipes.World.Models.BackendModels.Recipe;
using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient;
using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList;
using Francesco.Recipes.World.Models.BackendModels.Shoppinglist;
using Francesco.Recipes.World.Models.BackendModels.Unit;
using Microsoft.EntityFrameworkCore;
public class FrancescosRecipesWorldDbContext : DbContext
{
public FrancescosRecipesWorldDbContext(DbContextOptions<FrancescosRecipesWorldDbContext> options)
: base(options)
{
public FrancescosRecipesWorldDbContext(DbContextOptions<FrancescosRecipesWorldDbContext> options)
: base(options)
{
}
public DbSet<Category> Categories => Set<Category>();
public DbSet<Ingredient> Ingredients => Set<Ingredient>();
public DbSet<Instruction> Instructions => Set<Instruction>();
public DbSet<Recipe> Recipes => Set<Recipe>();
public DbSet<RecipeIngredient> RecipeIngredients => Set<RecipeIngredient>();
public DbSet<Unit> Units => Set<Unit>();
public DbSet<Favorit> Favorits => Set<Favorit>();
public DbSet<RecipeIngredientShoppingList> RecipeIngredientsShoppingLists => Set<RecipeIngredientShoppingList>();
public DbSet<RecipeShoppingList> RecipeShoppingLists => Set<RecipeShoppingList>();
public DbSet<ShoppingList> ShoppingLists => Set<ShoppingList>();
public DbSet<MediaFile> MediaFiles => Set<MediaFile>();
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
foreach (var entry in ChangeTracker.Entries())
{
if (entry.Entity is ITimeStampedEntity timeStampedEntity)
{
switch (entry.State)
{
case EntityState.Added:
timeStampedEntity.CreatedAt = DateTime.UtcNow;
break;
case EntityState.Modified:
timeStampedEntity.ModifiedAt = DateTime.UtcNow;
break;
}
}
}
return base.SaveChangesAsync(cancellationToken);
}
protected override void OnModelCreating(ModelBuilder builder)
{
ArgumentNullException.ThrowIfNull(builder);
SeedData(builder);
base.OnModelCreating(builder);
}
private static void SeedData(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>()
.HasData(GetCategories());
modelBuilder.Entity<Unit>()
.HasData(GetUnits());
}
private static IEnumerable<Category> GetCategories()
{
return
[
new Category { Id = Guid.Parse("b248244f-f21c-4555-a14d-5dd49a2717cf"), Name = "Vorspeisen & Snacks" },
new Category { Id = Guid.Parse("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"), Name = "Erste Gänge" },
new Category { Id = Guid.Parse("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"), Name = "Hauptgerichte" },
new Category { Id = Guid.Parse("90deec39-dcd0-422d-9018-ac8389e332e1"), Name = "Desserts & Süßspeisen" },
new Category { Id = Guid.Parse("28e39168-701a-4084-81da-d96c987c462f"), Name = "Beilagen & Salate" },
new Category { Id = Guid.Parse("20585b74-4805-4aff-a6df-aa6b7af04ff1"), Name = "Kuchen" },
new Category { Id = Guid.Parse("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"), Name = "Hefegebäck & Brot" },
new Category { Id = Guid.Parse("d332f88d-d241-48c5-a2f2-bfd124eada7e"), Name = "Soßen & Saucen" },
new Category { Id = Guid.Parse("23b1c740-e427-44f9-a6ea-d33d3f30f05a"), Name = "Marmeladen & Eingemachtes" },
new Category { Id = Guid.Parse("0a91a200-dc76-4e00-b38c-b38cab5b69d7"), Name = "Getränke" },
];
}
private static IEnumerable<Unit> GetUnits()
{
return
[
new Unit { Id = Guid.Parse("62eb2c11-8768-46fb-9db0-c77f940bb4aa"), Name = "liter", Symbol = "l" },
new Unit { Id = Guid.Parse("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"), Name = "gramm", Symbol = "g" },
new Unit { Id = Guid.Parse("7e3d1b86-ac48-45d6-814e-d3492a86db1d"), Name = "kilogramm", Symbol = "kg" },
new Unit { Id = Guid.Parse("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"), Name = "stücke", Symbol = "stk" },
new Unit { Id = Guid.Parse("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"), Name = "blatt", Symbol = "blatt" },
new Unit { Id = Guid.Parse("df5cb4c3-4de6-4c6f-be8c-da41b2986408"), Name = "messerspitze", Symbol = "msp" },
new Unit { Id = Guid.Parse("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"), Name = "stange", Symbol = "stange" },
new Unit { Id = Guid.Parse("7ea2f51d-7493-4f19-a663-1f309186d3ae"), Name = "bund", Symbol = "bund" },
new Unit { Id = Guid.Parse("66556e0e-eb2b-4bc3-9a56-135dd508ed09"), Name = "zehe", Symbol = "zehe" },
new Unit { Id = Guid.Parse("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"), Name = "teelöffel", Symbol = "TL" },
new Unit { Id = Guid.Parse("24a91b89-3389-4465-89e4-2f70d2ea6fd7"), Name = "esslöffel", Symbol = "EL" },
];
}
}
}
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
@@ -6,10 +6,14 @@
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>be6a70eb-b657-40b2-b4a1-418e5c6ec131</UserSecretsId>
</PropertyGroup>
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<CodeAnalysisRuleSet>$(SolutionDir)Francesco.Recipes.World.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.11" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0">
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -25,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>
@@ -37,5 +42,12 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include=".\stylecop.json" />
</ItemGroup>
<ItemGroup>
<Folder Include="Services\Category\" />
<Folder Include="Services\MediaFile\" />
<Folder Include="Services\Ingredient\" />
</ItemGroup>
</Project>
@@ -1,230 +0,0 @@
// <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("20250114131454_InitialMigration")]
partial class InitialMigration
{
/// <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");
});
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.Property<int>("Quantity")
.HasColumnType("int");
b.Property<Guid>("UnitId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("UnitId");
b.ToTable("Ingredients");
});
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<string>("Number")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<Guid>("RecipeId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("RecipeId");
b.ToTable("Instructions");
});
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<string>("Description")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Difficulty")
.IsRequired()
.HasColumnType("nvarchar(max)");
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.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<Guid>("RecipeId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("IngredientId");
b.HasIndex("RecipeId");
b.ToTable("RecipeIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Unit");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit")
.WithMany("Recipes")
.HasForeignKey("UnitId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Unit");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
.WithMany("Instructions")
.HasForeignKey("RecipeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Recipe");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", null)
.WithMany("Recipes")
.HasForeignKey("CategoryId");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient")
.WithMany()
.HasForeignKey("IngredientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
.WithMany("RecipeIngredients")
.HasForeignKey("RecipeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ingredient");
b.Navigation("Recipe");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
{
b.Navigation("Recipes");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
{
b.Navigation("Instructions");
b.Navigation("RecipeIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
{
b.Navigation("Recipes");
});
#pragma warning restore 612, 618
}
}
}
@@ -1,184 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Francesco.Recipes.World.Migrations
{
/// <inheritdoc />
public partial class InitialMigration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
{
throw new ArgumentNullException(nameof(migrationBuilder));
}
migrationBuilder.CreateTable(
name: "Categories",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Categories", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Unit",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Unit", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Recipes",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
Description = table.Column<string>(type: "nvarchar(max)", nullable: false),
Difficulty = table.Column<string>(type: "nvarchar(max)", nullable: false),
Servings = table.Column<int>(type: "int", nullable: false),
PreparationTime = table.Column<TimeSpan>(type: "time", nullable: false),
CookingTime = table.Column<TimeSpan>(type: "time", nullable: false),
CategoryId = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Recipes", x => x.Id);
table.ForeignKey(
name: "FK_Recipes_Categories_CategoryId",
column: x => x.CategoryId,
principalTable: "Categories",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "Ingredients",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
UnitId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Quantity = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Ingredients", x => x.Id);
table.ForeignKey(
name: "FK_Ingredients_Unit_UnitId",
column: x => x.UnitId,
principalTable: "Unit",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Instructions",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Description = table.Column<string>(type: "nvarchar(max)", nullable: false),
Number = table.Column<string>(type: "nvarchar(max)", nullable: false),
RecipeId = table.Column<Guid>(type: "uniqueidentifier", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Instructions", x => x.Id);
table.ForeignKey(
name: "FK_Instructions_Recipes_RecipeId",
column: x => x.RecipeId,
principalTable: "Recipes",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "RecipeIngredients",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
RecipeId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
IngredientId = table.Column<Guid>(type: "uniqueidentifier", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_RecipeIngredients", x => x.Id);
table.ForeignKey(
name: "FK_RecipeIngredients_Ingredients_IngredientId",
column: x => x.IngredientId,
principalTable: "Ingredients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_RecipeIngredients_Recipes_RecipeId",
column: x => x.RecipeId,
principalTable: "Recipes",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Ingredients_UnitId",
table: "Ingredients",
column: "UnitId");
migrationBuilder.CreateIndex(
name: "IX_Instructions_RecipeId",
table: "Instructions",
column: "RecipeId");
migrationBuilder.CreateIndex(
name: "IX_RecipeIngredients_IngredientId",
table: "RecipeIngredients",
column: "IngredientId");
migrationBuilder.CreateIndex(
name: "IX_RecipeIngredients_RecipeId",
table: "RecipeIngredients",
column: "RecipeId");
migrationBuilder.CreateIndex(
name: "IX_Recipes_CategoryId",
table: "Recipes",
column: "CategoryId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
{
throw new ArgumentNullException(nameof(migrationBuilder));
}
migrationBuilder.DropTable(
name: "Instructions");
migrationBuilder.DropTable(
name: "RecipeIngredients");
migrationBuilder.DropTable(
name: "Ingredients");
migrationBuilder.DropTable(
name: "Recipes");
migrationBuilder.DropTable(
name: "Unit");
migrationBuilder.DropTable(
name: "Categories");
}
}
}
@@ -0,0 +1,513 @@
// <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("20250318150949_InitialMIgration")]
partial class InitialMIgration
{
/// <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.IngredientsShoppingList", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("IngredientId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("ShoppinglistId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("IngredientId");
b.HasIndex("ShoppinglistId");
b.ToTable("IngredientsShoppingLists");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Number")
.IsRequired()
.HasColumnType("nvarchar(max)");
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.IngredientsShoppingList", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient")
.WithMany("IngredientShoppingLists")
.HasForeignKey("IngredientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "Shoppinglist")
.WithMany("IngredientsShoppingLists")
.HasForeignKey("ShoppinglistId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ingredient");
b.Navigation("Shoppinglist");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
.WithMany("Instructions")
.HasForeignKey("RecipeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Recipe");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", "Instruction")
.WithMany("MediaFiles")
.HasForeignKey("InstructionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
.WithMany("MediaFiles")
.HasForeignKey("RecipeId");
b.Navigation("Instruction");
b.Navigation("Recipe");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", null)
.WithMany("Recipes")
.HasForeignKey("CategoryId");
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorit")
.WithMany("Recipe")
.HasForeignKey("FavoritId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Favorit");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient")
.WithMany("RecipeIngredients")
.HasForeignKey("IngredientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
.WithMany("RecipeIngredients")
.HasForeignKey("RecipeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit")
.WithMany("RecipeIngredient")
.HasForeignKey("UnitId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ingredient");
b.Navigation("Recipe");
b.Navigation("Unit");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
{
b.Navigation("Recipes");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b =>
{
b.Navigation("Recipe");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
{
b.Navigation("IngredientShoppingLists");
b.Navigation("RecipeIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
{
b.Navigation("MediaFiles");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
{
b.Navigation("Instructions");
b.Navigation("MediaFiles");
b.Navigation("RecipeIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
{
b.Navigation("IngredientsShoppingLists");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
{
b.Navigation("RecipeIngredient");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,346 @@
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace Francesco.Recipes.World.Migrations
{
using System;
using Microsoft.EntityFrameworkCore.Migrations;
/// <inheritdoc />
public partial class InitialMIgration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder is null)
{
throw new ArgumentNullException(nameof(migrationBuilder));
}
migrationBuilder.CreateTable(
name: "Categories",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
},
constraints: table =>
{
table.PrimaryKey("PK_Categories", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Favorits",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
},
constraints: table =>
{
table.PrimaryKey("PK_Favorits", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Ingredients",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
},
constraints: table =>
{
table.PrimaryKey("PK_Ingredients", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ShoppingLists",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
ModifiedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
},
constraints: table =>
{
table.PrimaryKey("PK_ShoppingLists", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Units",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
Symbol = table.Column<string>(type: "nvarchar(max)", nullable: false),
},
constraints: table =>
{
table.PrimaryKey("PK_Units", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Recipes",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
Description = table.Column<string>(type: "nvarchar(max)", nullable: true),
Difficulty = table.Column<int>(type: "int", nullable: false),
Servings = table.Column<int>(type: "int", nullable: false),
PreparationTime = table.Column<TimeSpan>(type: "time", nullable: false),
CookingTime = table.Column<TimeSpan>(type: "time", nullable: false),
IsFavorite = table.Column<bool>(type: "bit", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
ModifiedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
FavoritId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
CategoryId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
},
constraints: table =>
{
table.PrimaryKey("PK_Recipes", x => x.Id);
table.ForeignKey(
name: "FK_Recipes_Categories_CategoryId",
column: x => x.CategoryId,
principalTable: "Categories",
principalColumn: "Id");
table.ForeignKey(
name: "FK_Recipes_Favorits_FavoritId",
column: x => x.FavoritId,
principalTable: "Favorits",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "IngredientsShoppingLists",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ShoppinglistId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
IngredientId = 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.CreateTable(
name: "Instructions",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Description = table.Column<string>(type: "nvarchar(max)", nullable: false),
Number = table.Column<string>(type: "nvarchar(max)", nullable: false),
RecipeId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
},
constraints: table =>
{
table.PrimaryKey("PK_Instructions", x => x.Id);
table.ForeignKey(
name: "FK_Instructions_Recipes_RecipeId",
column: x => x.RecipeId,
principalTable: "Recipes",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "RecipeIngredients",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
RecipeId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
IngredientId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UnitId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Quantity = table.Column<int>(type: "int", nullable: false),
},
constraints: table =>
{
table.PrimaryKey("PK_RecipeIngredients", x => x.Id);
table.ForeignKey(
name: "FK_RecipeIngredients_Ingredients_IngredientId",
column: x => x.IngredientId,
principalTable: "Ingredients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_RecipeIngredients_Recipes_RecipeId",
column: x => x.RecipeId,
principalTable: "Recipes",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_RecipeIngredients_Units_UnitId",
column: x => x.UnitId,
principalTable: "Units",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "MediaFiles",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
FileName = table.Column<string>(type: "nvarchar(max)", nullable: true),
MimeType = table.Column<string>(type: "nvarchar(max)", nullable: true),
Data = table.Column<byte[]>(type: "varbinary(max)", nullable: true),
RecipeId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
InstructionId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
},
constraints: table =>
{
table.PrimaryKey("PK_MediaFiles", x => x.Id);
table.ForeignKey(
name: "FK_MediaFiles_Instructions_InstructionId",
column: x => x.InstructionId,
principalTable: "Instructions",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_MediaFiles_Recipes_RecipeId",
column: x => x.RecipeId,
principalTable: "Recipes",
principalColumn: "Id");
});
migrationBuilder.InsertData(
table: "Categories",
columns: new[] { "Id", "Name" },
values: new object[,]
{
{ new Guid("0a91a200-dc76-4e00-b38c-b38cab5b69d7"), "Getränke" },
{ new Guid("20585b74-4805-4aff-a6df-aa6b7af04ff1"), "Kuchen" },
{ new Guid("23b1c740-e427-44f9-a6ea-d33d3f30f05a"), "Marmeladen & Eingemachtes" },
{ new Guid("28e39168-701a-4084-81da-d96c987c462f"), "Beilagen & Salate" },
{ new Guid("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"), "Erste Gänge" },
{ new Guid("90deec39-dcd0-422d-9018-ac8389e332e1"), "Desserts & Süßspeisen" },
{ new Guid("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"), "Hauptgerichte" },
{ new Guid("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"), "Hefegebäck & Brot" },
{ new Guid("b248244f-f21c-4555-a14d-5dd49a2717cf"), "Vorspeisen & Snacks" },
{ new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"), "Soßen & Saucen" },
});
migrationBuilder.InsertData(
table: "Units",
columns: new[] { "Id", "Name", "Symbol" },
values: new object[,]
{
{ new Guid("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"), "stücke", "stk" },
{ new Guid("24a91b89-3389-4465-89e4-2f70d2ea6fd7"), "esslöffel", "EL" },
{ new Guid("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"), "teelöffel", "TL" },
{ new Guid("62eb2c11-8768-46fb-9db0-c77f940bb4aa"), "liter", "l" },
{ new Guid("66556e0e-eb2b-4bc3-9a56-135dd508ed09"), "zehe", "zehe" },
{ new Guid("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"), "gramm", "g" },
{ new Guid("7e3d1b86-ac48-45d6-814e-d3492a86db1d"), "kilogramm", "kg" },
{ new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"), "bund", "bund" },
{ new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"), "blatt", "blatt" },
{ new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"), "messerspitze", "msp" },
{ new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"), "stange", "stange" },
});
migrationBuilder.CreateIndex(
name: "IX_IngredientsShoppingLists_IngredientId",
table: "IngredientsShoppingLists",
column: "IngredientId");
migrationBuilder.CreateIndex(
name: "IX_IngredientsShoppingLists_ShoppinglistId",
table: "IngredientsShoppingLists",
column: "ShoppinglistId");
migrationBuilder.CreateIndex(
name: "IX_Instructions_RecipeId",
table: "Instructions",
column: "RecipeId");
migrationBuilder.CreateIndex(
name: "IX_MediaFiles_InstructionId",
table: "MediaFiles",
column: "InstructionId");
migrationBuilder.CreateIndex(
name: "IX_MediaFiles_RecipeId",
table: "MediaFiles",
column: "RecipeId");
migrationBuilder.CreateIndex(
name: "IX_RecipeIngredients_IngredientId",
table: "RecipeIngredients",
column: "IngredientId");
migrationBuilder.CreateIndex(
name: "IX_RecipeIngredients_RecipeId",
table: "RecipeIngredients",
column: "RecipeId");
migrationBuilder.CreateIndex(
name: "IX_RecipeIngredients_UnitId",
table: "RecipeIngredients",
column: "UnitId");
migrationBuilder.CreateIndex(
name: "IX_Recipes_CategoryId",
table: "Recipes",
column: "CategoryId");
migrationBuilder.CreateIndex(
name: "IX_Recipes_FavoritId",
table: "Recipes",
column: "FavoritId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
if (migrationBuilder is null)
{
throw new ArgumentNullException(nameof(migrationBuilder));
}
migrationBuilder.DropTable(
name: "IngredientsShoppingLists");
migrationBuilder.DropTable(
name: "MediaFiles");
migrationBuilder.DropTable(
name: "RecipeIngredients");
migrationBuilder.DropTable(
name: "ShoppingLists");
migrationBuilder.DropTable(
name: "Instructions");
migrationBuilder.DropTable(
name: "Ingredients");
migrationBuilder.DropTable(
name: "Units");
migrationBuilder.DropTable(
name: "Recipes");
migrationBuilder.DropTable(
name: "Categories");
migrationBuilder.DropTable(
name: "Favorits");
}
}
}
@@ -0,0 +1,518 @@
// <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("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");
}
}
}
@@ -0,0 +1,571 @@
// <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("20250430094852_MakeInstructionOptionalInMediaFile")]
partial class MakeInstructionOptionalInMediaFile
{
/// <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");
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("RecipeShoppingList")
.HasForeignKey("ShoppingListId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Recipe");
b.Navigation("ShoppingList");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
{
b.Navigation("Recipes");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b =>
{
b.Navigation("Recipe");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
{
b.Navigation("IngredientShoppingLists");
b.Navigation("RecipeIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
{
b.Navigation("MediaFiles");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
{
b.Navigation("Instructions");
b.Navigation("MediaFiles");
b.Navigation("RecipeIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
{
b.Navigation("SelectedIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
{
b.Navigation("RecipeShoppingList");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
{
b.Navigation("RecipeIngredient");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,69 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Francesco.Recipes.World.Migrations
{
/// <inheritdoc />
public partial class MakeInstructionOptionalInMediaFile : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder is null)
{
throw new ArgumentNullException(nameof(migrationBuilder));
}
migrationBuilder.DropForeignKey(
name: "FK_MediaFiles_Instructions_InstructionId",
table: "MediaFiles");
migrationBuilder.AlterColumn<Guid>(
name: "InstructionId",
table: "MediaFiles",
type: "uniqueidentifier",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uniqueidentifier");
migrationBuilder.AddForeignKey(
name: "FK_MediaFiles_Instructions_InstructionId",
table: "MediaFiles",
column: "InstructionId",
principalTable: "Instructions",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
if (migrationBuilder is null)
{
throw new ArgumentNullException(nameof(migrationBuilder));
}
migrationBuilder.DropForeignKey(
name: "FK_MediaFiles_Instructions_InstructionId",
table: "MediaFiles");
migrationBuilder.AlterColumn<Guid>(
name: "InstructionId",
table: "MediaFiles",
type: "uniqueidentifier",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
oldClrType: typeof(Guid),
oldType: "uniqueidentifier",
oldNullable: true);
migrationBuilder.AddForeignKey(
name: "FK_MediaFiles_Instructions_InstructionId",
table: "MediaFiles",
column: "InstructionId",
principalTable: "Instructions",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}
@@ -0,0 +1,571 @@
// <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("20250602133408_RenameFavoriteColumn")]
partial class RenameFavoriteColumn
{
/// <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>("FavoriteId")
.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("FavoriteId");
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");
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
.WithMany("MediaFiles")
.HasForeignKey("RecipeId");
b.Navigation("Instruction");
b.Navigation("Recipe");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", "Category")
.WithMany("Recipes")
.HasForeignKey("CategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorite")
.WithMany("Recipe")
.HasForeignKey("FavoriteId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Category");
b.Navigation("Favorite");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient")
.WithMany("RecipeIngredients")
.HasForeignKey("IngredientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
.WithMany("RecipeIngredients")
.HasForeignKey("RecipeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit")
.WithMany("RecipeIngredient")
.HasForeignKey("UnitId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ingredient");
b.Navigation("Recipe");
b.Navigation("Unit");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
.WithMany()
.HasForeignKey("RecipeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList")
.WithMany("RecipeShoppingList")
.HasForeignKey("ShoppingListId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Recipe");
b.Navigation("ShoppingList");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
{
b.Navigation("Recipes");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b =>
{
b.Navigation("Recipe");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
{
b.Navigation("IngredientShoppingLists");
b.Navigation("RecipeIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
{
b.Navigation("MediaFiles");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
{
b.Navigation("Instructions");
b.Navigation("MediaFiles");
b.Navigation("RecipeIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
{
b.Navigation("SelectedIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
{
b.Navigation("RecipeShoppingList");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
{
b.Navigation("RecipeIngredient");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,72 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Francesco.Recipes.World.Migrations
{
/// <inheritdoc />
public partial class RenameFavoriteColumn : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder is null)
{
throw new ArgumentNullException(nameof(migrationBuilder));
}
migrationBuilder.DropForeignKey(
name: "FK_Recipes_Favorits_FavoritId",
table: "Recipes");
migrationBuilder.RenameColumn(
name: "FavoritId",
table: "Recipes",
newName: "FavoriteId");
migrationBuilder.RenameIndex(
name: "IX_Recipes_FavoritId",
table: "Recipes",
newName: "IX_Recipes_FavoriteId");
migrationBuilder.AddForeignKey(
name: "FK_Recipes_Favorits_FavoriteId",
table: "Recipes",
column: "FavoriteId",
principalTable: "Favorits",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
if (migrationBuilder is null)
{
throw new ArgumentNullException(nameof(migrationBuilder));
}
migrationBuilder.DropForeignKey(
name: "FK_Recipes_Favorits_FavoriteId",
table: "Recipes");
migrationBuilder.RenameColumn(
name: "FavoriteId",
table: "Recipes",
newName: "FavoritId");
migrationBuilder.RenameIndex(
name: "IX_Recipes_FavoriteId",
table: "Recipes",
newName: "IX_Recipes_FavoritId");
migrationBuilder.AddForeignKey(
name: "FK_Recipes_Favorits_FavoritId",
table: "Recipes",
column: "FavoritId",
principalTable: "Favorits",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}
@@ -35,6 +35,72 @@ namespace Francesco.Recipes.World.Migrations
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 =>
@@ -47,17 +113,38 @@ namespace Francesco.Recipes.World.Migrations
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("Quantity")
.HasColumnType("int");
b.HasKey("Id");
b.Property<Guid>("UnitId")
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("UnitId");
b.HasIndex("IngredientId");
b.ToTable("Ingredients");
b.HasIndex("RecipeIngredientId");
b.HasIndex("RecipeShoppingListId");
b.ToTable("RecipeIngredientsShoppingLists");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
@@ -70,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");
@@ -84,25 +170,65 @@ namespace Francesco.Recipes.World.Migrations
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")
b.Property<Guid>("CategoryId")
.HasColumnType("uniqueidentifier");
b.Property<TimeSpan>("CookingTime")
.HasColumnType("time");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Difficulty")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("Difficulty")
.HasColumnType("int");
b.Property<Guid>("FavoriteId")
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsFavorite")
.HasColumnType("bit");
b.Property<DateTime?>("ModifiedAt")
.HasColumnType("datetime2");
b.Property<string>("Name")
.IsRequired()
@@ -118,6 +244,8 @@ namespace Francesco.Recipes.World.Migrations
b.HasIndex("CategoryId");
b.HasIndex("FavoriteId");
b.ToTable("Recipes");
});
@@ -130,18 +258,64 @@ namespace Francesco.Recipes.World.Migrations
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")
@@ -152,20 +326,104 @@ namespace Francesco.Recipes.World.Migrations
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Symbol")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Unit");
b.ToTable("Units");
b.HasData(
new
{
Id = new Guid("62eb2c11-8768-46fb-9db0-c77f940bb4aa"),
Name = "liter",
Symbol = "l"
},
new
{
Id = new Guid("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"),
Name = "gramm",
Symbol = "g"
},
new
{
Id = new Guid("7e3d1b86-ac48-45d6-814e-d3492a86db1d"),
Name = "kilogramm",
Symbol = "kg"
},
new
{
Id = new Guid("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"),
Name = "stücke",
Symbol = "stk"
},
new
{
Id = new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"),
Name = "blatt",
Symbol = "blatt"
},
new
{
Id = new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"),
Name = "messerspitze",
Symbol = "msp"
},
new
{
Id = new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"),
Name = "stange",
Symbol = "stange"
},
new
{
Id = new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"),
Name = "bund",
Symbol = "bund"
},
new
{
Id = new Guid("66556e0e-eb2b-4bc3-9a56-135dd508ed09"),
Name = "zehe",
Symbol = "zehe"
},
new
{
Id = new Guid("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"),
Name = "teelöffel",
Symbol = "TL"
},
new
{
Id = new Guid("24a91b89-3389-4465-89e4-2f70d2ea6fd7"),
Name = "esslöffel",
Symbol = "EL"
});
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit")
.WithMany("Recipes")
.HasForeignKey("UnitId")
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.Navigation("Unit");
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 =>
@@ -179,17 +437,44 @@ namespace Francesco.Recipes.World.Migrations
b.Navigation("Recipe");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", "Instruction")
.WithMany("MediaFiles")
.HasForeignKey("InstructionId");
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)
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", "Favorite")
.WithMany("Recipe")
.HasForeignKey("FavoriteId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Category");
b.Navigation("Favorite");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient")
.WithMany()
.WithMany("RecipeIngredients")
.HasForeignKey("IngredientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -200,9 +485,36 @@ namespace Francesco.Recipes.World.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit")
.WithMany("RecipeIngredient")
.HasForeignKey("UnitId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ingredient");
b.Navigation("Recipe");
b.Navigation("Unit");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
.WithMany()
.HasForeignKey("RecipeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList")
.WithMany("RecipeShoppingList")
.HasForeignKey("ShoppingListId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Recipe");
b.Navigation("ShoppingList");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
@@ -210,16 +522,45 @@ namespace Francesco.Recipes.World.Migrations
b.Navigation("Recipes");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b =>
{
b.Navigation("Instructions");
b.Navigation("Recipe");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
{
b.Navigation("IngredientShoppingLists");
b.Navigation("RecipeIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
{
b.Navigation("MediaFiles");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
{
b.Navigation("Instructions");
b.Navigation("MediaFiles");
b.Navigation("RecipeIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
{
b.Navigation("SelectedIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
{
b.Navigation("RecipeShoppingList");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
{
b.Navigation("Recipes");
b.Navigation("RecipeIngredient");
});
#pragma warning restore 612, 618
}
@@ -1,10 +1,13 @@
namespace Francesco.Recipes.World.Models.BackendModels.Category
{
using Francesco.Recipes.World.Models.BackendModels.Recipe;
public class Category
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Name { get; set; } = string.Empty;
public virtual ICollection<Recipe> Recipes { get; set; } = new List<Recipe>();
}
}
@@ -0,0 +1,13 @@
namespace Francesco.Recipes.World.Models.BackendModels.Favorit
{
using Francesco.Recipes.World.Models.BackendModels.Recipe;
public class Favorit
{
public Guid Id { get; set; }
public DateTime CreatedAt { get; set; }
public virtual ICollection<Recipe> Recipe { get; set; } = new List<Recipe>();
}
}
@@ -0,0 +1,9 @@
namespace Francesco.Recipes.World.Models.BackendModels
{
public interface ITimeStampedEntity
{
DateTime CreatedAt { get; set; }
DateTime? ModifiedAt { get; set; }
}
}
@@ -1,11 +1,16 @@
namespace Francesco.Recipes.World.Models.BackendModels.Ingredient
{
using Francesco.Recipes.World.Models.BackendModels.Unit;
using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList;
using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient;
public class Ingredient
{
public Guid Id { get; set; }
public string Name { get; set; }
public virtual Unit Unit { get; set; } = new();
public int Quantity { get; set; }
public string Name { get; set; } = string.Empty;
public virtual ICollection<RecipeIngredient> RecipeIngredients { get; set; } = new List<RecipeIngredient>();
public virtual ICollection<RecipeIngredientShoppingList> IngredientShoppingLists { get; set; } = new List<RecipeIngredientShoppingList>();
}
}
@@ -1,11 +1,18 @@
namespace Francesco.Recipes.World.Models.BackendModels.Instruction
{
using Francesco.Recipes.World.Models.BackendModels.MediaFile;
using Francesco.Recipes.World.Models.BackendModels.Recipe;
public class Instruction
{
public Guid Id { get; set; }
public string Description { get; set; }
public string Number { get; set; }
public virtual Recipe Recipe { get; set; } = new();
public string Description { get; set; } = string.Empty;
public int Number { get; set; }
public virtual Recipe Recipe { get; set; } = new ();
public virtual ICollection<MediaFile> MediaFiles { get; set; } = new List<MediaFile>();
}
}
@@ -0,0 +1,20 @@
namespace Francesco.Recipes.World.Models.BackendModels.MediaFile
{
using Francesco.Recipes.World.Models.BackendModels.Instruction;
using Francesco.Recipes.World.Models.BackendModels.Recipe;
public class MediaFile
{
public Guid Id { get; set; } = Guid.NewGuid();
public string? FileName { get; set; }
public string? MimeType { get; set; }
public byte[]? Data { get; set; }
public virtual Recipe? Recipe { get; set; } = null;
public virtual Instruction? Instruction { get; set; } = null;
}
}
@@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations;
namespace Francesco.Recipes.World.Models.BackendModels.Recipe
{
public enum Difficulty
{
[Display(Name = "Sehr einfach")]
VeryEasy = 0,
[Display(Name = "Einfach")]
Easy = 1,
[Display(Name = "Mittel")]
Medium = 2,
[Display(Name = "Schwer")]
Hard = 3,
[Display(Name = "Experte")]
Expert = 4,
}
}
@@ -1,18 +1,40 @@
namespace Francesco.Recipes.World.Models.BackendModels.Recipe
{
using Francesco.Recipes.World.Models.BackendModels.Instruction;
using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient;
namespace Francesco.Recipes.World.Models.BackendModels.Recipe;
public class Recipe
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Difficulty { get; set; }
public int Servings { get; set; }
public TimeSpan PreparationTime { get; set; }
public TimeSpan CookingTime { get; set; }
public virtual ICollection <RecipeIngredient> RecipeIngredients { get; set; } = new List<RecipeIngredient>();
public virtual ICollection <Instruction> Instructions { get; set; } = new List<Instruction>();
}
using Francesco.Recipes.World.Models.BackendModels.Category;
using Francesco.Recipes.World.Models.BackendModels.Favorit;
using Francesco.Recipes.World.Models.BackendModels.Instruction;
using Francesco.Recipes.World.Models.BackendModels.MediaFile;
using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient;
public class Recipe : ITimeStampedEntity
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public Difficulty Difficulty { get; set; }
public int Servings { get; set; }
public TimeSpan PreparationTime { get; set; }
public TimeSpan CookingTime { get; set; }
public bool IsFavorite { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? ModifiedAt { get; set; }
public virtual ICollection<RecipeIngredient> RecipeIngredients { get; set; } = new List<RecipeIngredient>();
public virtual ICollection<Instruction> Instructions { get; set; } = new List<Instruction>();
public virtual ICollection<MediaFile> MediaFiles { get; set; } = new List<MediaFile>();
public virtual Favorit Favorite { get; set; } = new ();
public virtual Category Category { get; set; } = new ();
}
@@ -2,10 +2,18 @@
{
using Francesco.Recipes.World.Models.BackendModels.Ingredient;
using Francesco.Recipes.World.Models.BackendModels.Recipe;
using Francesco.Recipes.World.Models.BackendModels.Unit;
public class RecipeIngredient
{
public Guid Id { get; set; }
public virtual Recipe Recipe { get; set; } = new ();
public virtual Ingredient Ingredient { get; set; } = new ();
public virtual Unit Unit { get; set; } = new ();
public int Quantity { get; set; }
}
}
@@ -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>();
}
}
@@ -0,0 +1,15 @@
namespace Francesco.Recipes.World.Models.BackendModels.Shoppinglist
{
using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList;
public class ShoppingList : ITimeStampedEntity
{
public Guid Id { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? ModifiedAt { get; set; }
public virtual ICollection<RecipeShoppingList> RecipeShoppingList { get; set; } = new List<RecipeShoppingList>();
}
}
@@ -1,11 +1,15 @@
namespace Francesco.Recipes.World.Models.BackendModels.Unit
{
using Francesco.Recipes.World.Models.BackendModels.Ingredient;
using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient;
public class Unit
{
public Guid Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Ingredient> Recipes { get; set; } = new List<Ingredient>();
public string Name { get; set; } = string.Empty;
public string Symbol { get; set; } = string.Empty;
public virtual ICollection<RecipeIngredient> RecipeIngredient { get; set; } = new List<RecipeIngredient>();
}
}
@@ -0,0 +1,9 @@
namespace Francesco.Recipes.World.Models
{
public class CreateOrAddIngredientRequestModel
{
public Guid RecipeId { get; set; }
public List<Guid> IngredientIds { get; set; } = new ();
}
}
@@ -0,0 +1,39 @@
using Francesco.Recipes.World.Models.BackendModels.Recipe;
namespace Francesco.Recipes.World.Models
{
public class CreateRecipeViewModel
{
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public Difficulty Difficulty { get; set; }
public int Servings { get; set; }
public TimeSpan PreparationTime { get; set; }
public TimeSpan CookingTime { get; set; }
public int PrepHours { get; set; }
public int PrepMinutes { get; set; }
public int CookHours { get; set; }
public int CookMinutes { get; set; }
public Guid CategoryId { get; set; }
public string? CategoryName { get; set; }
public IFormFile? Photo { get; set; }
public IFormFile? Video { get; set; }
public IngredientViewModel? IngredientViewModel { get; set; }
public InstructionViewModel? InstructionViewModel { get; set; }
}
}
@@ -0,0 +1,16 @@
using Francesco.Recipes.World.Constants;
using Francesco.Recipes.World.Models.BackendModels.Recipe;
namespace Francesco.Recipes.World.Models
{
public class FavoriteViewModel
{
public IEnumerable<Recipe> FavoriteRecipes { get; set; } = new List<Recipe>();
public string SortOrder { get; set; } = SortOrders.Newest;
public bool HasFavorites => FavoriteRecipes.Any();
public string SortOrderDisplayText => SortOrder == SortOrders.Oldest ? "Älteste Favorits" : "Neueste Favorits";
}
}
@@ -0,0 +1,14 @@
namespace Francesco.Recipes.World.Models
{
using Francesco.Recipes.World.Models.BackendModels.Ingredient;
using Francesco.Recipes.World.Models.BackendModels.Unit;
public class IngredientViewModel
{
public Guid RecipeId { get; set; }
public List<Ingredient> Ingredients { get; set; } = new List<Ingredient>();
public List<Unit> Units { get; set; } = new List<Unit>();
}
}
@@ -0,0 +1,13 @@
using Francesco.Recipes.World.Models.BackendModels.Instruction;
namespace Francesco.Recipes.World.Models
{
public class InstructionViewModel
{
public Guid RecipeId { get; set; }
public string Description { get; set; } = string.Empty;
public List<Instruction> Instructions { get; set; } = new List<Instruction>();
}
}
@@ -0,0 +1,21 @@
namespace Francesco.Recipes.World.Models
{
public class SearchViewModel
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public bool IsFavorite { get; set; }
public byte[]? ImageData { get; set; }
public string? MimeType { get; set; }
public List<string> Ingredients { get; set; } = new ();
public TimeSpan TotalTime { get; set; }
}
}
@@ -0,0 +1,13 @@
using Francesco.Recipes.World.Models.BackendModels.Recipe;
namespace Francesco.Recipes.World.Models
{
public class ShoppingListDetailsViewModel
{
public int RecipeCount { get; set; }
public List<Recipe> RecipesInAnyShoppingList { get; set; } = new List<Recipe>();
public Dictionary<Guid, Guid> RecipeIngredientToShoppingListMap { get; set; } = new Dictionary<Guid, Guid>();
}
}
+36 -11
View File
@@ -1,24 +1,49 @@
using Francesco.Recipes.World.Data;
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 Francesco.Recipes.World.Services.Instruction;
using Microsoft.EntityFrameworkCore;
using Serilog;
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
var configuration = builder.Configuration;
var connectionString = builder.Configuration.GetConnectionString("FrancescosRecipesWorldDbContextConnection")
?? throw new InvalidOperationException("Connection string 'FrancescosRecipesWorldDbContextConnection' not found.");
services.AddDbContext<FrancescosRecipesWorldDbContext>(options =>
options.UseSqlServer(connectionString));
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<FrancescosRecipesWorldDbContext>();
services.AddDbContext<FrancescosRecipesWorldDbContext>(options =>
options.UseSqlServer(connectionString, sqlOptions =>
sqlOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)));
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddScoped<IRecipeRepository, RecipeRepository>();
builder.Services.AddScoped<IIngredientRepository, IngredientRepository>();
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>();
builder.Services.AddScoped<IInstructionService, InstructionService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
@@ -30,18 +55,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,89 @@
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;
using Francesco.Recipes.World.Models.BackendModels.Category;
using Francesco.Recipes.World.Models.BackendModels.Recipe;
using Francesco.Recipes.World.Views.Category;
using Microsoft.EntityFrameworkCore;
public class CategoryRepository : ICategoryRepository
{
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()
{
var categories = await _context.Categories
.Include(c => c.Recipes.OrderByDescending(r => r.CreatedAt).Take(3))
.ThenInclude(r => r.MediaFiles.Take(2))
.AsSplitQuery()
.ToListAsync();
return categories;
}
public async Task<IEnumerable<CategoryRecipesViewModel>> GetAllCategoriesWithRecipesViewModelAsync()
{
var categories = await _context.Categories
.Select(c => new CategoryRecipesViewModel
{
Category = c,
Recipes = c.Recipes
.OrderByDescending(r => r.CreatedAt)
.Take(3)
.Select(r => new RecipeCardViewModel
{
Id = r.Id,
Name = r.Name,
CookingTime = r.CookingTime,
IsFavorite = r.IsFavorite,
ImageData = r.MediaFiles
.OrderBy(m => m.Id)
.Select(m => m.Data)
.FirstOrDefault(),
MimeType = r.MediaFiles
.OrderBy(m => m.Id)
.Select(m => m.MimeType)
.FirstOrDefault(),
}),
})
.AsSplitQuery()
.ToListAsync();
return categories;
}
}
}
@@ -0,0 +1,22 @@
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;
using Francesco.Recipes.World.Views.Category;
public interface ICategoryRepository
{
Task<Category> GetCategoryByIdAsync(Guid categoryId);
Task<IEnumerable<Category>> GetAllCategoriesAsync();
Task<IEnumerable<Recipe>> GetRecipesByCategoryAsync(Guid categoryId);
Task<IEnumerable<Category>> GetAllCategoriesWithRecipesAsync();
Task<IEnumerable<CategoryRecipesViewModel>> GetAllCategoriesWithRecipesViewModelAsync();
}
}
@@ -0,0 +1,72 @@
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)
.Include(r => r.Favorite)
.Include(r => r.MediaFiles)
.Take(6)
.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
.Include(r => r.Favorite)
.FirstOrDefaultAsync(r => r.Id == recipeId);
if (recipe == null)
{
throw new InvalidOperationException("Recipe not found.");
}
if (recipe.IsFavorite)
{
throw new InvalidOperationException("Recipe is already a favorite.");
}
recipe.IsFavorite = true;
if (recipe.Favorite == null || recipe.Favorite.Id == Guid.Empty)
{
recipe.Favorite = new Models.BackendModels.Favorit.Favorit
{
Id = Guid.NewGuid(),
CreatedAt = DateTime.Now,
};
}
await _context.SaveChangesAsync();
}
public async Task RemoveFavoriteAsync(Guid recipeId)
{
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,19 @@
namespace Francesco.Recipes.World.Repositories.Instruction
{
using Francesco.Recipes.World.Models.BackendModels.Instruction;
public interface IInstructionRepository
{
Task<Instruction> GetInstructionAsync(Guid instructionId);
Task<Instruction> CreateInstructionAsync(Guid recipeId, string description, IFormFile? photo);
Task RemoveInstructionFromRecipeAsync(Guid recipeId, Guid instructionId);
Task SwapInstructionNumbersAsync(Instruction a, Instruction b);
Task<List<Instruction>> GetInstructionsOfRecipeAsync(Guid recipeId);
Task RenumberInstructionsAsync(Guid recipeId);
}
}
@@ -0,0 +1,160 @@
namespace Francesco.Recipes.World.Repositories.Instruction
{
using Francesco.Recipes.World.Data;
using Francesco.Recipes.World.Models.BackendModels.Instruction;
using Francesco.Recipes.World.Models.BackendModels.MediaFile;
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> CreateInstructionAsync(Guid recipeId, string description, IFormFile? photo)
{
if (string.IsNullOrWhiteSpace(description))
{
throw new ArgumentException("Description cannot be empty", nameof(description));
}
var recipe = await _context.Recipes
.Include(r => r.Instructions)
.FirstOrDefaultAsync(r => r.Id == recipeId);
if (recipe == null)
{
throw new ArgumentException("Recipe not found.", nameof(recipeId));
}
var nextNumber = 1;
if (recipe.Instructions != null && recipe.Instructions.Any())
{
nextNumber = recipe.Instructions.Max(i => i.Number) + 1;
}
var newInstruction = new Instruction
{
Id = Guid.NewGuid(),
Description = description,
Number = nextNumber,
Recipe = recipe,
};
_context.Instructions.Add(newInstruction);
await _context.SaveChangesAsync();
if (photo != null && photo.Length > 0)
{
using var memoryStream = new MemoryStream();
await photo.CopyToAsync(memoryStream);
var instructionImage = new MediaFile
{
FileName = photo.FileName,
MimeType = photo.ContentType,
Data = memoryStream.ToArray(),
Instruction = newInstruction,
Recipe = null,
};
_context.Add(instructionImage);
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)
{
await _context.Entry(instructionToRemove)
.Collection(i => i.MediaFiles)
.LoadAsync();
if (instructionToRemove.MediaFiles != null && instructionToRemove.MediaFiles.Any())
{
_context.MediaFiles.RemoveRange(instructionToRemove.MediaFiles);
}
recipe.Instructions?.Remove(instructionToRemove);
await _context.SaveChangesAsync();
await RenumberInstructionsAsync(recipeId);
}
}
public async Task<List<Instruction>> GetInstructionsOfRecipeAsync(Guid recipeId)
{
var instructions = await _context.Instructions
.Include(i => i.MediaFiles)
.Where(i => i.Recipe.Id == recipeId)
.OrderBy(i => i.Number)
.ToListAsync();
if (!instructions.Any())
{
throw new InvalidDataException($"No instructions found for Recipe ID {recipeId}.");
}
return instructions;
}
public async Task RenumberInstructionsAsync(Guid recipeId)
{
var instructions = await _context.Instructions
.Where(i => i.Recipe.Id == recipeId)
.OrderBy(i => i.Number)
.ToListAsync();
for (var i = 0; i < instructions.Count; i++)
{
instructions[i].Number = i + 1;
}
await _context.SaveChangesAsync();
}
public async Task SwapInstructionNumbersAsync(Instruction a, Instruction b)
{
if (a == null)
{
throw new ArgumentNullException(nameof(a), "Instruction 'a' cannot be null.");
}
if (b == null)
{
throw new ArgumentNullException(nameof(b), "Instruction 'b' cannot be null.");
}
var temp = a.Number;
a.Number = b.Number;
b.Number = temp;
await _context.SaveChangesAsync();
}
}
}
@@ -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,136 @@
namespace Francesco.Recipes.World.Repositories.MediaFile
{
using Francesco.Recipes.World.Constants;
using Francesco.Recipes.World.Data;
using Francesco.Recipes.World.Models.BackendModels.MediaFile;
using Francesco.Recipes.World.Models.BackendModels.Recipe;
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,
Recipe = null,
};
_context.Add(instructionImage);
await _context.SaveChangesAsync();
}
}
public async Task UploadRecipeMediaAsync(Guid recipeId, IFormFile mediaFile)
{
if (mediaFile == null || mediaFile.Length == 0)
{
return;
}
try
{
var recipe = await _recipeRepository.GetRecipeAsync(recipeId);
var isImage = mediaFile.ContentType.StartsWith(ContentType.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, ContentType.Image);
}
else
{
await RemoveExistingMediaAsync(recipe, "video/");
}
using var memoryStream = new MemoryStream();
await mediaFile.CopyToAsync(memoryStream);
var newMedia = new MediaFile
{
Id = Guid.NewGuid(),
FileName = mediaFile.FileName,
MimeType = mediaFile.ContentType,
Data = memoryStream.ToArray(),
Recipe = recipe,
Instruction = null,
};
_context.MediaFiles.Add(newMedia);
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
throw new InvalidOperationException("An error occurred while uploading the media file.", ex);
}
}
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,25 @@
namespace Francesco.Recipes.World.Repositories.Recipe
{
using Francesco.Recipes.World.Models;
using Francesco.Recipes.World.Models.BackendModels.Category;
using Francesco.Recipes.World.Models.BackendModels.Recipe;
public interface IRecipeRepository
{
Task<Recipe> GetRecipeAsync(Guid recipeId);
Task<Recipe?> GetRecipeByIdAsync(Guid id);
Task CreateRecipeIngredientAsync(Guid recipeId, string ingredientName, int quantity, Guid unitId);
Task RemoveIngredientFromRecipeAsync(Guid recipeId, Guid ingredientId);
Task<IReadOnlyCollection<Recipe>> GetRecipesByDifficultyAsync(Difficulty? difficulty);
Task<Recipe> CreateRecipeForCategoryAsync(Category category, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime);
Task<bool> DeleteRecipeAsync(Guid recipeId);
Task<IEnumerable<SearchViewModel>> SearchInRecipesAndIngredients(string searchTerm);
}
}
@@ -0,0 +1,273 @@
namespace Francesco.Recipes.World.Repositories.Recipe
{
using System.Linq.Expressions;
using Francesco.Recipes.World.Constants;
using Francesco.Recipes.World.Data;
using Francesco.Recipes.World.Models;
using Francesco.Recipes.World.Models.BackendModels.Category;
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)
.ThenInclude(i => i.MediaFiles)
.FirstOrDefaultAsync(r => r.Id == recipeId);
}
public async Task CreateRecipeIngredientAsync(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<SearchViewModel>> SearchInRecipesAndIngredients(string searchTerm)
{
try
{
if (string.IsNullOrWhiteSpace(searchTerm))
{
return await _context.Recipes
.OrderByDescending(r => r.CreatedAt)
.Take(20)
.Select(SearchViewModelSelector())
.ToListAsync();
}
var normalizedSearchTerm = searchTerm.ToLower();
return await ApplyRecipeSearchFilter(_context.Recipes, normalizedSearchTerm)
.OrderByDescending(r => r.CreatedAt)
.Take(100)
.Select(SearchViewModelSelector())
.ToListAsync();
}
catch (Exception ex)
{
Console.WriteLine($"Error in SearchInRecipesAndIngredientsOptimized: {ex.Message}");
return new List<SearchViewModel>();
}
}
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();
}
public async Task<bool> DeleteRecipeAsync(Guid recipeId)
{
var recipe = await GetRecipeByIdAsync(recipeId);
if (recipe == null)
{
return false;
}
if (recipe.RecipeIngredients != null && recipe.RecipeIngredients.Any())
{
_context.RecipeIngredients.RemoveRange(recipe.RecipeIngredients);
}
if (recipe.Instructions != null && recipe.Instructions.Any())
{
foreach (var instruction in recipe.Instructions)
{
if (instruction.MediaFiles != null && instruction.MediaFiles.Any())
{
_context.MediaFiles.RemoveRange(instruction.MediaFiles);
}
}
_context.Instructions.RemoveRange(recipe.Instructions);
}
if (recipe.MediaFiles != null && recipe.MediaFiles.Any())
{
_context.MediaFiles.RemoveRange(recipe.MediaFiles);
}
if (recipe.Favorite != null && recipe.Favorite.Id != Guid.Empty)
{
_context.Remove(recipe.Favorite);
}
_context.Recipes.Remove(recipe);
await _context.SaveChangesAsync();
return true;
}
private static Expression<Func<Recipe, SearchViewModel>> SearchViewModelSelector()
{
return r => new SearchViewModel
{
Id = r.Id,
Name = r.Name,
Description = r.Description,
IsFavorite = r.IsFavorite,
ImageData = r.MediaFiles
.Where(m => m.MimeType != null && m.MimeType.StartsWith(ContentType.Image))
.Select(m => m.Data)
.FirstOrDefault(),
MimeType = r.MediaFiles
.Where(m => m.MimeType != null && m.MimeType.StartsWith(ContentType.Image))
.Select(m => m.MimeType)
.FirstOrDefault(),
Ingredients = r.RecipeIngredients.Select(ri => ri.Ingredient.Name).ToList(),
TotalTime = r.PreparationTime.Add(r.CookingTime),
};
}
private static IQueryable<Recipe> ApplyRecipeSearchFilter(IQueryable<Recipe> query, string normalizedSearchTerm)
{
return query.Where(r =>
EF.Functions.Like(r.Name.ToLower(), $"%{normalizedSearchTerm}%") ||
(r.Description != null && EF.Functions.Like(r.Description.ToLower(), $"%{normalizedSearchTerm}%")) ||
r.RecipeIngredients.Any(ri => EF.Functions.Like(ri.Ingredient.Name.ToLower(), $"%{normalizedSearchTerm}%")));
}
}
}
@@ -0,0 +1,19 @@
namespace Francesco.Recipes.World.Repositories.ShoppingList
{
using Francesco.Recipes.World.Models.BackendModels.Shoppinglist;
public interface IShoppingListRepository
{
Task<ShoppingList> AddIngredientsToShoppingListAsync(Guid recipeId, List<Guid> ingredientIds);
Task RemoveRecipeIfEmptyAsync(Guid shoppingListRecipeId);
Task<int> CountAllRecipeShoppinglistsAsync();
Task<IList<ShoppingList>> GetAllShoppingListsAsync();
Task RemoveIngredientsFromShoppingListAsync(List<Guid> recipeIngredientShoppngListIds);
Task RemoveRecipeFromShoppingListAsync(Guid recipeShoppingListId);
}
}
@@ -0,0 +1,235 @@
namespace Francesco.Recipes.World.Repositories.ShoppingList
{
using Francesco.Recipes.World.Data;
using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList;
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<IList<ShoppingList>> GetAllShoppingListsAsync()
{
return await GetShoppingListQuery()
.OrderByDescending(sl => sl.CreatedAt)
.ToListAsync();
}
public async Task RemoveRecipeIfEmptyAsync(Guid shoppingListRecipeId)
{
var recipeEntry = await _context.RecipeShoppingLists
.Include(r => r.SelectedIngredients)
.Include(r => r.ShoppingList)
.ThenInclude(sl => sl.RecipeShoppingList)
.FirstOrDefaultAsync(r => r.Id == shoppingListRecipeId);
if (recipeEntry != null && !recipeEntry.SelectedIngredients.Any())
{
var shoppingList = recipeEntry.ShoppingList;
_context.RecipeShoppingLists.Remove(recipeEntry);
await _context.SaveChangesAsync();
if (shoppingList != null && (shoppingList.RecipeShoppingList == null || !shoppingList.RecipeShoppingList.Any()))
{
_context.ShoppingLists.Remove(shoppingList);
await _context.SaveChangesAsync();
}
}
}
public async Task RemoveRecipeFromShoppingListAsync(Guid recipeShoppingListId)
{
var recipeEntry = await _context.RecipeShoppingLists
.Include(r => r.SelectedIngredients)
.Include(r => r.ShoppingList)
.ThenInclude(sl => sl.RecipeShoppingList)
.FirstOrDefaultAsync(r => r.Id == recipeShoppingListId);
if (recipeEntry != null)
{
var shoppingList = recipeEntry.ShoppingList;
_context.RecipeIngredientsShoppingLists.RemoveRange(recipeEntry.SelectedIngredients);
_context.RecipeShoppingLists.Remove(recipeEntry);
await _context.SaveChangesAsync();
if (shoppingList != null)
{
await _context.Entry(shoppingList).Collection(sl => sl.RecipeShoppingList).LoadAsync();
if (!shoppingList.RecipeShoppingList.Any())
{
_context.ShoppingLists.Remove(shoppingList);
await _context.SaveChangesAsync();
}
}
}
}
public async Task<int> CountAllRecipeShoppinglistsAsync()
{
return await _context.RecipeShoppingLists
.CountAsync();
}
public async Task RemoveIngredientsFromShoppingListAsync(List<Guid> recipeIngredientShoppingListIds)
{
if (recipeIngredientShoppingListIds == null || !recipeIngredientShoppingListIds.Any())
{
throw new ArgumentNullException(nameof(recipeIngredientShoppingListIds));
}
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
var affectedRecipeShoppingListIds = await _context.RecipeIngredientsShoppingLists
.Where(risl => recipeIngredientShoppingListIds.Contains(risl.Id))
.Select(risl => risl.RecipeShoppingList.Id)
.Distinct()
.ToListAsync();
foreach (var id in recipeIngredientShoppingListIds)
{
var entry = await _context.RecipeIngredientsShoppingLists.FindAsync(id);
if (entry != null)
{
_context.RecipeIngredientsShoppingLists.Remove(entry);
}
}
await _context.SaveChangesAsync();
foreach (var recipeShoppingListId in affectedRecipeShoppingListIds)
{
await RemoveRecipeIfEmptyAsync(recipeShoppingListId);
}
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
private IQueryable<ShoppingList> GetShoppingListQuery()
{
return _context.ShoppingLists
.Include(sl => sl.RecipeShoppingList)
.ThenInclude(rsl => rsl.Recipe)
.ThenInclude(r => r.MediaFiles)
.Include(sl => sl.RecipeShoppingList)
.ThenInclude(rsl => rsl.SelectedIngredients)
.ThenInclude(si => si.RecipeIngredient)
.ThenInclude(ri => ri.Ingredient)
.Include(sl => sl.RecipeShoppingList)
.ThenInclude(rsl => rsl.SelectedIngredients)
.ThenInclude(si => si.RecipeIngredient)
.ThenInclude(ri => ri.Unit);
}
}
}
@@ -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<List<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<List<Unit>> GetAllUnitsAsync()
{
return await _context.Units.ToListAsync();
}
}
}
@@ -0,0 +1,13 @@
namespace Francesco.Recipes.World.Services.Instruction
{
using Francesco.Recipes.World.Models.BackendModels.Instruction;
public interface IInstructionService
{
Task MoveInstructionUpAsync(Guid recipeId, Guid instructionId);
Task MoveInstructionDownAsync(Guid recipeId, Guid instructionId);
Task<List<Instruction>> GetSortedInstructionsAsync(Guid recipeId);
}
}
@@ -0,0 +1,58 @@
namespace Francesco.Recipes.World.Services.Instruction
{
using Francesco.Recipes.World.Models.BackendModels.Instruction;
using Francesco.Recipes.World.Repositories.Instruction;
public class InstructionService : IInstructionService
{
private readonly IInstructionRepository _instructionRepository;
public InstructionService(IInstructionRepository instructionRepository)
{
_instructionRepository = instructionRepository;
}
public Task MoveInstructionUpAsync(Guid recipeId, Guid instructionId)
=> MoveInstructionAsync(recipeId, instructionId, moveUp: true);
public Task MoveInstructionDownAsync(Guid recipeId, Guid instructionId)
=> MoveInstructionAsync(recipeId, instructionId, moveUp: false);
public async Task<List<Instruction>> GetSortedInstructionsAsync(Guid recipeId)
{
var instructions = await _instructionRepository.GetInstructionsOfRecipeAsync(recipeId);
return instructions.OrderBy(i => i.Number).ToList();
}
private async Task MoveInstructionAsync(Guid recipeId, Guid instructionId, bool moveUp)
{
var instructions = await _instructionRepository.GetInstructionsOfRecipeAsync(recipeId);
var instruction = instructions.FirstOrDefault(i => i.Id == instructionId);
if (instruction == null)
{
throw new InvalidDataException($"Instruction with ID {instructionId} not found.");
}
var minStep = 1;
var maxStep = instructions.Max(i => i.Number);
if ((moveUp && instruction.Number == minStep) || (!moveUp && instruction.Number >= maxStep))
{
return;
}
// Instructions are ordered by ascending numbers (1, 2, 3, ...).
// Moving up means swapping with the instruction that has one number less (Number - 1).
// Moving down means swapping with the instruction that has one number more (Number + 1).
var targetNumber = moveUp ? instruction.Number - 1 : instruction.Number + 1;
var neighbor = instructions.FirstOrDefault(i => i.Number == targetNumber);
if (neighbor != null)
{
await _instructionRepository.SwapInstructionNumbersAsync(instruction, neighbor);
}
}
}
}
@@ -0,0 +1,12 @@
namespace Francesco.Recipes.World.Views.Category
{
using Francesco.Recipes.World.Models;
using Francesco.Recipes.World.Models.BackendModels.Category;
public class CategoryRecipesViewModel
{
public Category Category { get; set; } = new ();
public IEnumerable<RecipeCardViewModel> Recipes { get; set; } = new List<RecipeCardViewModel>();
}
}
@@ -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>
@@ -0,0 +1,77 @@
@model Francesco.Recipes.World.Models.FavoriteViewModel
@{
ViewData["Title"] = "Favoriten";
}
<h2 class="mb-4">Favoriten</h2>
<div class="mb-4 text-end">
<div class="dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="sortDropdown"
data-bs-toggle="dropdown" aria-expanded="false">
@Model.SortOrderDisplayText
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="sortDropdown">
<li>
<a class="dropdown-item @(Model.SortOrder == "newest" ? "active" : "")"
href="@Url.Action("Index", new { sortOrder = "newest" })">Neueste Favorits</a>
</li>
<li>
<a class="dropdown-item @(Model.SortOrder == "oldest" ? "active" : "")"
href="@Url.Action("Index", new { sortOrder = "oldest" })">Älteste Favorits</a>
</li>
</ul>
</div>
</div>
@if (!Model.HasFavorites)
{
<div class="alert alert-info">
Keine Favoriten vorhanden. Füge Rezepte zu deinen Favoriten hinzu, indem du auf den Stern klickst.
</div>
}
else
{
<div class="row row-cols-1 row-cols-md-3 g-4">
@foreach (var recipe in Model.FavoriteRecipes)
{
<div class="col">
<div class="card h-100">
@{
var mediaFile = recipe.MediaFiles?.FirstOrDefault(m => m.MimeType != null && m.MimeType.StartsWith("image/"));
var imageData = mediaFile?.Data;
var mimeType = mediaFile?.MimeType;
}
<div class="card-img-top text-center bg-light" style="height:200px; display:flex; align-items:center; justify-content:center;">
@if (imageData != null && mimeType != null)
{
<img src="data:@mimeType;base64,@Convert.ToBase64String(imageData)"
alt="@recipe.Name" class="img-fluid" style="max-height:100%; max-width:100%; object-fit:contain;" />
}
else
{
<div class="text-secondary">Kein Bild</div>
}
</div>
<div class="card-body">
<h5 class="card-title">@recipe.Name</h5>
<div class="d-flex align-items-center mt-3">
<span class="me-3">
@await Html.PartialAsync("_FavoriteButton", recipe)
</span>
<span class="text-muted">
<i class="bi bi-clock"></i> @(recipe.PreparationTime.TotalMinutes + recipe.CookingTime.TotalMinutes)min
</span>
</div>
</div>
<div class="card-footer bg-white">
<a href="@Url.Action("Details", "Recipe", new { recipeId = recipe.Id })"
class="btn btn-outline-primary btn-sm w-100">Details</a>
</div>
</div>
</div>
}
</div>
}
@@ -1,8 +1,81 @@
@{
ViewData["Title"] = "Home Page";
@model IEnumerable<Francesco.Recipes.World.Views.Category.CategoryRecipesViewModel>
@Html.AntiForgeryToken()
<div class="welcome-banner text-center mb-4">
<img src="/images/banner2.jpg" alt="Willkommen" class="img-fluid" />
<h1 class="mt-3">Willkommen in der Rezept-App</h1>
</div>
<form hx-get="/Home/Search"
hx-target="#search-results"
hx-trigger="keyup changed delay:300ms"
onsubmit="return false;"
class="mb-4">
<input type="text" name="term" class="form-control" placeholder="Suchen nach Rezepten oder Zutaten..." autocomplete="off" />
</form>
<div id="search-results" class="mt-4"></div>
@foreach (var category in Model)
{
<div class="category-section mb-5" id="category-@category.Category.Id">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2>@category.Category.Name</h2>
<a href="/Category/Details/@category.Category.Id"
class="btn btn-link"
hx-get="/Category/Details/@category.Category.Id"
hx-target="#category-@category.Category.Id"
hx-swap="outerHTML">Alle @category.Category.Name-Rezepte anzeigen</a>
</div>
<div class="row">
@foreach (var recipe in category.Recipes)
{
var imageData = recipe.ImageData;
var mimeType = recipe.MimeType;
<div class="col-md-3 mb-4">
<div class="card h-100">
<div class="recipe-image">
@if (imageData != null && mimeType != null)
{
<img src="data:@mimeType;base64,@Convert.ToBase64String(imageData)" alt="@recipe.Name" class="card-img-top" />
}
else
{
<img src="/images/placeholder.png" alt="@recipe.Name" class="card-img-top" />
}
</div>
<div class="card-body d-flex flex-column">
<h5 class="card-title">@recipe.Name</h5>
<p class="card-text text-muted mb-2">@recipe.CookingTime</p>
<div id="favorite-button-@recipe.Id">
@await Html.PartialAsync("_FavoriteButton", recipe)
</div>
<a href="/Details/@recipe.Id"
class="btn btn-primary mt-auto">Details</a>
</div>
</div>
</div>
}
<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>
<div class="col-md-3 mb-4">
<div class="card h-100 text-center">
<div class="card-body d-flex flex-column justify-content-center">
<a href="/Create/@category.Category.Id"
class="btn btn-outline-primary">Rezept hinzufügen</a>
</div>
</div>
</div>
</div>
</div>
}
@@ -0,0 +1,75 @@
@model IEnumerable<Francesco.Recipes.World.Models.SearchViewModel>
@{
if (!Model.Any())
{
<p class="text-muted">Keine Ergebnisse gefunden.</p>
}
else
{
<div class="row">
@foreach (var recipe in Model)
{
<div class="col-md-4 mb-3">
<div class="card h-100 shadow-sm">
<div class="recipe-image">
@if (recipe.ImageData != null && recipe.MimeType != null)
{
<img src="data:@recipe.MimeType;base64,@Convert.ToBase64String(recipe.ImageData)"
alt="@recipe.Name"
class="card-img-top" />
}
else
{
<img src="~/images/placeholder.png"
alt="Platzhalter"
class="card-img-top" />
}
</div>
<div class="card-body d-flex flex-column justify-content-between">
<div>
<h5 class="card-title">@recipe.Name</h5>
<p class="card-text text-muted mb-2">
<i class="bi bi-clock"></i>
@recipe.TotalTime.Hours h @recipe.TotalTime.Minutes min
</p>
</div>
<div class="d-flex justify-content-between align-items-center mt-3">
<div class="favorite-button-container">
<form hx-post="@Url.Action(recipe.IsFavorite ? "RemoveFavorite" : "AddFavorite", "Recipe")"
hx-target="this"
hx-swap="outerHTML">
@Html.AntiForgeryToken()
<input type="hidden" name="recipeId" value="@recipe.Id" />
<button type="submit" class="btn btn-link p-0" style="width: 40px; height: 40px;">
@if (recipe.IsFavorite)
{
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#FFD700">
<path d="M12 17.27L18.18 21 16.54 13.97
22 9.24l-7.19-.62L12 2 9.19 8.62
2 9.24l5.46 4.73L5.82 21z" />
</svg>
}
else
{
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 17.27L18.18 21 16.54 13.97
22 9.24l-7.19-.62L12 2 9.19 8.62
2 9.24l5.46 4.73L5.82 21z" />
</svg>
}
</button>
</form>
</div>
<a href="/Details/@recipe.Id" class="btn btn-primary btn-sm">Details</a>
</div>
</div>
</div>
</div>
}
</div>
}
}
@@ -0,0 +1,61 @@
@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>
<button type="button" class="btn btn-primary" onclick="addInstructionToRecipe()">Add Instruction</button>
</form>
</div>
@section Scripts {
<script>
async function addInstructionToRecipe() {
event.preventDefault();
var form = document.getElementById('instruction-form');
var recipeId = form.querySelector('input[name="recipeId"]').value;
var description = form.querySelector('input[name="description"]').value;
var token = form.querySelector('input[name="__RequestVerificationToken"]').value;
var response = await fetch('/' + recipeId + '/AddInstruction', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'RequestVerificationToken': token
},
body: `recipeId=${encodeURIComponent(recipeId)}&description=${encodeURIComponent(description)}`
});
if (response.ok) {
location.reload();
} else {
alert('Failed to add instruction.');
}
}
</script>
}
@@ -0,0 +1,114 @@
@model Francesco.Recipes.World.Models.CreateRecipeViewModel
@using Francesco.Recipes.World.Models.BackendModels.Recipe
@using Francesco.Recipes.World.Models
@{
ViewData["Title"] = "Erstelle Rezept";
}
<h1 class="mb-4">Erstelle Rezept</h1>
<form method="post" enctype="multipart/form-data" asp-controller="Recipe" asp-action="Create" asp-route-categoryId="@Model.CategoryId">
<div class="mb-4">
<h2>Allgemein</h2>
<div class="form-group">
<label asp-for="Name">Name</label>
<input asp-for="Name" class="form-control" required />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Description">Beschreibung</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">Schwierigkeit</label>
<select asp-for="Difficulty" class="form-control" required>
<option value="">Schwierigkeit wählen</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-row">
<div class="col-sm-4">
<label asp-for="Servings">Portion (pro Person)</label>
<input asp-for="Servings" type="number" min="1" class="form-control" required />
<span asp-validation-for="Servings" class="text-danger"></span>
</div>
<div class="col-sm-4">
<label>Vorbereitungszeit</label>
<div class="d-flex">
<div class="input-group mr-2">
<input asp-for="PrepHours" type="number" class="form-control" min="0" value="0" />
<div class="input-group-append">
<span class="input-group-text">h</span>
</div>
</div>
<div class="input-group">
<input asp-for="PrepMinutes" type="number" class="form-control" min="0" max="59" value="0" />
<div class="input-group-append">
<span class="input-group-text">min</span>
</div>
</div>
</div>
</div>
<div class="col-sm-4">
<label>Kochzeit</label>
<div class="d-flex">
<div class="input-group mr-2">
<input asp-for="CookHours" type="number" class="form-control" min="0" value="0" />
<div class="input-group-append">
<span class="input-group-text">h</span>
</div>
</div>
<div class="input-group">
<input asp-for="CookMinutes" type="number" class="form-control" min="0" max="59" value="0" />
<div class="input-group-append">
<span class="input-group-text">min</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="mb-4">
<h2>Zutaten</h2>
@await Html.PartialAsync("_IngredientsPartial", Model.IngredientViewModel ?? new IngredientViewModel
{
RecipeId = Model.CategoryId,
Ingredients = new List<Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient>(),
Units = ViewBag.Units ?? new List<Francesco.Recipes.World.Models.BackendModels.Unit.Unit>()
})
</div>
<div class="mb-4">
<h2>Anweisungen</h2>
@await Html.PartialAsync("_GetInstructions", Model.InstructionViewModel ?? new InstructionViewModel
{
RecipeId = Model.CategoryId,
Instructions = new List<Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction>()
})
</div>
<div class="mb-4">
<h2>Bild/Video</h2>
<div class="form-group">
<label asp-for="Photo">Foto</label>
<input asp-for="Photo" class="form-control-file" accept="image/*" />
</div>
<div class="form-group">
<label asp-for="Video">Video</label>
<input asp-for="Video" class="form-control-file" accept="video/*" />
</div>
</div>
<div class="text-center mt-4">
<button type="submit" class="btn btn-success btn-lg">Rezept speichern</button>
<a asp-action="Index" asp-controller="Home" class="btn btn-secondary btn-lg ml-2">Abbrechen</a>
</div>
</form>
@@ -0,0 +1,209 @@
@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe
@{
ViewData["Title"] = "Recipe Details";
}
<h1>@Model.Name</h1>
<div class="recipe-details" data-recipe-id="@Model.Id">
<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>Preparation Time:</strong> @Model.PreparationTime</p>
<p><strong>Cooking Time:</strong> @Model.CookingTime</p>
</div>
@await Html.PartialAsync("_AdjustableIngredientsPartial", Model)
@await Html.PartialAsync("_RecipeInstructionGridPartial", new Francesco.Recipes.World.Models.InstructionViewModel
{
RecipeId = Model.Id,
Instructions = Model.Instructions.ToList()
})
<form hx-delete="@($"/{Model.Id}/Delete")"
hx-confirm="Sind Sie sicher, dass Sie dieses Rezept löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden."
hx-redirect="/">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-danger">
<i class="bi bi-trash"></i> Rezept löschen
</button>
</form>
</div>
@section Scripts {
<script>
document.addEventListener('DOMContentLoaded', function() {
const recipeId = '@Model.Id';
window.recipeId = recipeId;
const originalServings = @Model.Servings;
const specialUnits = {
discreteUnits: ["stk", "zehe", "blatt", "bund", "stange"],
smallUnits: ["msp"],
spoonUnits: {
"TL": { name: "teelöffel", toMl: 5 },
"EL": { name: "esslöffel", toMl: 15 }
}
};
const conversionTable = {
"knoblauch": {
unit: "zehe",
smallAmount: 0.5
},
};
const savedServings = localStorage.getItem(`recipe_${recipeId}_servings`);
if (savedServings) {
document.getElementById('servingsInput').value = savedServings;
adjustIngredientQuantities(parseInt(savedServings));
}
document.getElementById('decreaseServings').addEventListener('click', function () {
const input = document.getElementById('servingsInput');
const currentValue = parseInt(input.value);
if (currentValue > 1) {
input.value = currentValue - 1;
adjustIngredientQuantities(currentValue - 1);
saveServingsToLocalStorage(currentValue - 1);
}
});
document.getElementById('increaseServings').addEventListener('click', function () {
const input = document.getElementById('servingsInput');
const currentValue = parseInt(input.value);
input.value = currentValue + 1;
adjustIngredientQuantities(currentValue + 1);
saveServingsToLocalStorage(currentValue + 1);
});
document.getElementById('servingsInput').addEventListener('change', function () {
const newServings = parseInt(this.value);
if (newServings < 1) {
this.value = 1;
adjustIngredientQuantities(1);
saveServingsToLocalStorage(1);
} else {
adjustIngredientQuantities(newServings);
saveServingsToLocalStorage(newServings);
}
});
function adjustIngredientQuantities(newServings) {
const ingredients = document.querySelectorAll('#adjustable-ingredient-list li');
ingredients.forEach(ingredient => {
const originalQuantity = parseFloat(ingredient.getAttribute('data-original-quantity'));
const ingredientName = ingredient.getAttribute('data-ingredient-name').toLowerCase();
const unitSymbol = ingredient.getAttribute('data-unit');
const unitName = ingredient.getAttribute('data-unit-name');
let adjustedQuantity = (originalQuantity / originalServings) * newServings;
let finalQuantity = adjustedQuantity;
let note = "";
if (specialUnits.discreteUnits.includes(unitSymbol)) {
if (adjustedQuantity < 1 && adjustedQuantity > 0) {
finalQuantity = Math.ceil(adjustedQuantity);
note = " (ggf. eine kleine/halbe nehmen)";
} else {
finalQuantity = Math.round(adjustedQuantity);
}
}
// Convert small unit "msp" (pinch) to teaspoons (TL) for a better estimate
// 4 pinches = about 1 TL → show that as a note, rounded to 1 decimal
else if (specialUnits.smallUnits.includes(unitSymbol)) {
if (adjustedQuantity > 3) {
finalQuantity = Math.round(adjustedQuantity / 4 * 10) / 10;
note = ` (ca. ${finalQuantity} TL)`;
finalQuantity = adjustedQuantity;
} else {
finalQuantity = Math.round(adjustedQuantity);
}
}
else if (Object.keys(specialUnits.spoonUnits).includes(unitSymbol)) {
if (adjustedQuantity > 4 && unitSymbol === "TL") {
const esslöffel = Math.round(adjustedQuantity / 3 * 10) / 10;
note = ` (ca. ${esslöffel} EL)`;
}
finalQuantity = Math.round(adjustedQuantity * 2) / 2;
}
if (ingredientName.includes("knoblauch") && unitSymbol === "zehe" && adjustedQuantity < 1) {
finalQuantity = 1;
note = " (kleine Zehe)";
}
const formattedQuantity = formatQuantity(finalQuantity);
ingredient.querySelector('.ingredient-quantity').textContent = formattedQuantity;
const noteElement = ingredient.querySelector('.ingredient-note');
if (noteElement) {
noteElement.textContent = note;
}
});
}
function saveServingsToLocalStorage(servings) {
localStorage.setItem(`recipe_${recipeId}_servings`, servings.toString());
}
function formatQuantity(quantity) {
if (Number.isInteger(quantity)) {
return quantity;
}
if (quantity === 0.5 || quantity === 0.25 || quantity === 0.75) {
return quantity;
}
return Math.round(quantity * 10) / 10;
}
window.addSelectedIngredientsToShoppingList = function(recipeIdParam) {
const idToUse = recipeIdParam || recipeId;
var form = document.getElementById('ingredient-form');
var formData = new FormData(form);
var selectedIngredientIds = formData.getAll('ingredientIds');
fetch('/ShoppingList/CreateOrAddIngredients', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
},
body: JSON.stringify({ recipeId: idToUse, ingredientIds: selectedIngredientIds })
})
.then(response => {
if (response.ok) {
return response.json();
}
throw new Error('Failed to update shopping list');
})
.then(result => {
localStorage.setItem('shoppingListId', result.shoppingListId);
alert('Einkaufsliste aktualisiert.');
})
.catch(error => {
alert('Fehler beim Aktualisieren der Einkaufsliste.');
});
};
});
</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,20 @@
@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>
@@ -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>
}
@@ -0,0 +1,36 @@
@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe
<div class="adjustable-ingredients">
<div class="serving-adjustment mb-3" data-original-servings="@Model.Servings">
<h3>Ingredients</h3>
<div class="d-flex align-items-center mb-2">
<label for="servingsInput" class="me-2">Adjust servings:</label>
<div class="input-group" style="max-width: 150px;">
<button type="button" class="btn btn-outline-secondary" id="decreaseServings">-</button>
<input type="number" class="form-control text-center" id="servingsInput" value="@Model.Servings" min="1">
<button type="button" class="btn btn-outline-secondary" id="increaseServings">+</button>
</div>
<span class="ms-2 text-muted">(Original: @Model.Servings)</span>
</div>
</div>
<form id="ingredient-form">
<ul id="adjustable-ingredient-list">
@foreach (var ingredient in Model.RecipeIngredients)
{
<li id="ingredient-@ingredient.Id"
data-ingredient-id="@ingredient.Id"
data-original-quantity="@ingredient.Quantity"
data-ingredient-name="@ingredient.Ingredient.Name"
data-unit="@(ingredient.Unit?.Symbol ?? string.Empty)">
<input type="checkbox" name="ingredientIds" value="@ingredient.Id" />
<span class="ingredient-name">@ingredient.Ingredient.Name</span> -
<span class="ingredient-quantity">@ingredient.Quantity</span>
<span class="ingredient-unit">@(ingredient.Unit != null ? ingredient.Unit.Symbol : string.Empty)</span>
<span class="ingredient-note"></span>
</li>
}
</ul>
<button type="button" class="btn btn-primary mt-2" onclick="addSelectedIngredientsToShoppingList('@Model.Id')">Add Selected to Shopping List</button>
</form>
</div>
@@ -0,0 +1,26 @@
@model Francesco.Recipes.World.Models.IFavoritable
<form hx-post="@Url.Action(Model.IsFavorite ? "RemoveFavorite" : "AddFavorite", "Recipe")"
hx-target="this"
hx-swap="outerHTML">
@Html.AntiForgeryToken()
<input type="hidden" name="recipeId" value="@Model.Id" />
<button type="submit" class="btn btn-link p-0" style="width: 40px; height: 40px;">
@if (Model.IsFavorite)
{
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#FFD700">
<path d="M12 17.27L18.18 21 16.54 13.97
22 9.24l-7.19-.62L12 2 9.19 8.62
2 9.24l5.46 4.73L5.82 21z" />
</svg>
}
else
{
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 17.27L18.18 21 16.54 13.97
22 9.24l-7.19-.62L12 2 9.19 8.62
2 9.24l5.46 4.73L5.82 21z" />
</svg>
}
</button>
</form>
@@ -0,0 +1,39 @@
@model Francesco.Recipes.World.Models.InstructionViewModel
@Html.AntiForgeryToken()
<div id="instructions-container">
@for (int i = 0; i < Model.Instructions.Count; i++)
{
<div class="instruction-item" id="instruction-@Model.Instructions[i].Id">
<div class="instruction-controls">
@if (Model.Instructions[i].MediaFiles != null && Model.Instructions[i].MediaFiles.Any())
{
var mediaFile = Model.Instructions[i].MediaFiles.First();
if (mediaFile.Data != null)
{
<div class="instruction-media">
<img src="data:@mediaFile.MimeType;base64,@Convert.ToBase64String(mediaFile.Data)"
alt="Instruction Media" class="instruction-media-preview" />
</div>
}
}
<input type="file" name="InstructionViewModel.Instructions[@i].MediaFile" class="instruction-file" />
<textarea name="InstructionViewModel.Instructions[@i].Description" placeholder="Beschreibung" class="form-control">@Model.Instructions[i].Description</textarea>
<button type="button" class="btn-delete" onclick="Francesco.removeInstruction('@Model.Instructions[i].Id', '@Model.RecipeId')">🗑️</button>
</div>
<div class="instruction-actions">
<button type="button" class="btn-move-up" onclick="Francesco.moveInstructionUp('@Model.Instructions[i].Id', '@Model.RecipeId')">⬆️</button>
<button type="button" class="btn-move-down" onclick="Francesco.moveInstructionDown('@Model.Instructions[i].Id', '@Model.RecipeId')">⬇️</button>
</div>
</div>
}
</div>
<button type="button" class="btn-add" onclick="Francesco.addInstruction()">Schritte hinzufügen</button>
<script src="~/js/site.js" asp-append-version="true" defer></script>
@@ -0,0 +1,29 @@
@model Francesco.Recipes.World.Models.IngredientViewModel
@Html.AntiForgeryToken()
<div id="ingredients-container">
@for (int i = 0; i < Model.Ingredients.Count; i++)
{
<div class="ingredient-item" id="ingredient-@Model.Ingredients[i].Id">
<div class="ingredient-controls">
<input type="text" name="IngredientViewModel.Ingredients[@i].Name" value="@Model.Ingredients[i].Name" placeholder="Name" class="form-control" />
<input type="number" name="IngredientViewModel.Ingredients[@i].RecipeIngredients[0].Quantity" value="@(Model.Ingredients[i].RecipeIngredients.FirstOrDefault()?.Quantity)" placeholder="Menge" class="form-control" />
<select name="IngredientViewModel.Ingredients[@i].RecipeIngredients[0].Unit.Id" class="form-control">
@foreach (var unit in Model.Units)
{
@if (Model.Ingredients[i].RecipeIngredients.FirstOrDefault()?.Unit?.Id == unit.Id)
{
<option value="@unit.Id" selected>@unit.Name (@unit.Symbol)</option>
}
else
{
<option value="@unit.Id">@unit.Name (@unit.Symbol)</option>
}
}
</select>
<button type="button" class="btn-delete" onclick="Francesco.removeIngredient('@Model.Ingredients[i].Id')">🗑️</button>
</div>
</div>
}
</div>
<button type="button" class="btn-add" onclick="Francesco.addIngredient()">Zutat hinzufügen</button>
@@ -1,49 +1,91 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Francesco.Recipes.World</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/Francesco.Recipes.World.styles.css" asp-append-version="true" />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Francesco.Recipes.World</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/Francesco.Recipes.World.styles.css" asp-append-version="true"/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Francesco.Recipes.World</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Francesco.Recipes.World</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Startseite</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="ShoppingList" asp-action="Details">Einkaufsliste</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Favorite" asp-action="Index">Favoriten</a>
</li>
</ul>
<div class="d-flex">
<button class="btn btn-outline-dark me-2" id="darkModeToggle">
<i class="bi bi-moon"></i>
</button>
<button class="btn btn-outline-light" id="lightModeToggle">
<i class="bi bi-sun"></i>
</button>
</div>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
&copy; 2024 - Francesco.Recipes.World - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<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>
@await RenderSectionAsync("Scripts", required: false)
<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>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
let recipeId;
@if (ViewData["RecipeId"] != null)
{
<text>recipeId = '@ViewData["RecipeId"]';</text>
}
else if (ViewData["CategoryId"] != null)
{
<text>recipeId = '@ViewData["CategoryId"]';</text>
}
const darkModeToggle = document.getElementById('darkModeToggle');
const lightModeToggle = document.getElementById('lightModeToggle');
darkModeToggle.addEventListener('click', () => {
document.body.classList.add('bg-dark', 'text-white');
});
lightModeToggle.addEventListener('click', () => {
document.body.classList.remove('bg-dark', 'text-white');
});
document.body.addEventListener('htmx:configRequest', (event) => {
const token = document.querySelector('input[name="__RequestVerificationToken"]')?.value;
if (token) {
event.detail.headers['RequestVerificationToken'] = token;
}
});
</script>
@await Html.PartialAsync("_ValidationScriptsPartial")
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
@@ -0,0 +1,31 @@
@model Francesco.Recipes.World.Models.InstructionViewModel
<div class="recipe-instructions-section">
<h3>Instructions</h3>
<div class="instructions-grid">
@foreach (var instruction in Model.Instructions.OrderBy(i => i.Number))
{
<div class="instruction-card">
<div class="instruction-image">
@if (instruction.MediaFiles != null && instruction.MediaFiles.Any())
{
var mediaFile = instruction.MediaFiles.First();
if (mediaFile.Data != null && mediaFile.Data.Length > 0)
{
<img src="data:@mediaFile.MimeType;base64,@Convert.ToBase64String(mediaFile.Data)" alt="Step @instruction.Number" />
}
}
else
{
<div class="placeholder-image">
<i class="bi bi-image"></i>
</div>
}
<span class="step-number">@instruction.Number</span>
</div>
<p class="instruction-text">@instruction.Description</p>
</div>
}
</div>
</div>
@@ -0,0 +1,106 @@
@model Francesco.Recipes.World.Models.ShoppingListDetailsViewModel
@{
ViewData["Title"] = "Einkaufsliste Details";
}
@using System.Text.Json
@{
var recipeIngredientsJson = JsonSerializer.Serialize(
Model.RecipesInAnyShoppingList.ToDictionary(
r => r.Id,
r => r.RecipeIngredients.Select(ri => new
{
id = ri.Id,
name = ri.Ingredient.Name,
amount = ri.Quantity,
unit = ri.Unit.Name,
shoppingListId = Model.RecipeIngredientToShoppingListMap.ContainsKey(ri.Id)
? Model.RecipeIngredientToShoppingListMap[ri.Id]
: Guid.Empty
})
)
);
}
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Einkaufsliste Details</h2>
<div style="border:2px solid black; border-radius:30px; padding:10px 30px; display:inline-block;">
Anzahl Rezepte: <span id="recipeCount">@Model.RecipeCount</span>
</div>
</div>
<div class="d-flex align-items-center mb-4">
<button class="btn btn-outline-secondary me-2" type="button" id="carouselLeft">
<i class="bi bi-arrow-left"></i>
</button>
<div class="flex-grow-1 overflow-auto" style="white-space:nowrap;" id="recipeCarousel">
@foreach (var recipe in Model.RecipesInAnyShoppingList)
{
var mediaFile = recipe.MediaFiles?.FirstOrDefault();
var imageData = mediaFile?.Data;
var mimeType = mediaFile?.MimeType;
var ingredientCount = recipe.RecipeIngredients?.Count() ?? 0;
<div class="card d-inline-block mx-2 recipe-card"
data-recipe-id="@recipe.Id"
style="width: 180px; vertical-align:top; border: 1px solid #000;">
<div class="position-relative">
<div class="position-absolute top-0 end-0 p-2">
<button class="btn btn-sm btn-link text-dark" title="Rezept entfernen"
onclick="Francesco.removeRecipe('@recipe.Id');">
<i class="bi bi-trash" style="font-size: 1.2rem;"></i>
</button>
</div>
<div class="text-center p-2" style="height: 120px; display: flex; align-items: center; justify-content: center;">
@if (imageData != null && mimeType != null)
{
<img src="data:@mimeType;base64,@Convert.ToBase64String(imageData)"
alt="@recipe.Name"
style="max-height: 100%; max-width: 100%; object-fit: contain;" />
}
else
{
<div style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;">
<i class="bi bi-image" style="font-size: 3rem; color: #ccc;"></i>
</div>
}
</div>
<div class="p-2 text-center" style="border-top: 1px solid #000; background-color: #fff;">
<div class="recipe-name" style="font-weight: bold; font-size: 0.9rem; white-space: normal; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; height: 40px;">@recipe.Name</div>
</div>
</div>
</div>
}
</div>
<button class="btn btn-outline-secondary ms-2" type="button" id="carouselRight">
<i class="bi bi-arrow-right"></i>
</button>
</div>
<div id="ingredientListContainer" class="mt-4">
<div class="card">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">Zutaten</h4>
</div>
<div class="card-body">
<ul id="ingredientList" class="list-group"></ul>
<div class="text-center mt-3">
<button id="removeSelectedButton" class="btn" onclick="Francesco.removeSelectedIngredients()">
ENTFERNE MARKIERTE
</button>
</div>
</div>
</div>
</div>
</div>
@section Scripts {
@Html.AntiForgeryToken()
<script>
window.recipeIngredients = @Html.Raw(recipeIngredientsJson);
</script>
<script src="~/js/site.js"></script>
}
+215 -1
View File
@@ -19,4 +19,218 @@ html {
body {
margin-bottom: 60px;
}
}
.instruction-item, .ingredient-item {
background-color: #f8f9fa;
padding: 15px;
margin-bottom: 10px;
border-radius: 4px;
}
.instruction-controls, .ingredient-controls {
display: flex;
align-items: center;
gap: 10px;
}
.instruction-media {
width: 80px;
height: 80px;
overflow: hidden;
margin-right: 10px;
}
.instruction-media img {
width: 100%;
height: 100%;
object-fit: cover;
}
.instruction-file {
max-width: 200px;
}
textarea.form-control {
min-height: 80px;
}
.btn-delete, .btn-move-up, .btn-move-down, .btn-save {
background: none;
border: none;
font-size: 1.2rem;
cursor: pointer;
}
.btn-delete {
color: #dc3545;
}
.btn-save {
color: #28a745;
}
.instruction-actions {
display: flex;
justify-content: flex-end;
margin-top: 5px;
}
.btn-add {
background-color: #007bff;
color: white;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
}
/* Recipe Instructions Grid Layout */
.instructions-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
margin-top: 2rem;
margin-bottom: 2rem;
}
@media (max-width: 992px) {
.instructions-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 576px) {
.instructions-grid {
grid-template-columns: 1fr;
}
}
.instruction-card {
border: 1px solid #ccc;
padding: 1rem;
border-radius: 8px;
background-color: white;
}
.instruction-image {
position: relative;
width: 100%;
aspect-ratio: 1 / 1;
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
margin-bottom: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
background-color: #f8f9fa;
text-align: center;
}
.instruction-image img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 4px;
}
.placeholder-image {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 2rem;
color: #999;
background-color: #f0f0f0;
border-radius: 4px;
}
.step-number {
position: absolute;
bottom: 8px;
right: 8px;
background: rgba(0, 0, 0, 0.6);
color: white;
padding: 3px 8px;
border-radius: 50%;
font-size: 0.875rem;
font-weight: bold;
}
.instruction-text {
font-size: 0.9rem;
line-height: 1.4;
color: #333;
}
.recipe-instructions-section h3 {
margin-bottom: 1.5rem;
position: relative;
padding-bottom: 0.5rem;
}
.recipe-instructions-section h3:after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 50px;
height: 2px;
background-color: #007bff;
}
}
.recipe-card {
cursor: pointer;
transition: all 0.3s ease;
}
.active-recipe-card {
border-bottom: 4px solid #000 !important;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.selected-ingredient .btn-outline-danger {
background-color: #dc3545 !important;
color: white !important;
border-color: #dc3545 !important;
}
#removeSelectedButton {
display: none;
margin-top: 15px;
background-color: #FFA500;
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
font-weight: bold;
transition: all 0.3s;
}
#removeSelectedButton:hover {
background-color: #FF8C00;
}
.ingredient-counter {
position: absolute;
top: -8px;
right: -8px;
background-color: #dc3545;
color: white;
border-radius: 50%;
width: 22px;
height: 22px;
display: flex;
justify-content: center;
align-items: center;
font-size: 0.8rem;
font-weight: bold;
}
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="m354-287 126-76 126 77-33-144 111-96-146-13-58-136-58 135-146 13 111 97-33 143ZM233-120l65-281L80-590l288-25 112-265 112 265 288 25-218 189 65 281-247-149-247 149Zm247-350Z"/></svg>

After

Width:  |  Height:  |  Size: 297 B

+415 -3
View File
@@ -1,4 +1,416 @@
// Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
// for details on configuring this project to bundle and minify static web assets.
(function (window, document) {
const selectedIngredients = new Set();
const recipeIngredients = window.recipeIngredients || {};
let activeRecipeId = Object.keys(recipeIngredients)[0];
let recipeId;
// Write your JavaScript code.
htmx && htmx.on('htmx:afterSwap', (event) => {
if (event.target.id === 'instructions-container') {
console.log('Instructions reloaded.');
}
if (event.target.id === 'ingredients-container') {
console.log('Ingredients reloaded.');
}
});
function setRecipeId(id) {
recipeId = id;
}
async function updateRecipeCount() {
try {
const response = await fetch('/ShoppingList/RecipeCount');
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
const data = await response.json();
document.getElementById('recipeCount').textContent = data.count;
} catch (err) {
console.error("Fehler beim Aktualisieren der Rezeptanzahl:", err);
}
}
const countUpdateInterval = setInterval(updateRecipeCount, 5000);
document.addEventListener('shopping-list-changed', updateRecipeCount);
window.addEventListener('beforeunload', function () {
clearInterval(countUpdateInterval);
});
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('carouselLeft')?.addEventListener('click', function () {
document.getElementById('recipeCarousel').scrollBy({ left: -200, behavior: 'smooth' });
});
document.getElementById('carouselRight')?.addEventListener('click', function () {
document.getElementById('recipeCarousel').scrollBy({ left: 200, behavior: 'smooth' });
});
if (activeRecipeId) setActiveCard(activeRecipeId);
document.querySelectorAll('.recipe-card').forEach(card => {
card.addEventListener('click', function () {
setActiveCard(this.dataset.recipeId);
});
});
});
function renderIngredientList(recipeId) {
const list = document.getElementById('ingredientList');
list.innerHTML = '';
const ingredients = recipeIngredients[recipeId] || [];
if (ingredients.length === 0) {
list.innerHTML = '<li class="list-group-item text-muted">Keine Zutaten vorhanden.</li>';
return;
}
ingredients.forEach(ingredient => {
const isSelected = selectedIngredients.has(ingredient.id);
const li = document.createElement('li');
li.className = 'list-group-item d-flex justify-content-between align-items-center';
if (isSelected) li.classList.add('selected-ingredient');
li.innerHTML = `
<span>
<strong>${ingredient.name}</strong>
<span class="text-secondary ms-2">${ingredient.amount} ${ingredient.unit}</span>
</span>
<button class="btn btn-sm btn-outline-danger" title="Zutat entfernen" onclick="Francesco.toggleIngredientSelection('${ingredient.id}', this); event.stopPropagation();">
<i class="bi bi-dash"></i>
</button>
`;
list.appendChild(li);
});
updateRemoveSelectedButton();
}
function setActiveCard(recipeId) {
document.querySelectorAll('.recipe-card').forEach(card => {
card.classList.toggle('active-recipe-card', card.dataset.recipeId === recipeId);
});
activeRecipeId = recipeId;
renderIngredientList(recipeId);
}
function toggleIngredientSelection(ingredientId, button) {
const listItem = button.closest('li');
if (selectedIngredients.has(ingredientId)) {
selectedIngredients.delete(ingredientId);
listItem.classList.remove('selected-ingredient');
} else {
selectedIngredients.add(ingredientId);
listItem.classList.add('selected-ingredient');
}
updateRemoveSelectedButton();
}
function updateRemoveSelectedButton() {
document.getElementById('removeSelectedButton').style.display =
selectedIngredients.size > 0 ? 'inline-block' : 'none';
}
async function removeSelectedIngredients() {
if (selectedIngredients.size === 0) return;
const shoppingListIds = [];
selectedIngredients.forEach(ingredientId => {
for (const recipeId in recipeIngredients) {
const ingredients = recipeIngredients[recipeId];
const ingredient = ingredients.find(ing => ing.id === ingredientId);
if (ingredient && ingredient.shoppingListId &&
ingredient.shoppingListId !== "00000000-0000-0000-0000-000000000000") {
shoppingListIds.push(ingredient.shoppingListId);
}
}
});
if (shoppingListIds.length === 0) {
console.warn("Keine gültigen Shopping List IDs gefunden");
return;
}
try {
const response = await fetch('/ShoppingList/RemoveIngredients', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value,
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
},
body: JSON.stringify(shoppingListIds)
});
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
const result = await response.json();
if (result.success) {
window.location.reload();
} else {
console.error("Fehler beim Entfernen der Zutaten:", result.error);
alert("Beim Entfernen der Zutaten ist ein Fehler aufgetreten.");
}
} catch (err) {
console.error("Fehler beim Entfernen der Zutaten:", err);
alert("Beim Entfernen der Zutaten ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.");
}
}
async function removeRecipe(recipeId) {
let confirmed = false;
if (window.Swal) {
const result = await Swal.fire({
title: 'Rezept entfernen?',
text: 'Möchten Sie dieses Rezept wirklich aus der Einkaufsliste entfernen?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Ja, entfernen',
cancelButtonText: 'Abbrechen',
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6'
});
confirmed = result.isConfirmed;
} else {
confirmed = confirm('Möchten Sie dieses Rezept wirklich aus der Einkaufsliste entfernen?');
}
if (!confirmed) return;
try {
const tokenElement = document.querySelector('input[name="__RequestVerificationToken"]');
if (!tokenElement) {
console.error('Anti-forgery token not found');
alert('Fehler: Anti-Forgery Token nicht gefunden');
return;
}
const response = await fetch(`/ShoppingList/RemoveRecipeFromList/${recipeId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'RequestVerificationToken': tokenElement.value
}
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
if (window.Swal) {
await Swal.fire({
title: 'Erfolgreich!',
text: 'Das Rezept wurde entfernt.',
icon: 'success',
timer: 1500,
showConfirmButton: false
});
} else {
alert('Das Rezept wurde erfolgreich entfernt.');
}
window.location.reload();
} else {
const errorMsg = result.error || 'Unbekannter Fehler';
console.error('Fehler beim Entfernen:', errorMsg);
if (window.Swal) {
await Swal.fire({
title: 'Fehler',
text: errorMsg,
icon: 'error'
});
} else {
alert('Fehler: ' + errorMsg);
}
}
} catch (err) {
console.error('Fehler beim Entfernen des Rezepts:', err);
if (window.Swal) {
await Swal.fire({
title: 'Fehler',
text: 'Beim Löschen ist ein Fehler aufgetreten.',
icon: 'error'
});
} else {
alert('Beim Löschen ist ein Fehler aufgetreten.');
}
}
}
async function moveInstructionUp(instructionId, recipeIdParam) {
const idToUse = recipeIdParam || recipeId;
if (!idToUse) {
alert('Recipe ID is not set. Please select a recipe first.');
return;
}
try {
const response = await fetch(`/Recipe/${idToUse}/Instruction/${instructionId}/move-up`, {
method: 'POST',
headers: {
'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
}
});
if (response.ok) {
htmx.ajax('GET', `/Recipe/${idToUse}/Instructions`, '#instructions-container');
} else {
const error = await response.json();
alert(error.Error || 'Failed to move instruction up.');
}
} catch (error) {
console.error('Error moving instruction up:', error);
}
}
async function moveInstructionDown(instructionId, recipeIdParam) {
const idToUse = recipeIdParam || recipeId;
if (!idToUse) {
alert('Recipe ID is not set. Please select a recipe first.');
return;
}
try {
const response = await fetch(`/Recipe/${idToUse}/Instruction/${instructionId}/move-down`, {
method: 'POST',
headers: {
'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
}
});
if (response.ok) {
htmx.ajax('GET', `/Recipe/${idToUse}/Instructions`, '#instructions-container');
} else {
const error = await response.json();
alert(error.Error || 'Failed to move instruction down.');
}
} catch (error) {
console.error('Error moving instruction down:', error);
}
}
async function removeInstruction(instructionId, recipeIdParam) {
const idToUse = recipeIdParam || recipeId;
if (!idToUse) {
alert('Recipe ID is not set. Please select a recipe first.');
return;
}
if (!confirm('Möchtest du diesen Schritt wirklich löschen?')) return;
try {
const response = await fetch(`/${idToUse}/RemoveInstruction/${instructionId}`, {
method: 'POST',
headers: {
'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
}
});
if (response.ok) {
const element = document.getElementById(`instruction-${instructionId}`);
if (element) element.remove();
} else {
const error = await response.json();
alert(error.Error || 'Fehler beim Löschen der Anweisung.');
}
} catch (error) {
console.error('Fehler beim Löschen:', error);
}
}
function addInstruction() {
const container = document.getElementById('instructions-container');
const index = document.querySelectorAll('.instruction-item').length;
const newInstructionHtml = `
<div class="instruction-item" id="instruction-new-${index}">
<div class="instruction-controls">
<input type="file" name="InstructionViewModel.Instructions[${index}].MediaFile" class="instruction-file" />
<textarea name="InstructionViewModel.Instructions[${index}].Description" placeholder="Beschreibung" class="form-control"></textarea>
<button type="button" class="btn-delete" onclick="document.getElementById('instruction-new-${index}').remove()">🗑️</button>
</div>
<div class="instruction-actions">
<button type="button" class="btn-move-up" disabled>⬆️</button>
<button type="button" class="btn-move-down" disabled>⬇️</button>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', newInstructionHtml);
}
async function removeIngredient(ingredientId) {
if (!confirm('Möchtest du diese Zutat wirklich löschen?')) return;
try {
const response = await fetch(`/${recipeId}/RemoveIngredient/${ingredientId}`, {
method: 'POST',
headers: {
'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
}
});
if (response.ok) {
const element = document.getElementById(`ingredient-${ingredientId}`);
if (element) element.remove();
} else {
const error = await response.json();
alert(error.Error || 'Fehler beim Löschen der Zutat.');
}
} catch (error) {
console.error('Fehler beim Löschen:', error);
}
}
async function addIngredient() {
const container = document.getElementById('ingredients-container');
const index = document.querySelectorAll('.ingredient-item').length;
const newIngredientHtml = `
<div class="ingredient-item" id="ingredient-new-${index}">
<div class="ingredient-controls">
<input type="text" name="IngredientViewModel.Ingredients[${index}].Name" placeholder="Name" class="form-control" />
<input type="number" name="IngredientViewModel.Ingredients[${index}].RecipeIngredients[0].Quantity" placeholder="Menge" class="form-control" />
<select name="IngredientViewModel.Ingredients[${index}].RecipeIngredients[0].Unit.Id" class="form-control unit-select">
<option value="">Lade Einheiten...</option>
</select>
<button type="button" class="btn-delete" onclick="document.getElementById('ingredient-new-${index}').remove()">🗑️</button>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', newIngredientHtml);
const addedElement = document.getElementById(`ingredient-new-${index}`);
const unitSelect = addedElement.querySelector('.unit-select');
try {
const response = await fetch('/Unit/GetAllUnits');
if (response.ok) {
const units = await response.json();
unitSelect.innerHTML = '';
units.forEach(unit => {
const option = new Option(unit.name, unit.id);
unitSelect.add(option);
});
} else {
unitSelect.innerHTML = '<option value="">Fehler beim Laden</option>';
}
} catch (error) {
unitSelect.innerHTML = '<option value="">Fehler beim Laden</option>';
}
}
window.Francesco = {
setRecipeId,
moveInstructionUp,
moveInstructionDown,
removeInstruction,
addInstruction,
removeIngredient,
addIngredient,
toggleIngredientSelection,
removeSelectedIngredients,
removeRecipe,
};
})(window, document);
console.log("Francesco-Objekt initialisiert:", window.Francesco);
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>FrancescosRecipeWorld_Mock</RootNamespace>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.11" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="MSTest" Version="3.6.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Francesco.Recipes.World\Francesco.Recipes.World.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>
</Project>
@@ -0,0 +1 @@
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
+156
View File
@@ -0,0 +1,156 @@
using Francesco.Recipes.World.Controller.Recipe;
using Francesco.Recipes.World.Data;
using Francesco.Recipes.World.Models;
using Francesco.Recipes.World.Models.BackendModels.Instruction;
using Francesco.Recipes.World.Models.BackendModels.MediaFile;
using Francesco.Recipes.World.Models.BackendModels.Recipe;
using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient;
using Francesco.Recipes.World.Repositories.Category;
using Francesco.Recipes.World.Repositories.Favorit;
using Francesco.Recipes.World.Repositories.Ingredient;
using Francesco.Recipes.World.Repositories.Instruction;
using Francesco.Recipes.World.Repositories.MediaFile;
using Francesco.Recipes.World.Repositories.Recipe;
using Francesco.Recipes.World.Repositories.Unit;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Moq;
namespace FrancescosRecipeWorld_Mock
{
[TestClass]
public sealed class FrancescoDamicoUnittest2
{
private Mock<IFavoriteRepository> _mockFavoriteRepository;
private Mock<IRecipeRepository> _mockRecipeRepository;
private RecipeController _recipeController;
[TestInitialize]
public void Setup()
{
_mockFavoriteRepository = new Mock<IFavoriteRepository>();
_mockRecipeRepository = new Mock<IRecipeRepository>();
var options = new DbContextOptionsBuilder<FrancescosRecipesWorldDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
_recipeController = new RecipeController(
_mockRecipeRepository.Object,
Mock.Of<IUnitRepository>(),
Mock.Of<ICategoryRepository>(),
Mock.Of<IIngredientRepository>(),
Mock.Of<IMediaFileRepository>(),
Mock.Of<IInstructionRepository>(),
_mockFavoriteRepository.Object,
new FrancescosRecipesWorldDbContext(options)
);
}
/// <summary>
/// Test 1: AddFavorite(Guid recipeId) -> PartialViewResult mit FavoriteButtonViewModel
/// Parameter: recipeId (Guid)
/// Rückgabewert: PartialViewResult
/// </summary>
[TestMethod]
public async Task FrancescoDamico_UnitTest1()
{
// Arrange
var recipeId = Guid.NewGuid();
_mockFavoriteRepository
.Setup(x => x.AddFavoriteAsync(recipeId))
.Returns(Task.CompletedTask);
// Act
var result = await _recipeController.AddFavorite(recipeId);
// Assert
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result, typeof(PartialViewResult));
var partialViewResult = result as PartialViewResult;
Assert.AreEqual("_FavoriteButton", partialViewResult.ViewName);
Assert.IsInstanceOfType(partialViewResult.Model, typeof(FavoritButtonViewModel));
var model = partialViewResult.Model as FavoritButtonViewModel;
Assert.AreEqual(recipeId, model.Id);
Assert.IsTrue(model.IsFavorite);
}
/// <summary>
/// Test 2: RemoveFavorite(Guid recipeId) -> PartialViewResult mit FavoriteButtonViewModel
/// Parameter: recipeId (Guid)
/// Rückgabewert: PartialViewResult
/// </summary>
[TestMethod]
public async Task FrancescoDamico_UnitTest2()
{
// Arrange
var recipeId = Guid.NewGuid();
_mockFavoriteRepository
.Setup(x => x.RemoveFavoriteAsync(recipeId))
.Returns(Task.CompletedTask);
// Act
var result = await _recipeController.RemoveFavorite(recipeId);
// Assert
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result, typeof(PartialViewResult));
var partialViewResult = result as PartialViewResult;
Assert.AreEqual("_FavoriteButton", partialViewResult.ViewName);
Assert.IsInstanceOfType(partialViewResult.Model, typeof(FavoritButtonViewModel));
var model = partialViewResult.Model as FavoritButtonViewModel;
Assert.AreEqual(recipeId, model.Id);
Assert.IsFalse(model.IsFavorite);
}
/// <summary>
/// Test 3: Details(Guid recipeId) -> ViewResult mit Recipe Modell
/// Parameter: recipeId (Guid)
/// Rückgabewert: ViewResult mit Recipe-Objekt
/// </summary>
[TestMethod]
public async Task FrancescoDamico_UnitTest3()
{
// Arrange
var recipeId = Guid.NewGuid();
var expectedRecipe = new Recipe
{
Id = recipeId,
Name = "Spaghetti Carbonara",
Description = "Klassisches italienisches Pasta-Gericht",
Difficulty = Difficulty.Easy,
Servings = 4,
PreparationTime = new TimeSpan(0, 10, 0),
CookingTime = new TimeSpan(0, 20, 0),
IsFavorite = false,
CreatedAt = DateTime.UtcNow,
RecipeIngredients = new List<RecipeIngredient>(),
Instructions = new List<Instruction>(),
MediaFiles = new List<MediaFile>()
};
_mockRecipeRepository
.Setup(x => x.GetRecipeByIdAsync(recipeId))
.ReturnsAsync(expectedRecipe);
// Act
var result = await _recipeController.Details(recipeId);
// Assert
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result, typeof(ViewResult));
var viewResult = result as ViewResult;
Assert.IsInstanceOfType(viewResult.Model, typeof(Recipe));
var model = viewResult.Model as Recipe;
Assert.AreEqual(recipeId, model.Id);
Assert.AreEqual("Spaghetti Carbonara", model.Name);
Assert.AreEqual(Difficulty.Easy, model.Difficulty);
}
}
}