add CategoryController with endpoints

This commit is contained in:
franc
2025-04-08 15:22:28 +02:00
parent 712b561d12
commit 36bca4d658
24 changed files with 1535 additions and 142 deletions
@@ -5,6 +5,7 @@
using Francesco.Recipes.World.Repositories.Category;
using Microsoft.AspNetCore.Mvc;
[ValidateAntiForgeryToken]
[Route("Category")]
public class CategoryController : Controller
@@ -37,7 +38,7 @@
public async Task<ActionResult<IEnumerable<Recipe>>> GetRecipesByCategory(Guid id)
{
var recipes = await _categoryRepository.GetRecipesByCategoryAsync(id);
return Ok(recipes);
return View(recipes);
}
}
}
@@ -1,6 +1,48 @@
namespace Francesco.Recipes.World.Controller.MediaFile
{
public class MediaFileController
using Francesco.Recipes.World.Data;
using Francesco.Recipes.World.Repositories.MediaFile;
using Microsoft.AspNetCore.Mvc;
[ValidateAntiForgeryToken]
[Route("categories/{categoryId}/Recipe")]
public class MediaFileController : Controller
{
private readonly IMediaFileRepository _mediaFileRepository;
private readonly FrancescosRecipesWorldDbContext _context;
public MediaFileController(IMediaFileRepository mediaFileRepository, FrancescosRecipesWorldDbContext context)
{
_mediaFileRepository = mediaFileRepository;
_context = context;
}
// POST: /UploadImage
[HttpPost("UploadImage")]
public async Task<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();
}
}
}
@@ -1,43 +1,94 @@
namespace Francesco.Recipes.World.Controller.Recipe
{
using System.ComponentModel.DataAnnotations;
using Francesco.Recipes.World.Models.BackendModels.Recipe;
using Francesco.Recipes.World.Repositories.Category;
using Francesco.Recipes.World.Repositories.Favorit;
using Francesco.Recipes.World.Repositories.Ingredient;
using Francesco.Recipes.World.Repositories.Instruction;
using Francesco.Recipes.World.Repositories.MediaFile;
using Francesco.Recipes.World.Repositories.Recipe;
using Francesco.Recipes.World.Repositories.Unit;
using Francesco.Recipes.World.Views.Category;
using Francesco.Recipes.World.Views.Recipe;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
[Route("categories/{categoryId}/Recipe")]
[ValidateAntiForgeryToken]
[Route("Recipe")]
public class RecipeController : Controller
{
private readonly IRecipeRepository _recipeRepository;
private readonly IUnitRepository _unitRepository;
private readonly ICategoryRepository _categoryRepository;
private readonly IIngredientRepository _ingredientRepository;
private readonly IMediaFileRepository _mediaFileRepository;
private readonly IInstructionRepository _instructionRepository;
private readonly IFavoritRepository _favoritRepository;
public RecipeController(IRecipeRepository recipeRepository, IUnitRepository unitRepository, ICategoryRepository categoryRepository, IIngredientRepository ingredientRepository)
[Display(Name = "Schwierigkeitsgrad")]
[BindProperty(SupportsGet = true)]
public Difficulty? SelectedDifficulty { get; set; }
public RecipeController(IRecipeRepository recipeRepository, IUnitRepository unitRepository, ICategoryRepository categoryRepository, IIngredientRepository ingredientRepository, IMediaFileRepository mediaFileRepository, IInstructionRepository instructionRepository, IFavoritRepository favoritRepository)
{
_recipeRepository = recipeRepository;
_unitRepository = unitRepository;
_categoryRepository = categoryRepository;
_ingredientRepository = ingredientRepository;
Recipes = new List<Recipe>();
_mediaFileRepository = mediaFileRepository;
_instructionRepository = instructionRepository;
_favoritRepository = favoritRepository;
}
public IReadOnlyCollection<Recipe> Recipes { get; set; }
// GET: /Recipe/AddOrCreateIngredient
// GET: /Recipe/{recipeId}/AddOrCreateIngredient
[HttpGet("{recipeId}/AddOrCreateIngredient")]
public async Task<IActionResult> AddOrCreateIngredient()
public async Task<IActionResult> AddOrCreateIngredient(Guid recipeId)
{
var units = await _unitRepository.GetAllUnitsAsync();
ViewBag.Units = new SelectList(units, "Id", "Name");
ViewBag.RecipeId = recipeId;
return View();
}
// POST: /Recipe/AddOrCreateIngredient
// 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.GetAllCategoriesAsync();
var viewModel = new List<CategoryRecipesViewModel>();
foreach (var category in categories)
{
var recipes = await _categoryRepository.GetRecipesByCategoryAsync(category.Id);
viewModel.Add(new CategoryRecipesViewModel
{
Category = category,
Recipes = recipes,
});
}
return View(viewModel);
}
// POST: /Recipe/{recipeId}/AddOrCreateIngredient
[HttpPost("{recipeId}/AddOrCreateIngredient")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddOrCreateIngredient(Guid recipeId, string ingredientName, int quantity, Guid unitId)
{
if (quantity <= 0)
@@ -56,8 +107,8 @@
return RedirectToAction("Details", new { id = recipeId });
}
// GET: /categories/{categoryId}/Recipe/Create
[HttpGet("Create")]
// GET: /Recipe/Create/{categoryId}
[HttpGet("Create/{categoryId}")]
public async Task<IActionResult> Create(Guid categoryId)
{
var category = await _categoryRepository.GetCategoryByIdAsync(categoryId);
@@ -70,10 +121,9 @@
return View();
}
// POST: /categories/{categoryId}/Recipe/Create
[HttpPost("Create")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Guid categoryId, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime)
// POST: /Recipe/Create/{categoryId}
[HttpPost("Create/{categoryId}")]
public async Task<IActionResult> Create(Guid categoryId, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime, IFormFile? photo)
{
if (string.IsNullOrWhiteSpace(name))
{
@@ -103,13 +153,18 @@
return NotFound($"Kategorie mit ID {categoryId} wurde nicht gefunden.");
}
await _recipeRepository.CreateRecipeForCategoryAsync(categoryEntity, name, description, difficulty, servings, preparationTime, cookingTime);
var recipe = await _recipeRepository.CreateRecipeForCategoryAsync(categoryEntity, name, description, difficulty, servings, preparationTime, cookingTime);
if (photo != null)
{
await _mediaFileRepository.UploadRecipeMediaAsync(recipe.Id, photo);
}
return RedirectToAction("Details", "Category", new { id = categoryId });
}
// GET: /categories/{categoryId}/Recipe/{recipeId}/RemoveIngredient/{ingredientId}
// GET: /Recipe/{recipeId}/RemoveIngredient/{ingredientId}
[HttpGet("{recipeId}/RemoveIngredient/{ingredientId}")]
public async Task<IActionResult> RemoveIngredient(Guid categoryId, Guid recipeId, Guid ingredientId)
public async Task<IActionResult> RemoveIngredient(Guid recipeId, Guid ingredientId)
{
var recipe = await _recipeRepository.GetRecipeAsync(recipeId);
var ingredient = await _ingredientRepository.GetIngredientByIdAsync(ingredientId);
@@ -121,28 +176,87 @@
ViewBag.RecipeId = recipeId;
ViewBag.IngredientId = ingredientId;
ViewBag.CategoryId = categoryId;
ViewBag.IngredientName = ingredient.Name;
return View();
}
// POST: /categories/{categoryId}/Recipe/{recipeId}/RemoveIngredient/{ingredientId}
// POST: /Recipe/{recipeId}/RemoveIngredient/{ingredientId}
[HttpPost("{recipeId}/RemoveIngredient/{ingredientId}")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveIngredientConfirmed(Guid categoryId, Guid recipeId, Guid ingredientId)
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: /categories/{categoryId}/Recipe/FilterByDifficulty
// GET: /Recipe/FilterByDifficulty
[HttpGet("FilterByDifficulty")]
public async Task<IActionResult> FilterByDifficulty(Difficulty? difficulty)
public async Task<IActionResult> FilterByDifficulty(Difficulty? selectedDifficulty)
{
Recipes = await _recipeRepository.GetRecipesByDifficultyAsync(difficulty);
return View(Recipes);
var recipes = await _recipeRepository.GetRecipesByDifficultyAsync(selectedDifficulty);
var viewModel = new FilterByDifficultyViewModel
{
SelectedDifficulty = selectedDifficulty,
Recipes = recipes,
};
return View(viewModel);
}
// GET: /Recipe/{recipeId}/AddInstruction
[HttpGet("{recipeId}/AddInstruction")]
public async Task<IActionResult> AddInstruction(Guid recipeId)
{
var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId);
if (recipe == null)
{
return NotFound("Recipe not found.");
}
ViewBag.RecipeId = recipeId;
return View(recipe);
}
// POST: /Recipe/{recipeId}/AddInstruction
[HttpPost("{recipeId}/AddInstruction")]
public async Task<IActionResult> AddInstruction(Guid recipeId, string description, int number)
{
try
{
await _instructionRepository.CreateInstructionToRecipeAsync(recipeId, description, number);
return RedirectToAction("AddInstruction", new { recipeId });
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, ex.Message);
var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId);
ViewBag.RecipeId = recipeId;
return View(recipe);
}
}
// GET: /Recipe/Favorites
[HttpGet("Favorites")]
public async Task<IActionResult> Favorites()
{
var favoriteRecipes = await _favoritRepository.GetFavoriteRecipesAsync();
return View(favoriteRecipes);
}
// POST: /Recipe/AddFavorite
[HttpPost("AddFavorite")]
public async Task<IActionResult> AddFavorite(Guid recipeId)
{
await _favoritRepository.AddFavoriteAsync(recipeId);
return RedirectToAction("Details", new { recipeId });
}
// POST: /Recipe/RemoveFavorite
[HttpPost("RemoveFavorite")]
public async Task<IActionResult> RemoveFavorite(Guid recipeId)
{
await _favoritRepository.RemoveFavoriteAsync(recipeId);
return RedirectToAction("Details", new { recipeId });
}
}
}
@@ -1,6 +1,50 @@
namespace Francesco.Recipes.World.Controller.ShoppingList
namespace Francesco.Recipes.World.Controllers
{
public class ShoppingListController
using Francesco.Recipes.World.Data;
using Francesco.Recipes.World.Repositories.ShoppingList;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
[Route("ShoppingList")]
public class ShoppingListController : Controller
{
private readonly IShoppingListRepository _shoppingListRepository;
private readonly FrancescosRecipesWorldDbContext _context;
public ShoppingListController(IShoppingListRepository shoppingListRepository, FrancescosRecipesWorldDbContext context)
{
_shoppingListRepository = shoppingListRepository;
_context = context;
}
[HttpPost("CreateOrAddIngredients")]
public async Task<IActionResult> CreateOrAddIngredients([FromBody] CreateOrAddIngredientsRequest request)
{
if (request == null || request.IngredientIds == null || !request.IngredientIds.Any())
{
return BadRequest("No ingredients provided.");
}
await _shoppingListRepository.AddIngredientsToShoppingListAsync(request.RecipeId, request.IngredientIds);
var shoppingList = await _context.ShoppingLists
.Include(sl => sl.RecipeShoppingList)
.ThenInclude(rsl => rsl.Recipe)
.FirstOrDefaultAsync(sl => sl.RecipeShoppingList.Any(rsl => rsl.Recipe.Id == request.RecipeId));
if (shoppingList == null)
{
return BadRequest("Error creating shopping list.");
}
return Json(new { shoppingListId = shoppingList.Id });
}
public class CreateOrAddIngredientsRequest
{
public Guid RecipeId { get; set; }
public List<Guid> IngredientIds { get; set; } = new ();
}
}
}
@@ -9,7 +9,8 @@
using Francesco.Recipes.World.Models.BackendModels.MediaFile;
using Francesco.Recipes.World.Models.BackendModels.Recipe;
using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient;
using Francesco.Recipes.World.Models.BackendModels.Shoppinglist;
using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList;
using Francesco.Recipes.World.Models.BackendModels.Shoppinglist;
using Francesco.Recipes.World.Models.BackendModels.Unit;
using Microsoft.EntityFrameworkCore;
@@ -36,6 +37,8 @@
public DbSet<RecipeIngredientShoppingList> RecipeIngredientsShoppingLists => Set<RecipeIngredientShoppingList>();
public DbSet<RecipeShoppingList> RecipeShoppingLists => Set<RecipeShoppingList>();
public DbSet<ShoppingList> ShoppingLists => Set<ShoppingList>();
public DbSet<MediaFile> MediaFiles => Set<MediaFile>();
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
@@ -45,5 +45,13 @@
<ItemGroup>
<AdditionalFiles Include=".\stylecop.json" />
</ItemGroup>
<ItemGroup>
<Folder Include="Services\Category\" />
<Folder Include="Services\MediaFile\" />
<Folder Include="Services\Ingredient\" />
<Folder Include="Services\Instruction\" />
<Folder Include="Services\ShoppingList\" />
<Folder Include="Services\Recipe\" />
</ItemGroup>
</Project>
@@ -0,0 +1,572 @@
// <auto-generated />
using Francesco.Recipes.World.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Francesco.Recipes.World.Migrations
{
[DbContext(typeof(FrancescosRecipesWorldDbContext))]
[Migration("20250402120842_UpdateShoppingLIstLogic")]
partial class UpdateShoppingLIstLogic
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.11")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Categories");
b.HasData(
new
{
Id = new Guid("b248244f-f21c-4555-a14d-5dd49a2717cf"),
Name = "Vorspeisen & Snacks"
},
new
{
Id = new Guid("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"),
Name = "Erste Gänge"
},
new
{
Id = new Guid("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"),
Name = "Hauptgerichte"
},
new
{
Id = new Guid("90deec39-dcd0-422d-9018-ac8389e332e1"),
Name = "Desserts & Süßspeisen"
},
new
{
Id = new Guid("28e39168-701a-4084-81da-d96c987c462f"),
Name = "Beilagen & Salate"
},
new
{
Id = new Guid("20585b74-4805-4aff-a6df-aa6b7af04ff1"),
Name = "Kuchen"
},
new
{
Id = new Guid("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"),
Name = "Hefegebäck & Brot"
},
new
{
Id = new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"),
Name = "Soßen & Saucen"
},
new
{
Id = new Guid("23b1c740-e427-44f9-a6ea-d33d3f30f05a"),
Name = "Marmeladen & Eingemachtes"
},
new
{
Id = new Guid("0a91a200-dc76-4e00-b38c-b38cab5b69d7"),
Name = "Getränke"
});
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.ToTable("Favorits");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Ingredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("IngredientId")
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsChecked")
.HasColumnType("bit");
b.Property<Guid>("RecipeIngredientId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("RecipeShoppingListId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("IngredientId");
b.HasIndex("RecipeIngredientId");
b.HasIndex("RecipeShoppingListId");
b.ToTable("RecipeIngredientsShoppingLists");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("Number")
.HasColumnType("int");
b.Property<Guid>("RecipeId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("RecipeId");
b.ToTable("Instructions");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<byte[]>("Data")
.HasColumnType("varbinary(max)");
b.Property<string>("FileName")
.HasColumnType("nvarchar(max)");
b.Property<Guid>("InstructionId")
.HasColumnType("uniqueidentifier");
b.Property<string>("MimeType")
.HasColumnType("nvarchar(max)");
b.Property<Guid?>("RecipeId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("InstructionId");
b.HasIndex("RecipeId");
b.ToTable("MediaFiles");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("CategoryId")
.HasColumnType("uniqueidentifier");
b.Property<TimeSpan>("CookingTime")
.HasColumnType("time");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Description")
.HasColumnType("nvarchar(max)");
b.Property<int>("Difficulty")
.HasColumnType("int");
b.Property<Guid>("FavoritId")
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsFavorite")
.HasColumnType("bit");
b.Property<DateTime?>("ModifiedAt")
.HasColumnType("datetime2");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<TimeSpan>("PreparationTime")
.HasColumnType("time");
b.Property<int>("Servings")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("CategoryId");
b.HasIndex("FavoritId");
b.ToTable("Recipes");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("IngredientId")
.HasColumnType("uniqueidentifier");
b.Property<int>("Quantity")
.HasColumnType("int");
b.Property<Guid>("RecipeId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("UnitId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("IngredientId");
b.HasIndex("RecipeId");
b.HasIndex("UnitId");
b.ToTable("RecipeIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("RecipeId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("ShoppingListId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("RecipeId");
b.HasIndex("ShoppingListId");
b.ToTable("RecipeShoppingLists");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<DateTime?>("ModifiedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.ToTable("ShoppingLists");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Symbol")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Units");
b.HasData(
new
{
Id = new Guid("62eb2c11-8768-46fb-9db0-c77f940bb4aa"),
Name = "liter",
Symbol = "l"
},
new
{
Id = new Guid("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"),
Name = "gramm",
Symbol = "g"
},
new
{
Id = new Guid("7e3d1b86-ac48-45d6-814e-d3492a86db1d"),
Name = "kilogramm",
Symbol = "kg"
},
new
{
Id = new Guid("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"),
Name = "stücke",
Symbol = "stk"
},
new
{
Id = new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"),
Name = "blatt",
Symbol = "blatt"
},
new
{
Id = new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"),
Name = "messerspitze",
Symbol = "msp"
},
new
{
Id = new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"),
Name = "stange",
Symbol = "stange"
},
new
{
Id = new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"),
Name = "bund",
Symbol = "bund"
},
new
{
Id = new Guid("66556e0e-eb2b-4bc3-9a56-135dd508ed09"),
Name = "zehe",
Symbol = "zehe"
},
new
{
Id = new Guid("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"),
Name = "teelöffel",
Symbol = "TL"
},
new
{
Id = new Guid("24a91b89-3389-4465-89e4-2f70d2ea6fd7"),
Name = "esslöffel",
Symbol = "EL"
});
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", null)
.WithMany("IngredientShoppingLists")
.HasForeignKey("IngredientId");
b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", "RecipeIngredient")
.WithMany()
.HasForeignKey("RecipeIngredientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", "RecipeShoppingList")
.WithMany("SelectedIngredients")
.HasForeignKey("RecipeShoppingListId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("RecipeIngredient");
b.Navigation("RecipeShoppingList");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
.WithMany("Instructions")
.HasForeignKey("RecipeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Recipe");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", "Instruction")
.WithMany("MediaFiles")
.HasForeignKey("InstructionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
.WithMany("MediaFiles")
.HasForeignKey("RecipeId");
b.Navigation("Instruction");
b.Navigation("Recipe");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", "Category")
.WithMany("Recipes")
.HasForeignKey("CategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorit")
.WithMany("Recipe")
.HasForeignKey("FavoritId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Category");
b.Navigation("Favorit");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient")
.WithMany("RecipeIngredients")
.HasForeignKey("IngredientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
.WithMany("RecipeIngredients")
.HasForeignKey("RecipeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit")
.WithMany("RecipeIngredient")
.HasForeignKey("UnitId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ingredient");
b.Navigation("Recipe");
b.Navigation("Unit");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
.WithMany()
.HasForeignKey("RecipeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList")
.WithMany("RecipeIngredientShoppingLists")
.HasForeignKey("ShoppingListId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Recipe");
b.Navigation("ShoppingList");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
{
b.Navigation("Recipes");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b =>
{
b.Navigation("Recipe");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
{
b.Navigation("IngredientShoppingLists");
b.Navigation("RecipeIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
{
b.Navigation("MediaFiles");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
{
b.Navigation("Instructions");
b.Navigation("MediaFiles");
b.Navigation("RecipeIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
{
b.Navigation("SelectedIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
{
b.Navigation("RecipeIngredientShoppingLists");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
{
b.Navigation("RecipeIngredient");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,162 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Francesco.Recipes.World.Migrations
{
/// <inheritdoc />
public partial class UpdateShoppingLIstLogic : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder is null)
{
throw new ArgumentNullException(nameof(migrationBuilder));
}
migrationBuilder.DropForeignKey(
name: "FK_RecipeIngredientsShoppingLists_ShoppingLists_ShoppingListId",
table: "RecipeIngredientsShoppingLists");
migrationBuilder.DropForeignKey(
name: "FK_Recipes_Categories_CategoryId",
table: "Recipes");
migrationBuilder.RenameColumn(
name: "ShoppingListId",
table: "RecipeIngredientsShoppingLists",
newName: "RecipeShoppingListId");
migrationBuilder.RenameIndex(
name: "IX_RecipeIngredientsShoppingLists_ShoppingListId",
table: "RecipeIngredientsShoppingLists",
newName: "IX_RecipeIngredientsShoppingLists_RecipeShoppingListId");
migrationBuilder.AlterColumn<Guid>(
name: "CategoryId",
table: "Recipes",
type: "uniqueidentifier",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
oldClrType: typeof(Guid),
oldType: "uniqueidentifier",
oldNullable: true);
migrationBuilder.AddColumn<bool>(
name: "IsChecked",
table: "RecipeIngredientsShoppingLists",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.CreateTable(
name: "RecipeShoppingLists",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ShoppingListId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
RecipeId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
},
constraints: table =>
{
table.PrimaryKey("PK_RecipeShoppingLists", x => x.Id);
table.ForeignKey(
name: "FK_RecipeShoppingLists_Recipes_RecipeId",
column: x => x.RecipeId,
principalTable: "Recipes",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_RecipeShoppingLists_ShoppingLists_ShoppingListId",
column: x => x.ShoppingListId,
principalTable: "ShoppingLists",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_RecipeShoppingLists_RecipeId",
table: "RecipeShoppingLists",
column: "RecipeId");
migrationBuilder.CreateIndex(
name: "IX_RecipeShoppingLists_ShoppingListId",
table: "RecipeShoppingLists",
column: "ShoppingListId");
migrationBuilder.AddForeignKey(
name: "FK_RecipeIngredientsShoppingLists_RecipeShoppingLists_RecipeShoppingListId",
table: "RecipeIngredientsShoppingLists",
column: "RecipeShoppingListId",
principalTable: "RecipeShoppingLists",
principalColumn: "Id",
onDelete: ReferentialAction.NoAction);
migrationBuilder.AddForeignKey(
name: "FK_Recipes_Categories_CategoryId",
table: "Recipes",
column: "CategoryId",
principalTable: "Categories",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
if (migrationBuilder is null)
{
throw new ArgumentNullException(nameof(migrationBuilder));
}
migrationBuilder.DropForeignKey(
name: "FK_RecipeIngredientsShoppingLists_RecipeShoppingLists_RecipeShoppingListId",
table: "RecipeIngredientsShoppingLists");
migrationBuilder.DropForeignKey(
name: "FK_Recipes_Categories_CategoryId",
table: "Recipes");
migrationBuilder.DropTable(
name: "RecipeShoppingLists");
migrationBuilder.DropColumn(
name: "IsChecked",
table: "RecipeIngredientsShoppingLists");
migrationBuilder.RenameColumn(
name: "RecipeShoppingListId",
table: "RecipeIngredientsShoppingLists",
newName: "ShoppingListId");
migrationBuilder.RenameIndex(
name: "IX_RecipeIngredientsShoppingLists_RecipeShoppingListId",
table: "RecipeIngredientsShoppingLists",
newName: "IX_RecipeIngredientsShoppingLists_ShoppingListId");
migrationBuilder.AlterColumn<Guid>(
name: "CategoryId",
table: "Recipes",
type: "uniqueidentifier",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uniqueidentifier");
migrationBuilder.AddForeignKey(
name: "FK_RecipeIngredientsShoppingLists_ShoppingLists_ShoppingListId",
table: "RecipeIngredientsShoppingLists",
column: "ShoppingListId",
principalTable: "ShoppingLists",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Recipes_Categories_CategoryId",
table: "Recipes",
column: "CategoryId",
principalTable: "Categories",
principalColumn: "Id");
}
}
}
@@ -1,7 +1,10 @@
// <auto-generated />
using System;
using Francesco.Recipes.World.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
@@ -124,10 +127,13 @@ namespace Francesco.Recipes.World.Migrations
b.Property<Guid?>("IngredientId")
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsChecked")
.HasColumnType("bit");
b.Property<Guid>("RecipeIngredientId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("ShoppingListId")
b.Property<Guid>("RecipeShoppingListId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
@@ -136,7 +142,7 @@ namespace Francesco.Recipes.World.Migrations
b.HasIndex("RecipeIngredientId");
b.HasIndex("ShoppingListId");
b.HasIndex("RecipeShoppingListId");
b.ToTable("RecipeIngredientsShoppingLists");
});
@@ -200,7 +206,7 @@ namespace Francesco.Recipes.World.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("CategoryId")
b.Property<Guid>("CategoryId")
.HasColumnType("uniqueidentifier");
b.Property<TimeSpan>("CookingTime")
@@ -272,6 +278,27 @@ namespace Francesco.Recipes.World.Migrations
b.ToTable("RecipeIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
{
b.Property<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")
@@ -388,15 +415,15 @@ namespace Francesco.Recipes.World.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList")
.WithMany("RecipeIngredientShoppingLists")
.HasForeignKey("ShoppingListId")
b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", "RecipeShoppingList")
.WithMany("SelectedIngredients")
.HasForeignKey("RecipeShoppingListId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("RecipeIngredient");
b.Navigation("ShoppingList");
b.Navigation("RecipeShoppingList");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
@@ -429,9 +456,11 @@ namespace Francesco.Recipes.World.Migrations
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", null)
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", "Category")
.WithMany("Recipes")
.HasForeignKey("CategoryId");
.HasForeignKey("CategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorit")
.WithMany("Recipe")
@@ -439,6 +468,8 @@ namespace Francesco.Recipes.World.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Category");
b.Navigation("Favorit");
});
@@ -469,6 +500,25 @@ namespace Francesco.Recipes.World.Migrations
b.Navigation("Unit");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
{
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
.WithMany()
.HasForeignKey("RecipeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList")
.WithMany("RecipeIngredientShoppingLists")
.HasForeignKey("ShoppingListId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Recipe");
b.Navigation("ShoppingList");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
{
b.Navigation("Recipes");
@@ -500,6 +550,11 @@ namespace Francesco.Recipes.World.Migrations
b.Navigation("RecipeIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
{
b.Navigation("SelectedIngredients");
});
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
{
b.Navigation("RecipeIngredientShoppingLists");
@@ -1,15 +1,22 @@
namespace Francesco.Recipes.World.Models.BackendModels.Recipe
using System.ComponentModel.DataAnnotations;
namespace Francesco.Recipes.World.Models.BackendModels.Recipe
{
public enum Difficulty
{
[Display(Name = "Sehr einfach")]
VeryEasy = 0,
[Display(Name = "Einfach")]
Easy = 1,
[Display(Name = "Mittel")]
Medium = 2,
[Display(Name = "Schwer")]
Hard = 3,
[Display(Name = "Experte")]
Expert = 4,
}
}
@@ -1,14 +1,16 @@
namespace Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList
{
using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient;
using Francesco.Recipes.World.Models.BackendModels.Shoppinglist;
using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList;
public class RecipeIngredientShoppingList
{
public Guid Id { get; set; }
public virtual ShoppingList ShoppingList { get; set; } = new ();
public virtual RecipeShoppingList RecipeShoppingList { get; set; } = new ();
public virtual RecipeIngredient RecipeIngredient { get; set; } = new ();
public bool IsChecked { get; set; } = false;
}
}
@@ -0,0 +1,17 @@
namespace Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList
{
using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList;
using Francesco.Recipes.World.Models.BackendModels.Recipe;
using Francesco.Recipes.World.Models.BackendModels.Shoppinglist;
public class RecipeShoppingList
{
public Guid Id { get; set; }
public virtual ShoppingList ShoppingList { get; set; } = new ShoppingList();
public virtual Recipe Recipe { get; set; } = new ();
public virtual ICollection<RecipeIngredientShoppingList> SelectedIngredients { get; set; } = new List<RecipeIngredientShoppingList>();
}
}
@@ -1,6 +1,6 @@
namespace Francesco.Recipes.World.Models.BackendModels.Shoppinglist
{
using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList;
using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList;
public class ShoppingList : ITimeStampedEntity
{
@@ -10,6 +10,6 @@
public DateTime? ModifiedAt { get; set; }
public virtual ICollection<RecipeIngredientShoppingList> RecipeIngredientShoppingLists { get; set; } = new List<RecipeIngredientShoppingList>();
public virtual ICollection<RecipeShoppingList> RecipeShoppingList { get; set; } = new List<RecipeShoppingList>();
}
}
+3 -9
View File
@@ -1,5 +1,6 @@
using Francesco.Recipes.World.Data;
using Francesco.Recipes.World.Repositories.Category;
using Francesco.Recipes.World.Repositories.Favorit;
using Francesco.Recipes.World.Repositories.Ingredient;
using Francesco.Recipes.World.Repositories.Instruction;
using Francesco.Recipes.World.Repositories.MediaFile;
@@ -7,8 +8,6 @@ using Francesco.Recipes.World.Repositories.Recipe;
using Francesco.Recipes.World.Repositories.ShoppingList;
using Francesco.Recipes.World.Repositories.Unit;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
@@ -23,9 +22,6 @@ var connectionString = builder.Configuration.GetConnectionString("FrancescosReci
services.AddDbContext<FrancescosRecipesWorldDbContext>(options =>
options.UseSqlServer(connectionString));
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<FrancescosRecipesWorldDbContext>();
// Add services to the container.
builder.Services.AddControllersWithViews();
@@ -43,6 +39,8 @@ builder.Services.AddScoped<IMediaFileRepository, MediaFileRepository>();
builder.Services.AddScoped<IInstructionRepository, InstructionRepository>();
builder.Services.AddScoped<IFavoritRepository, FavoritRepository>();
var app = builder.Build();
// Configure the HTTP request pipeline.
@@ -62,10 +60,6 @@ app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapControllerRoute(
@@ -0,0 +1,12 @@
namespace Francesco.Recipes.World.Views.Category
{
using Francesco.Recipes.World.Models.BackendModels.Category;
using Francesco.Recipes.World.Models.BackendModels.Recipe;
public class CategoryRecipesViewModel
{
public Category Category { get; set; } = new ();
public IEnumerable<Recipe> Recipes { get; set; } = new List<Recipe>();
}
}
@@ -0,0 +1,59 @@
@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe
@{
ViewData["Title"] = "Add Instructions";
}
<h1>Add Instructions to @Model.Name</h1>
<div class="instructions-list">
<h3>Existing Instructions</h3>
<ul>
@foreach (var instruction in Model.Instructions.OrderBy(i => i.Number))
{
<li>@instruction.Number. @instruction.Description</li>
}
</ul>
</div>
<div class="add-instruction-form">
<h3>Add New Instruction</h3>
<form id="instruction-form" method="post" asp-action="AddInstruction" asp-controller="Recipe">
<input type="hidden" name="recipeId" value="@Model.Id" />
<div class="form-group">
<label for="description">Description</label>
<input type="text" class="form-control" id="description" name="description" required />
</div>
<div class="form-group">
<label for="number">Number</label>
<input type="number" class="form-control" id="number" name="number" required min="1" />
</div>
<button type="submit" class="btn btn-primary" onclick="addInstructionToRecipe()">Add Instruction</button>
</form>
</div>
@section Scripts {
<script>
async function addInstructionToRecipe() {
var form = document.getElementById('instruction-form');
var recipeId = form.querySelector('input[name="recipeId"]').value;
var description = form.querySelector('input[name="description"]').value;
var number = form.querySelector('input[name="number"]').value;
var response = await fetch('/Recipe/' + recipeId + '/AddInstruction', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ recipeId: recipeId, description: description, number: number })
});
if (response.ok) {
location.reload();
} else {
alert('Failed to add instruction.');
}
}
</script>
}
@@ -1,29 +1,27 @@
@{
ViewData["Title"] = "Add or Create Ingredient to Recipe";
ViewData["Title"] = "Add or Create Ingredient to Recipe";
}
<h2>@ViewData["Title"]</h2>
<form asp-action="AddOrCreateIngredient" method="post">
<div class="form-group">
<label for="RecipeId" class="control-label">Recipe Id</label>
<input type="text" id="RecipeId" name="recipeId" class="form-control" />
</div>
<div class="form-group">
<label for="IngredientName" class="control-label">Ingredient Name</label>
<input type="text" id="IngredientName" name="ingredientName" class="form-control" />
</div>
<div class="form-group">
<label for="Quantity" class="control-label">Quantity</label>
<input type="number" id="Quantity" name="quantity" class="form-control" />
</div>
<div class="form-group">
<label for="UnitId" class="control-label">Unit</label>
<select id="UnitId" name="unitId" class="form-control" asp-items="ViewBag.Units"></select>
</div>
<button type="submit" class="btn btn-primary">Add Ingredient</button>
<input type="hidden" id="RecipeId" name="recipeId" value="@ViewBag.RecipeId" />
<div class="form-group">
<label for="IngredientName" class="control-label">Ingredient Name</label>
<input type="text" id="IngredientName" name="ingredientName" class="form-control" />
</div>
<div class="form-group">
<label for="Quantity" class="control-label">Quantity</label>
<input type="number" id="Quantity" name="quantity" class="form-control" />
</div>
<div class="form-group">
<label for="UnitId" class="control-label">Unit</label>
<select id="UnitId" name="unitId" class="form-control" asp-items="ViewBag.Units"></select>
</div>
<button type="submit" class="btn btn-primary">Add Ingredient</button>
</form>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
<partial name="_ValidationScriptsPartial" />
}
@@ -0,0 +1,98 @@
@model IEnumerable<Francesco.Recipes.World.Views.Category.CategoryRecipesViewModel>
@{
ViewData["Title"] = "Category Recipes";
}
<h1>Category Recipes</h1>
@foreach (var categoryRecipes in Model)
{
<div class="category-section">
<h2>@categoryRecipes.Category.Name</h2>
<a asp-action="Create" asp-route-categoryId="@categoryRecipes.Category.Id" class="btn btn-primary">Rezept erstellen</a>
<div class="recipes">
@foreach (var recipe in categoryRecipes.Recipes)
{
<div class="recipe-card">
<div class="recipe-image">
@if (recipe.MediaFiles.Any() && recipe.MediaFiles.First().Data != null)
{
var mediaFile = recipe.MediaFiles.First();
if (mediaFile.Data != null)
{
<img src="data:@mediaFile.MimeType;base64,@Convert.ToBase64String(mediaFile.Data)" alt="@recipe.Name" class="img-fluid" />
}
}
</div>
<div class="recipe-info">
<h3>@recipe.Name</h3>
<p>@recipe.Description</p>
<p><strong>Difficulty:</strong> @recipe.Difficulty</p>
<p><strong>Servings:</strong> @recipe.Servings</p>
<p><strong>Preparation Time:</strong> @recipe.PreparationTime</p>
<p><strong>Cooking Time:</strong> @recipe.CookingTime</p>
<div class="recipe-favorite">
@if (recipe.IsFavorite)
{
<form method="post" asp-action="RemoveFavorite" asp-controller="Recipe">
<input type="hidden" name="recipeId" value="@recipe.Id" />
<button type="submit" class="btn btn-danger">Remove from Favorites</button>
</form>
}
else
{
<form method="post" asp-action="AddFavorite" asp-controller="Recipe">
<input type="hidden" name="recipeId" value="@recipe.Id" />
<button type="submit" class="btn btn-primary">Add to Favorites</button>
</form>
}
</div>
</div>
</div>
}
<div class="recipe-card add-recipe-card">
<a asp-action="Create" asp-route-categoryId="@categoryRecipes.Category.Id" class="btn btn-primary">Rezept hinzufügen</a>
</div>
</div>
</div>
}
<style>
.category-section {
margin-bottom: 2rem;
}
.recipes {
display: flex;
flex-wrap: wrap;
}
.recipe-card {
width: 200px;
margin: 1rem;
padding: 1rem;
border: 1px solid #ccc;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.recipe-image img {
width: 100%;
height: auto;
border-radius: 8px;
}
.recipe-info {
margin-top: 1rem;
}
.add-recipe-card {
display: flex;
align-items: center;
justify-content: center;
background-color: #f8f8f8;
border: 2px dashed #ccc;
}
</style>
@@ -1,45 +1,53 @@
@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe
@using Francesco.Recipes.World.Models.BackendModels.Recipe
@{
ViewData["Title"] = "Create Recipe";
ViewData["Title"] = "Create Recipe";
}
<h1>Create Recipe</h1>
<form asp-action="Create" method="post">
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Description" class="control-label"></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" class="control-label"></label>
<select asp-for="Difficulty" class="form-control" asp-items="Html.GetEnumSelectList<Difficulty>()"></select>
<span asp-validation-for="Difficulty" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Servings" class="control-label"></label>
<input asp-for="Servings" class="form-control" />
<span asp-validation-for="Servings" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PreparationTime" class="control-label"></label>
<input asp-for="PreparationTime" class="form-control" placeholder="hh:mm:ss" />
<span asp-validation-for="PreparationTime" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="CookingTime" class="control-label"></label>
<input asp-for="CookingTime" class="form-control" placeholder="hh:mm:ss" />
<span asp-validation-for="CookingTime" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
<form method="post" enctype="multipart/form-data">
<div class="form-group">
<label asp-for="Name"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Description"></label>
<textarea asp-for="Description" class="form-control"></textarea>
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Difficulty"></label>
<select asp-for="Difficulty" class="form-control">
<option value="">Select Difficulty</option>
@foreach (var difficulty in Enum.GetValues(typeof(Difficulty)))
{
<option value="@difficulty">@difficulty</option>
}
</select>
<span asp-validation-for="Difficulty" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Servings"></label>
<input asp-for="Servings" class="form-control" />
<span asp-validation-for="Servings" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PreparationTime"></label>
<input asp-for="PreparationTime" class="form-control" />
<span asp-validation-for="PreparationTime" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="CookingTime"></label>
<input asp-for="CookingTime" class="form-control" />
<span asp-validation-for="CookingTime" class="text-danger"></span>
</div>
<div class="form-group">
<label for="photo">Photo</label>
<input type="file" name="photo" class="form-control" />
</div>
<button type="submit" class="btn btn-primary">Create</button>
</form>
@section Scripts {
@@ -0,0 +1,68 @@
@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe
@{
ViewData["Title"] = "Recipe Details";
}
<h1>@Model.Name</h1>
<div class="recipe-details">
<div class="recipe-image">
@if (Model.MediaFiles.Any() && Model.MediaFiles.First().Data != null)
{
var mediaFile = Model.MediaFiles.First();
if (mediaFile.Data != null)
{
<img src="data:@mediaFile.MimeType;base64,@Convert.ToBase64String(mediaFile.Data)" alt="@Model.Name" class="img-fluid" />
}
}
</div>
<div class="recipe-info">
<p><strong>Description:</strong> @Model.Description</p>
<p><strong>Difficulty:</strong> @Model.Difficulty</p>
<p><strong>Servings:</strong> @Model.Servings</p>
<p><strong>Preparation Time:</strong> @Model.PreparationTime</p>
<p><strong>Cooking Time:</strong> @Model.CookingTime</p>
</div>
<div class="recipe-ingredients">
<h3>Ingredients</h3>
<form id="ingredient-form">
<ul id="ingredient-list">
@foreach (var ingredient in Model.RecipeIngredients)
{
<li id="ingredient-@ingredient.Id">
<input type="checkbox" name="ingredientIds" value="@ingredient.Id" />
@ingredient.Ingredient.Name - @ingredient.Quantity @(ingredient.Unit != null ? ingredient.Unit.Symbol : string.Empty)
</li>
}
</ul>
<button type="button" class="btn btn-primary" onclick="addSelectedIngredientsToShoppingList()">Add Selected to Shopping List</button>
</form>
</div>
</div>
@section Scripts {
<script>
async function addSelectedIngredientsToShoppingList() {
var form = document.getElementById('ingredient-form');
var formData = new FormData(form);
var selectedIngredientIds = formData.getAll('ingredientIds');
var response = await fetch('/ShoppingList/CreateOrAddIngredients', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ recipeId: '@Model.Id', ingredientIds: selectedIngredientIds })
});
if (response.ok) {
var result = await response.json();
localStorage.setItem('shoppingListId', result.shoppingListId);
alert('Shopping list updated.');
} else {
alert('Failed to update shopping list.');
}
}
</script>
}
@@ -1,42 +1,72 @@
@model Francesco.Recipes.World.Views.Recipe.FilterByDifficultyViewModel
@using Francesco.Recipes.World.Models.BackendModels.Recipe
@using Francesco.Recipes.World.Models.BackendModels.Recipe
@model Francesco.Recipes.World.Views.Recipe.FilterByDifficultyViewModel
@{
ViewData["Title"] = "Filter Recipes by Difficulty";
}
<h1>Rezepte nach Schwierigkeitsgrad</h1>
<h1>Filter Recipes by Difficulty</h1>
<div class="mb-3">
<a asp-controller="Recipe" asp-action="Create" class="btn btn-primary">Neues Rezept erstellen</a>
</div>
<form asp-action="FilterByDifficulty" method="get" id="filterForm">
<div class="form-group">
<label asp-for="SelectedDifficulty" class="control-label"></label>
<select asp-for="SelectedDifficulty" class="form-control" asp-items="Html.GetEnumSelectList<Difficulty>()" class="form-select" onchange="submitForm()">
<option value="">All Difficulties</option>
</select>
<span asp-validation-for="SelectedDifficulty" class="text-danger"></span>
</div>
</form>
<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>
@if (Model.Recipes != null && Model.Recipes.Any())
{
<h2>Filtered Recipes</h2>
<ul>
@foreach (var recipe in Model.Recipes)
{
<li>@recipe.Name - @recipe.Difficulty.</li>
}
</ul>
}
else
{
<p>No recipes found for the selected difficulty.</p>
}
</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>
@await Html.PartialAsync("_ValidationScriptsPartial")
<script>
function submitForm() {
document.getElementById('filterForm').submit();
}
</script>
}
@@ -7,6 +7,6 @@
{
public Difficulty? SelectedDifficulty { get; set; }
public List<Recipe> Recipes { get; set; } = new List<Recipe>();
public IReadOnlyCollection<Recipe> Recipes { get; set; } = new List<Recipe>();
}
}
@@ -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,56 @@
@model Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList
@using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList
@using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient
@using Francesco.Recipes.World.Models.BackendModels.Recipe
@{
ViewData["Title"] = "Einkaufsliste Details";
}
<h2>Einkaufsliste Details</h2>
@if (TempData["SuccessMessage"] != null)
{
<div class="alert alert-success">
@TempData["SuccessMessage"]
</div>
}
<div>
<h4>Einkaufsliste</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
ID
</dt>
<dd class="col-sm-10">
@Model.Id
</dd>
</dl>
</div>
<h4>Rezepte</h4>
<table class="table">
<thead>
<tr>
<th>Rezeptname</th>
<th>Zutaten</th>
</tr>
</thead>
<tbody>
@foreach (var recipeShoppingList in Model.RecipeShoppingList)
{
<tr>
<td>@recipeShoppingList.Recipe.Name</td>
<td>
<ul>
@foreach (var ingredient in recipeShoppingList.SelectedIngredients)
{
<li>@ingredient.RecipeIngredient.Ingredient.Name - @ingredient.RecipeIngredient.Quantity @ingredient.RecipeIngredient.Unit.Name</li>
}
</ul>
</td>
</tr>
}
</tbody>
</table>