From 81384ceb552a6f5a1d4e13d3bcf5d92474b5a1e5 Mon Sep 17 00:00:00 2001 From: Jeas0001 Date: Tue, 29 Apr 2025 12:31:40 +0200 Subject: [PATCH] ShoppingList endpoints --- backend/API/BusinessLogic/RecipeLogic.cs | 35 ++- .../API/BusinessLogic/ShoppingListLogic.cs | 209 ++++++++++++++++++ backend/API/BusinessLogic/UserLogic.cs | 2 + backend/API/Controllers/RecipeController.cs | 4 +- .../API/Controllers/ShoppingListController.cs | 74 +++++++ backend/API/DBAccess/DBContext.cs | 3 + backend/API/DBAccess/RecipeDBaccess.cs | 4 +- backend/API/DBAccess/ShoppingListDBAccess.cs | 35 +++ backend/API/DBAccess/UserDBAccess.cs | 2 +- .../API/Models/RecipeModels/IngredientDTO.cs | 11 + .../RecipeModels/PrefereredRecipesDTO.cs | 7 + backend/API/Models/RecipeModels/RecipeDTO.cs | 13 ++ .../Models/ShoppingListModels/ShoppingList.cs | 15 ++ .../ShoppingListModels/ShoppingListItemDTO.cs | 14 ++ backend/API/Models/UserModels/User.cs | 3 + backend/API/Startup.cs | 2 +- 16 files changed, 419 insertions(+), 14 deletions(-) create mode 100644 backend/API/BusinessLogic/ShoppingListLogic.cs create mode 100644 backend/API/Controllers/ShoppingListController.cs create mode 100644 backend/API/DBAccess/ShoppingListDBAccess.cs create mode 100644 backend/API/Models/RecipeModels/IngredientDTO.cs create mode 100644 backend/API/Models/RecipeModels/PrefereredRecipesDTO.cs create mode 100644 backend/API/Models/RecipeModels/RecipeDTO.cs create mode 100644 backend/API/Models/ShoppingListModels/ShoppingList.cs create mode 100644 backend/API/Models/ShoppingListModels/ShoppingListItemDTO.cs diff --git a/backend/API/BusinessLogic/RecipeLogic.cs b/backend/API/BusinessLogic/RecipeLogic.cs index eb32f5f..fb101f0 100644 --- a/backend/API/BusinessLogic/RecipeLogic.cs +++ b/backend/API/BusinessLogic/RecipeLogic.cs @@ -6,13 +6,11 @@ namespace API.BusinessLogic { public class RecipeLogic { - private readonly RecipeDBaccess _dbAccess; - private readonly IConfiguration _configuration; + private readonly RecipeDBAccess _dbAccess; - public RecipeLogic(IConfiguration configuration, RecipeDBaccess dbAccess) + public RecipeLogic(RecipeDBAccess dbAccess) { _dbAccess = dbAccess; - _configuration = configuration; } public async Task GetPrefereredRecipes(int userId) @@ -33,7 +31,7 @@ namespace API.BusinessLogic return new OkObjectResult(recipe); } - public async Task CreateRecipe(Recipe recipe, int prefereredRecipesId) + public async Task CreateRecipe(RecipeDTO recipe, int prefereredRecipesId) { var prefereredRecipes = await _dbAccess.ReadAllRecipe(prefereredRecipesId); @@ -45,10 +43,24 @@ namespace API.BusinessLogic } } - return await _dbAccess.CreateRecipe(recipe, prefereredRecipesId); + Recipe recipe1 = new Recipe(); + recipe1.Name = recipe.Name; + recipe1.Directions = recipe.Directions; + recipe1.Description = recipe.Description; + recipe1.Ingredients = new List(); + foreach (var item in recipe.Ingredients) + { + Ingredient ingredient = new Ingredient(); + ingredient.Unit = item.Unit; + ingredient.Element = item.Element; + ingredient.Amount = item.Amount; + recipe1.Ingredients.Add(ingredient); + } + + return await _dbAccess.CreateRecipe(recipe1, prefereredRecipesId); } - public async Task EditRecipe(Recipe recipe, int recipeId, int userId) + public async Task EditRecipe(RecipeDTO recipe, int recipeId, int userId) { var prefereredRecipes = await _dbAccess.ReadPrefereredRecipes(userId); var dish = await _dbAccess.ReadRecipe(recipeId); @@ -64,7 +76,14 @@ namespace API.BusinessLogic dish.Name = recipe.Name; dish.Description = recipe.Description; dish.Directions = recipe.Directions; - dish.Ingredients = recipe.Ingredients; + foreach (var item in recipe.Ingredients) + { + Ingredient ingredient = new Ingredient(); + ingredient.Unit = item.Unit; + ingredient.Element = item.Element; + ingredient.Amount = item.Amount; + dish.Ingredients.Add(ingredient); + } return await _dbAccess.UpdateRecipe(dish); } diff --git a/backend/API/BusinessLogic/ShoppingListLogic.cs b/backend/API/BusinessLogic/ShoppingListLogic.cs new file mode 100644 index 0000000..87c4165 --- /dev/null +++ b/backend/API/BusinessLogic/ShoppingListLogic.cs @@ -0,0 +1,209 @@ +using API.DBAccess; +using API.Models.ShoppingListModels; +using Microsoft.AspNetCore.Mvc; + +namespace API.BusinessLogic +{ + public class ShoppingListLogic + { + private readonly ShoppingListDBAccess _dbAccess; + private readonly RecipeDBAccess _recipeDBAccess; + + public ShoppingListLogic(ShoppingListDBAccess dbAccess, RecipeDBAccess recipeDBAccess) + { + _dbAccess = dbAccess; + _recipeDBAccess = recipeDBAccess; + } + + public async Task ReadShoppingList(int userId) + { + var user = await _dbAccess.ReadShoppingList(userId); + + return new OkObjectResult(user.ShoppingList); + } + + public async Task AddItemToShoppingList(ShoppingListItemDTO listItemDTO, int userId) + { + var user = await _dbAccess.ReadShoppingList(userId); + + List shoppingList = user.ShoppingList; + + if (shoppingList.Any(s => s.Name == listItemDTO.Name)) + { + ShoppingList item = shoppingList.Where(s => s.Name == listItemDTO.Name).FirstOrDefault(); + shoppingList.Remove(item); + + if (item.Unit == listItemDTO.Unit) + { + item.Amount += listItemDTO.Amount; + } + else if (item.Unit == "g" && listItemDTO.Unit == "kg") + { + item.Amount = (item.Amount / 1000) + listItemDTO.Amount; + item.Unit = "kg"; + } + else if (item.Unit == "ml" && item.Unit == "l") + { + item.Amount = (item.Amount / 1000) + listItemDTO.Amount; + item.Unit = "l"; + } + else if (item.Unit == "dl" && item.Unit == "l") + { + item.Amount = (item.Amount / 10) + listItemDTO.Amount; + item.Unit = "l"; + } + + item.Checked = false; + + if (item.Unit == "g" && item.Amount >= 1000) + { + item.Unit = "kg"; + item.Amount = item.Amount / 1000; + } + else if (item.Unit == "ml" && item.Amount >= 1000) + { + item.Unit = "l"; + item.Amount = item.Amount / 1000; + } + else if (item.Unit == "dl" && item.Amount >= 10) + { + item.Unit = "l"; + item.Amount = item.Amount / 10; + } + user.ShoppingList.Add(item); + } + else + { + ShoppingList newItem = new ShoppingList(); + newItem.Name = listItemDTO.Name; + newItem.Amount = listItemDTO.Amount; + newItem.Unit = listItemDTO.Unit; + newItem.Checked = false; + + user.ShoppingList.Add(newItem); + } + return await _dbAccess.UpdateShoppingList(user); + } + + public async Task CheckItemInShoppingList(int userId, int itemId) + { + var user = await _dbAccess.ReadShoppingList(userId); + + int itemIndex = user.ShoppingList.FindIndex(x => x.Id == itemId); + + user.ShoppingList[itemIndex].Checked = !user.ShoppingList[itemIndex].Checked; + + return await _dbAccess.UpdateShoppingList(user); + } + + public async Task UpdateItemInShoppingList(int userId, int itemId, ShoppingListItemDTO listItemDTO) + { + var user = await _dbAccess.ReadShoppingList(userId); + + int itemIndex = user.ShoppingList.FindIndex(x => x.Id == itemId); + + user.ShoppingList[itemIndex].Unit = listItemDTO.Unit; + user.ShoppingList[itemIndex].Name = listItemDTO.Name; + user.ShoppingList[itemIndex].Amount = listItemDTO.Amount; + + if (user.ShoppingList[itemIndex].Unit == "g" && user.ShoppingList[itemIndex].Amount >= 1000) + { + user.ShoppingList[itemIndex].Unit = "kg"; + user.ShoppingList[itemIndex].Amount = user.ShoppingList[itemIndex].Amount / 1000; + } + else if (user.ShoppingList[itemIndex].Unit == "ml" && user.ShoppingList[itemIndex].Amount >= 1000) + { + user.ShoppingList[itemIndex].Unit = "l"; + user.ShoppingList[itemIndex].Amount = user.ShoppingList[itemIndex].Amount / 1000; + } + else if (user.ShoppingList[itemIndex].Unit == "dl" && user.ShoppingList[itemIndex].Amount >= 10) + { + user.ShoppingList[itemIndex].Unit = "l"; + user.ShoppingList[itemIndex].Amount = user.ShoppingList[itemIndex].Amount / 10; + } + + return await _dbAccess.UpdateShoppingList(user); + } + + public async Task DeleteItemInShoppingList(int userId, int itemId) + { + var user = await _dbAccess.ReadShoppingList(userId); + + int itemIndex = user.ShoppingList.FindIndex(x => x.Id == itemId); + + user.ShoppingList.RemoveAt(itemIndex); + + return await _dbAccess.UpdateShoppingList(user); + } + + public async Task AddRecipeToShoppingList(int userId, int recipeId) + { + var user = await _dbAccess.ReadShoppingList(userId); + var recipe = await _recipeDBAccess.ReadRecipe(recipeId); + var ingredients = recipe.Ingredients; + + foreach (var ingredient in ingredients) + { + List shoppingList = user.ShoppingList; + + if (shoppingList.Any(s => s.Name == ingredient.Element)) + { + ShoppingList item = shoppingList.Where(s => s.Name == ingredient.Element).FirstOrDefault(); + shoppingList.Remove(item); + + if (item.Unit == ingredient.Unit) + { + item.Amount += ingredient.Amount; + } + else if (item.Unit == "g" && ingredient.Unit == "kg") + { + item.Amount = (item.Amount / 1000) + ingredient.Amount; + item.Unit = "kg"; + } + else if (item.Unit == "ml" && item.Unit == "l") + { + item.Amount = (item.Amount / 1000) + ingredient.Amount; + item.Unit = "l"; + } + else if (item.Unit == "dl" && item.Unit == "l") + { + item.Amount = (item.Amount / 10) + ingredient.Amount; + item.Unit = "l"; + } + + item.Checked = false; + + if (item.Unit == "g" && item.Amount >= 1000) + { + item.Unit = "kg"; + item.Amount = item.Amount / 1000; + } + else if (item.Unit == "ml" && item.Amount >= 1000) + { + item.Unit = "l"; + item.Amount = item.Amount / 1000; + } + else if (item.Unit == "dl" && item.Amount >= 10) + { + item.Unit = "l"; + item.Amount = item.Amount / 10; + } + + user.ShoppingList.Add(item); + } + else + { + ShoppingList newItem = new ShoppingList(); + newItem.Name = ingredient.Element; + newItem.Amount = ingredient.Amount; + newItem.Unit = ingredient.Unit; + newItem.Checked = false; + + user.ShoppingList.Add(newItem); + } + } + + return await _dbAccess.UpdateShoppingList(user); + } + } +} diff --git a/backend/API/BusinessLogic/UserLogic.cs b/backend/API/BusinessLogic/UserLogic.cs index c835bf4..c257631 100644 --- a/backend/API/BusinessLogic/UserLogic.cs +++ b/backend/API/BusinessLogic/UserLogic.cs @@ -8,6 +8,7 @@ using System.Security.Claims; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; +using API.Models.ShoppingListModels; namespace API.BusinessLogic { @@ -70,6 +71,7 @@ namespace API.BusinessLogic Password = hashedPassword, Salt = salt, PrefereredRecipes = recipes, + ShoppingList = new List(), }; return await _dbAccess.CreateUser(user); diff --git a/backend/API/Controllers/RecipeController.cs b/backend/API/Controllers/RecipeController.cs index 113adac..bb9c1eb 100644 --- a/backend/API/Controllers/RecipeController.cs +++ b/backend/API/Controllers/RecipeController.cs @@ -36,14 +36,14 @@ namespace API.Controllers [Authorize] [HttpPost("create/{prefereredRecipesId}")] - public async Task CreateRecipe([FromBody] Recipe recipe, int prefereredRecipesId) + public async Task CreateRecipe([FromBody] RecipeDTO recipe, int prefereredRecipesId) { return await _recipeLogic.CreateRecipe(recipe, prefereredRecipesId); } [Authorize] [HttpPut("edit/{recipeId}")] - public async Task EditRecipe([FromBody] Recipe recipe, int recipeId) + public async Task EditRecipe([FromBody] RecipeDTO recipe, int recipeId) { var claims = HttpContext.User.Claims; string userIdString = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value; diff --git a/backend/API/Controllers/ShoppingListController.cs b/backend/API/Controllers/ShoppingListController.cs new file mode 100644 index 0000000..7db1bd4 --- /dev/null +++ b/backend/API/Controllers/ShoppingListController.cs @@ -0,0 +1,74 @@ + +using API.BusinessLogic; +using API.Models.ShoppingListModels; +using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; + +namespace API.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class ShoppingListController : Controller + { + private readonly ShoppingListLogic _shoppingListLogic; + + public ShoppingListController(ShoppingListLogic shoppingListLogic) + { + _shoppingListLogic = shoppingListLogic; + } + + [HttpGet("get")] + public async Task ReadShoppingList() + { + var claims = HttpContext.User.Claims; + string userIdString = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value; + int userId = Convert.ToInt32(userIdString); + return await _shoppingListLogic.ReadShoppingList(userId); + } + + [HttpPost("add")] + public async Task AddItem([FromBody] ShoppingListItemDTO listItemDTO) + { + var claims = HttpContext.User.Claims; + string userIdString = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value; + int userId = Convert.ToInt32(userIdString); + return await _shoppingListLogic.AddItemToShoppingList(listItemDTO, userId); + } + + [HttpPut("check")] + public async Task CheckItem(int itemId) + { + var claims = HttpContext.User.Claims; + string userIdString = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value; + int userId = Convert.ToInt32(userIdString); + return await _shoppingListLogic.CheckItemInShoppingList(userId, itemId); + } + + [HttpPut("update")] + public async Task UpdateItem([FromBody] ShoppingListItemDTO listItemDTO, int itemId) + { + var claims = HttpContext.User.Claims; + string userIdString = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value; + int userId = Convert.ToInt32(userIdString); + return await _shoppingListLogic.UpdateItemInShoppingList(userId, itemId, listItemDTO); + } + + [HttpDelete("delete")] + public async Task DeleteItem(int itemId) + { + var claims = HttpContext.User.Claims; + string userIdString = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value; + int userId = Convert.ToInt32(userIdString); + return await _shoppingListLogic.DeleteItemInShoppingList(userId, itemId); + } + + [HttpPost("recipeadd")] + public async Task AddARecipesItems(int recipeId) + { + var claims = HttpContext.User.Claims; + string userIdString = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value; + int userId = Convert.ToInt32(userIdString); + return await _shoppingListLogic.AddRecipeToShoppingList(userId, recipeId); + } + } +} diff --git a/backend/API/DBAccess/DBContext.cs b/backend/API/DBAccess/DBContext.cs index 04c124e..f335651 100644 --- a/backend/API/DBAccess/DBContext.cs +++ b/backend/API/DBAccess/DBContext.cs @@ -1,4 +1,5 @@ using API.Models.RecipeModels; +using API.Models.ShoppingListModels; using API.Models.UserModels; using Microsoft.EntityFrameworkCore; @@ -12,6 +13,8 @@ namespace API.DBAccess public DbSet Recipes { get; set; } + public DbSet ShoppingList { get; set; } + public DBContext(DbContextOptions options) : base(options) { } } } diff --git a/backend/API/DBAccess/RecipeDBaccess.cs b/backend/API/DBAccess/RecipeDBaccess.cs index 10b1091..7c276a3 100644 --- a/backend/API/DBAccess/RecipeDBaccess.cs +++ b/backend/API/DBAccess/RecipeDBaccess.cs @@ -5,11 +5,11 @@ using Microsoft.EntityFrameworkCore; namespace API.DBAccess { - public class RecipeDBaccess + public class RecipeDBAccess { private readonly DBContext _context; - public RecipeDBaccess(DBContext context) + public RecipeDBAccess(DBContext context) { _context = context; } diff --git a/backend/API/DBAccess/ShoppingListDBAccess.cs b/backend/API/DBAccess/ShoppingListDBAccess.cs new file mode 100644 index 0000000..f77069d --- /dev/null +++ b/backend/API/DBAccess/ShoppingListDBAccess.cs @@ -0,0 +1,35 @@ +using API.Models.RecipeModels; +using API.Models.ShoppingListModels; +using API.Models.UserModels; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace API.DBAccess +{ + public class ShoppingListDBAccess + { + private readonly DBContext _context; + + public ShoppingListDBAccess(DBContext context) + { + _context = context; + } + public async Task ReadShoppingList(int userId) + { + var user = await _context.Users.Include(u => u.ShoppingList).FirstOrDefaultAsync(u => u.Id == userId); + + return user; + } + + public async Task UpdateShoppingList(User user) + { + _context.Entry(user).State = EntityState.Modified; + + bool saved = await _context.SaveChangesAsync() >= 1; + + if (saved) { return new OkObjectResult(user); } + + return new ConflictObjectResult(new { message = "Could not save to database" }); + } + } +} diff --git a/backend/API/DBAccess/UserDBAccess.cs b/backend/API/DBAccess/UserDBAccess.cs index 0ea3779..dfbc59e 100644 --- a/backend/API/DBAccess/UserDBAccess.cs +++ b/backend/API/DBAccess/UserDBAccess.cs @@ -49,7 +49,7 @@ namespace API.DBAccess { _context.Users.Add(user); - bool saved = await _context.SaveChangesAsync() == 1; + bool saved = await _context.SaveChangesAsync() >= 1; if (saved) { return new OkObjectResult(true); } diff --git a/backend/API/Models/RecipeModels/IngredientDTO.cs b/backend/API/Models/RecipeModels/IngredientDTO.cs new file mode 100644 index 0000000..b85d789 --- /dev/null +++ b/backend/API/Models/RecipeModels/IngredientDTO.cs @@ -0,0 +1,11 @@ +namespace API.Models.RecipeModels +{ + public class IngredientDTO + { + public int Amount { get; set; } + + public string Unit { get; set; } + + public string Element { get; set; } + } +} diff --git a/backend/API/Models/RecipeModels/PrefereredRecipesDTO.cs b/backend/API/Models/RecipeModels/PrefereredRecipesDTO.cs new file mode 100644 index 0000000..16ea538 --- /dev/null +++ b/backend/API/Models/RecipeModels/PrefereredRecipesDTO.cs @@ -0,0 +1,7 @@ +namespace API.Models.RecipeModels +{ + public class PrefereredRecipesDTO + { + public List Recipes { get; set; } + } +} diff --git a/backend/API/Models/RecipeModels/RecipeDTO.cs b/backend/API/Models/RecipeModels/RecipeDTO.cs new file mode 100644 index 0000000..0f27cdb --- /dev/null +++ b/backend/API/Models/RecipeModels/RecipeDTO.cs @@ -0,0 +1,13 @@ +namespace API.Models.RecipeModels +{ + public class RecipeDTO + { + public string Name { get; set; } + + public string Description { get; set; } + + public string Directions { get; set; } + + public List Ingredients { get; set; } + } +} diff --git a/backend/API/Models/ShoppingListModels/ShoppingList.cs b/backend/API/Models/ShoppingListModels/ShoppingList.cs new file mode 100644 index 0000000..a2c206a --- /dev/null +++ b/backend/API/Models/ShoppingListModels/ShoppingList.cs @@ -0,0 +1,15 @@ +namespace API.Models.ShoppingListModels +{ + public class ShoppingList + { + public int Id { get; set; } + + public double Amount { get; set; } + + public string Unit { get; set; } + + public string Name { get; set; } + + public bool Checked { get; set; } + } +} diff --git a/backend/API/Models/ShoppingListModels/ShoppingListItemDTO.cs b/backend/API/Models/ShoppingListModels/ShoppingListItemDTO.cs new file mode 100644 index 0000000..4b1959e --- /dev/null +++ b/backend/API/Models/ShoppingListModels/ShoppingListItemDTO.cs @@ -0,0 +1,14 @@ +namespace API.Models.ShoppingListModels +{ + public class ShoppingListItemDTO + { + + public string Name { get; set; } + + public double Amount { get; set; } + + public string Unit { get; set; } + + public bool Checked { get; set; } + } +} diff --git a/backend/API/Models/UserModels/User.cs b/backend/API/Models/UserModels/User.cs index e81b231..abca1fa 100644 --- a/backend/API/Models/UserModels/User.cs +++ b/backend/API/Models/UserModels/User.cs @@ -1,4 +1,5 @@ using API.Models.RecipeModels; +using API.Models.ShoppingListModels; namespace API.Models.UserModels { @@ -19,5 +20,7 @@ namespace API.Models.UserModels public DateTime RefreshTokenExpireAt { get; set; } public PrefereredRecipes PrefereredRecipes { get; set; } + + public List ShoppingList { get; set; } } } diff --git a/backend/API/Startup.cs b/backend/API/Startup.cs index 3969135..7b0dd60 100644 --- a/backend/API/Startup.cs +++ b/backend/API/Startup.cs @@ -27,7 +27,7 @@ namespace API services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddControllers();