From 15a09e0d30dfff9ed8ea0b0dc34e35e7adfbb65f Mon Sep 17 00:00:00 2001 From: Jeas0001 Date: Tue, 6 May 2025 13:56:46 +0200 Subject: [PATCH 01/12] Comments for the logic --- backend/API/BusinessLogic/RecipeLogic.cs | 25 +++++++++++++++---- .../API/BusinessLogic/ShoppingListLogic.cs | 10 ++++++-- backend/API/BusinessLogic/UserLogic.cs | 18 +++++++++++++ backend/API/DBAccess/RecipeDBaccess.cs | 4 +-- 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/backend/API/BusinessLogic/RecipeLogic.cs b/backend/API/BusinessLogic/RecipeLogic.cs index 8d02900..48aaa3f 100644 --- a/backend/API/BusinessLogic/RecipeLogic.cs +++ b/backend/API/BusinessLogic/RecipeLogic.cs @@ -16,8 +16,8 @@ namespace API.BusinessLogic /// /// Gets all the recipes from _dbaccess and checks if there are any /// - /// - /// + /// the usér connected to the recipes + /// a list of recipes in a ok objectresult public async Task GetRecipes(int userId) { var recipes = await _dbAccess.ReadRecipes(userId); @@ -26,7 +26,7 @@ namespace API.BusinessLogic return new OkObjectResult(recipes); } - // + // Gets a specifik with recipe with the ingredient and directions public async Task GetRecipe(int recipeId) { var recipe = await _dbAccess.ReadRecipe(recipeId); @@ -36,6 +36,13 @@ namespace API.BusinessLogic return new OkObjectResult(recipe); } + /// + /// Creates a recipe and checks if the recipe name is in use + /// Converts the recipeDTO to a normal recipe + /// + /// The recipeDTO that does not include all the id tags + /// The user that is going to get that recipe + /// returns what recipedbaccess gives back public async Task CreateRecipe(RecipeDTO recipe, int userId) { var recipes = await _dbAccess.ReadRecipes(userId); @@ -71,6 +78,13 @@ namespace API.BusinessLogic return await _dbAccess.CreateRecipe(dish, userId); } + /// + /// Updates the recipe that is saved in the db + /// + /// The updated recipe + /// The recipeId on the recipe to be updated + /// The userÍd of the owner of the to be updated recipe + /// returns what recipedbaccess gives back public async Task EditRecipe(RecipeDTO recipe, int recipeId, int userId) { var recipes = await _dbAccess.ReadRecipes(userId); @@ -105,13 +119,14 @@ namespace API.BusinessLogic return await _dbAccess.UpdateRecipe(dish); } + // Deletes the recipe public async Task DeleteRecipe(int recipeId) { var recipe = await _dbAccess.ReadRecipe(recipeId); - if (recipe != null) { return await _dbAccess.DeleteUser(recipe); } + if (recipe != null) { return await _dbAccess.DeleteRecipe(recipe); } - return new ConflictObjectResult(new { message = "Invalid user" }); + return new ConflictObjectResult(new { message = "Invalid recipe" }); } } } diff --git a/backend/API/BusinessLogic/ShoppingListLogic.cs b/backend/API/BusinessLogic/ShoppingListLogic.cs index 53f2da9..1651896 100644 --- a/backend/API/BusinessLogic/ShoppingListLogic.cs +++ b/backend/API/BusinessLogic/ShoppingListLogic.cs @@ -15,6 +15,7 @@ namespace API.BusinessLogic _recipeDBAccess = recipeDBAccess; } + // Reads the current shooping list of the user public async Task ReadShoppingList(int userId) { var user = await _dbAccess.ReadShoppingList(userId); @@ -22,6 +23,7 @@ namespace API.BusinessLogic return new OkObjectResult(user.ShoppingList); } + // Adds an item to the shoppinglist and checks if the unit should be changed and if the name is the same as an item already on the shoppinglist public async Task AddItemToShoppingList(ShoppingListItemDTO listItemDTO, int userId) { var user = await _dbAccess.ReadShoppingList(userId); @@ -42,12 +44,12 @@ namespace API.BusinessLogic item.Amount = (item.Amount / 1000) + listItemDTO.Amount; item.Unit = "kg"; } - else if (item.Unit == "ml" && item.Unit == "l") + else if (item.Unit == "ml" && listItemDTO.Unit == "l") { item.Amount = (item.Amount / 1000) + listItemDTO.Amount; item.Unit = "l"; } - else if (item.Unit == "dl" && item.Unit == "l") + else if (item.Unit == "dl" && listItemDTO.Unit == "l") { item.Amount = (item.Amount / 10) + listItemDTO.Amount; item.Unit = "l"; @@ -85,6 +87,7 @@ namespace API.BusinessLogic return await _dbAccess.UpdateShoppingList(user); } + // Gets the shoppinglist and tries to find the item and when it does it checks/unchecks that item public async Task CheckItemInShoppingList(int userId, int itemId) { var user = await _dbAccess.ReadShoppingList(userId); @@ -96,6 +99,7 @@ namespace API.BusinessLogic return await _dbAccess.UpdateShoppingList(user); } + // Updates an item on the shopping list to what the user specified public async Task UpdateItemInShoppingList(int userId, int itemId, ShoppingListItemDTO listItemDTO) { var user = await _dbAccess.ReadShoppingList(userId); @@ -125,6 +129,7 @@ namespace API.BusinessLogic return await _dbAccess.UpdateShoppingList(user); } + // Deletes an item from the shopping list if it is on the users shoppinglist public async Task DeleteItemInShoppingList(int userId, int itemId) { var user = await _dbAccess.ReadShoppingList(userId); @@ -136,6 +141,7 @@ namespace API.BusinessLogic return await _dbAccess.UpdateShoppingList(user); } + // Adds an entire recipes ingredients to the shoppinglist public async Task AddRecipeToShoppingList(int userId, int recipeId) { var user = await _dbAccess.ReadShoppingList(userId); diff --git a/backend/API/BusinessLogic/UserLogic.cs b/backend/API/BusinessLogic/UserLogic.cs index 2a93d55..f32bd2e 100644 --- a/backend/API/BusinessLogic/UserLogic.cs +++ b/backend/API/BusinessLogic/UserLogic.cs @@ -23,6 +23,7 @@ namespace API.BusinessLogic _configuration = configuration; } + // Gets an user from their id public async Task GetUser(int userId) { User user = await _dbAccess.ReadUser(userId); @@ -31,6 +32,7 @@ namespace API.BusinessLogic return new OkObjectResult(new { user.Id, user.UserName, user.Email }); } + // Checks if the userdata is ok before the user is created and creats the othere list's that the user have public async Task RegisterUser(CreateUserDTO userDTO) { if (!EmailCheck(userDTO.Email)) @@ -71,6 +73,7 @@ namespace API.BusinessLogic return await _dbAccess.CreateUser(user); } + // Checks if the username/email matches the password and generates a jwttoken if it is correct public async Task Login(LoginDTO loginDTO) { var user = await _dbAccess.ReadUserForLogin(loginDTO.EmailUsr); @@ -91,6 +94,7 @@ namespace API.BusinessLogic return new ConflictObjectResult(new { message = "Invalid password" }); } + // Checks if the username or email is already in use and changes them if they are diffrent from before public async Task EditProfile(UpdateUserDTO userDTO, int userId) { var profile = await _dbAccess.ReadUser(userId); @@ -142,6 +146,7 @@ namespace API.BusinessLogic return await _dbAccess.UpdateUser(profile); } + // Checks if the old password is correct and then it checks if the password is secure enough public async Task ChangePassword(ChangePasswordDTO passwordDTO, int userId) { var user = await _dbAccess.ReadUser(userId); @@ -167,6 +172,7 @@ namespace API.BusinessLogic return await _dbAccess.UpdatePassword(user); } + // Checks if the user exist and it deletes that user public async Task DeleteUser(int userId) { var user = await _dbAccess.ReadUserForDelete(userId); @@ -176,6 +182,7 @@ namespace API.BusinessLogic return new ConflictObjectResult(new { message = "Invalid user" }); } + // Checks if the refreshtoken is correct and if it is it generates a new jwttoken and refreshtoken public async Task RefreshToken(string refreshToken) { User user = await _dbAccess.ReadUserByRefreshToken(refreshToken); @@ -185,6 +192,7 @@ namespace API.BusinessLogic return new OkObjectResult(new { token = jwtToken, refreshToken = user.RefreshToken }); } + // Checks if the password is up to our security standard private bool PasswordSecurity(string password) { var hasMinimum8Chars = new Regex(@".{8,}"); @@ -192,11 +200,19 @@ namespace API.BusinessLogic return hasMinimum8Chars.IsMatch(password); } + // Checks if the email has all the things an email should have private bool EmailCheck(string email) { return new Regex(@".+@.+\..+").IsMatch(email); } + /// + /// Generates a hash from a salt and input using the algorithm that is provided + /// + /// This is the input that is supposed to be hashed + /// This is the alogorithm that is used to encrypt the input + /// This is something extra added to make the hashed input more unpredictable + /// The hashed input private static string ComputeHash(string input, HashAlgorithm algorithm, string salt) { Byte[] inputBytes = Encoding.UTF8.GetBytes(input); @@ -212,6 +228,7 @@ namespace API.BusinessLogic return BitConverter.ToString(hashedBytes); } + // Generates a jwttoken that contains the users id and username and a unique identifier that is valid for 1 hour private string GenerateJwtToken(User user) { var claims = new[] @@ -235,6 +252,7 @@ namespace API.BusinessLogic return new JwtSecurityTokenHandler().WriteToken(token); } + // Generate a new refreshtoken that expire after 30 days private async Task UpdateRefreshToken(User user) { user.RefreshToken = Guid.NewGuid().ToString(); diff --git a/backend/API/DBAccess/RecipeDBaccess.cs b/backend/API/DBAccess/RecipeDBaccess.cs index ef1ee81..b36b9be 100644 --- a/backend/API/DBAccess/RecipeDBaccess.cs +++ b/backend/API/DBAccess/RecipeDBaccess.cs @@ -23,7 +23,7 @@ namespace API.DBAccess public async Task ReadRecipe(int recipeId) { - return await _context.Recipes.Include(r => r.Ingredients).FirstOrDefaultAsync(r => r.Id == recipeId); + return await _context.Recipes.Include(r => r.Ingredients).Include(r => r.Directions).FirstOrDefaultAsync(r => r.Id == recipeId); } public async Task CreateRecipe(Recipe recipe, int userId) @@ -50,7 +50,7 @@ namespace API.DBAccess return new ConflictObjectResult(new { message = "Could not save to database" }); } - public async Task DeleteUser(Recipe recipe) + public async Task DeleteRecipe(Recipe recipe) { _context.Recipes.Remove(recipe); bool saved = await _context.SaveChangesAsync() >= 0; From b8fa54539fbbcd6c084cfe7e1250cfd1cac550d6 Mon Sep 17 00:00:00 2001 From: Reimar Date: Tue, 6 May 2025 14:31:04 +0200 Subject: [PATCH 02/12] Don't return user object when updating profile --- backend/API/DBAccess/UserDBAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/API/DBAccess/UserDBAccess.cs b/backend/API/DBAccess/UserDBAccess.cs index 9118433..ac7711c 100644 --- a/backend/API/DBAccess/UserDBAccess.cs +++ b/backend/API/DBAccess/UserDBAccess.cs @@ -77,7 +77,7 @@ namespace API.DBAccess bool saved = await _context.SaveChangesAsync() == 1; - if (saved) { return new OkObjectResult(user); } + if (saved) { return new OkObjectResult(true); } return new ConflictObjectResult(new { message = "Could not save to database" }); } From 87b5fbb014bbe41fe8fe38c2aa09608cc6b3863c Mon Sep 17 00:00:00 2001 From: Reimar Date: Tue, 6 May 2025 14:37:48 +0200 Subject: [PATCH 03/12] Update profile page immediately as user info has changed --- .../tech/mercantec/easyeat/helpers/auth.kt | 2 +- .../easyeat/ui/profile/EditProfileActivity.kt | 6 ++++ .../easyeat/ui/profile/ProfileFragment.kt | 35 ++++++++++++++++--- .../src/main/res/layout/fragment_profile.xml | 3 +- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/app/app/src/main/java/tech/mercantec/easyeat/helpers/auth.kt b/app/app/src/main/java/tech/mercantec/easyeat/helpers/auth.kt index 7d483c0..01849c9 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/helpers/auth.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/helpers/auth.kt @@ -98,7 +98,7 @@ data class UpdateUserRequest(val userName: String, val email: String) fun updateUser(ctx: Context, username: String, email: String) { val request = UpdateUserRequest(username, email) - return requestJson(ctx, "PUT", "/api/User/update", request) + requestJson(ctx, "PUT", "/api/User/update", request) } @Serializable diff --git a/app/app/src/main/java/tech/mercantec/easyeat/ui/profile/EditProfileActivity.kt b/app/app/src/main/java/tech/mercantec/easyeat/ui/profile/EditProfileActivity.kt index acf8a4c..ff78d51 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/ui/profile/EditProfileActivity.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/ui/profile/EditProfileActivity.kt @@ -1,6 +1,7 @@ package tech.mercantec.easyeat.ui.profile import android.app.ProgressDialog +import android.content.Intent import android.os.Bundle import android.widget.Button import android.widget.EditText @@ -43,6 +44,11 @@ class EditProfileActivity : AppCompatActivity() { } } + val intent = Intent() + intent.putExtra("username", username) + intent.putExtra("email", email) + setResult(RESULT_OK, intent) + finish() } } diff --git a/app/app/src/main/java/tech/mercantec/easyeat/ui/profile/ProfileFragment.kt b/app/app/src/main/java/tech/mercantec/easyeat/ui/profile/ProfileFragment.kt index 6f735f5..7c8e27c 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/ui/profile/ProfileFragment.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/ui/profile/ProfileFragment.kt @@ -1,11 +1,15 @@ package tech.mercantec.easyeat.ui.profile +import android.app.Activity import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.view.children import androidx.fragment.app.Fragment import tech.mercantec.easyeat.ui.auth.WelcomeActivity import tech.mercantec.easyeat.databinding.FragmentProfileBinding @@ -16,8 +20,25 @@ import tech.mercantec.easyeat.helpers.logout import kotlin.concurrent.thread class ProfileFragment : Fragment() { + private lateinit var launcher: ActivityResultLauncher + private lateinit var binding: FragmentProfileBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.data == null || it.resultCode != Activity.RESULT_OK) return@registerForActivityResult + + binding.username.text = it.data!!.getStringExtra("username") + binding.email.text = it.data!!.getStringExtra("email") + } + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - val binding = FragmentProfileBinding.inflate(inflater, container, false) + binding = FragmentProfileBinding.inflate(inflater, container, false) + + binding.layout.children.forEach { it.visibility = View.GONE } + binding.loading.visibility = View.VISIBLE thread { val userInfo: UserInfoResponse @@ -29,6 +50,11 @@ class ProfileFragment : Fragment() { } return@thread + } finally { + activity?.runOnUiThread { + binding.layout.children.forEach { it.visibility = View.VISIBLE } + binding.loading.visibility = View.GONE + } } activity?.runOnUiThread { @@ -37,9 +63,10 @@ class ProfileFragment : Fragment() { binding.editProfile.setOnClickListener { val intent = Intent(activity, EditProfileActivity::class.java) - intent.putExtra("username", userInfo.userName) - intent.putExtra("email", userInfo.email) - startActivity(intent) + intent.putExtra("username", binding.username.text) + intent.putExtra("email", binding.email.text) + + launcher.launch(intent) } } } diff --git a/app/app/src/main/res/layout/fragment_profile.xml b/app/app/src/main/res/layout/fragment_profile.xml index f2708c8..be15d10 100644 --- a/app/app/src/main/res/layout/fragment_profile.xml +++ b/app/app/src/main/res/layout/fragment_profile.xml @@ -1,6 +1,7 @@ Date: Wed, 7 May 2025 09:34:50 +0200 Subject: [PATCH 04/12] =?UTF-8?q?retunere=20true=20n=C3=A5r=20man=20tilf?= =?UTF-8?q?=C3=B8jer=20til=20shoppinglisten?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/API/DBAccess/ShoppingListDBAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/API/DBAccess/ShoppingListDBAccess.cs b/backend/API/DBAccess/ShoppingListDBAccess.cs index f77069d..6a6bb07 100644 --- a/backend/API/DBAccess/ShoppingListDBAccess.cs +++ b/backend/API/DBAccess/ShoppingListDBAccess.cs @@ -27,7 +27,7 @@ namespace API.DBAccess bool saved = await _context.SaveChangesAsync() >= 1; - if (saved) { return new OkObjectResult(user); } + if (saved) { return new OkObjectResult(true); } return new ConflictObjectResult(new { message = "Could not save to database" }); } From 1ce1f355c01cac81c116d28db8389ddbea65b153 Mon Sep 17 00:00:00 2001 From: Jeas0001 Date: Wed, 7 May 2025 11:28:21 +0200 Subject: [PATCH 05/12] Recipecontroller comments --- backend/API/Controllers/RecipeController.cs | 37 +++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/backend/API/Controllers/RecipeController.cs b/backend/API/Controllers/RecipeController.cs index 5fa9bdf..d2c65ea 100644 --- a/backend/API/Controllers/RecipeController.cs +++ b/backend/API/Controllers/RecipeController.cs @@ -20,6 +20,10 @@ namespace API.Controllers _openAiRecipes = openAiRecipes; } + /// + /// Gets the userId from the jwt token amd returns a list of recipes without their ingredients and directions + /// + /// returns a okobjectresult with a list of recipes if it fails it returns a confliftobjectresult with a message of why it failed [Authorize] [HttpGet("getall")] public async Task ReadRecipes() @@ -30,6 +34,11 @@ namespace API.Controllers return await _recipeLogic.GetRecipes(userId); } + /// + /// Gets a specifik recipe including the ingredients and directions + /// + /// The recipe that is you want + /// returns a okobjectresult with a recipe if it fails it returns a confliftobjectresult with a message of why it failed [Authorize] [HttpGet("get/{recipeId}")] public async Task ReadRecipe(int recipeId) @@ -37,13 +46,27 @@ namespace API.Controllers return await _recipeLogic.GetRecipe(recipeId); } + /// + /// Creates a recipe and adds it to the users recipes + /// + /// The recipe to be added + /// returns a okobjectresult with a boolean that is true if it fails it returns a confliftobjectresult with a message of why it failed [Authorize] [HttpPost("create/{RecipesId}")] - public async Task CreateRecipe([FromBody] RecipeDTO recipe, int RecipesId) + public async Task CreateRecipe([FromBody] RecipeDTO recipe) { - return await _recipeLogic.CreateRecipe(recipe, RecipesId); + var claims = HttpContext.User.Claims; + string userIdString = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value; + int userId = Convert.ToInt32(userIdString); + return await _recipeLogic.CreateRecipe(recipe, userId); } + /// + /// Edits a recipe + /// + /// the edited recipe + /// the recipe to be edited + /// returns a okobjectresult with a boolean that is true if it fails it returns a confliftobjectresult with a message of why it failed [Authorize] [HttpPut("edit/{recipeId}")] public async Task EditRecipe([FromBody] RecipeDTO recipe, int recipeId) @@ -54,6 +77,11 @@ namespace API.Controllers return await _recipeLogic.EditRecipe(recipe, recipeId, userId); } + /// + /// Deletess a recipe + /// + /// the id of the recipe to be deleted + /// returns a okobjectresult with a boolean that is true if it fails it returns a confliftobjectresult with a message of why it failed [Authorize] [HttpDelete("delete/{recipeId}")] public async Task DeleteRecipe(int recipeId) @@ -61,6 +89,11 @@ namespace API.Controllers return await _recipeLogic.DeleteRecipe(recipeId); } + /// + /// Generates a recipe using chatgpt + /// + /// Contains all the infomation that is needed to generate a recipe + /// returns a list of generated recipes [Authorize] [HttpPost("chatbot")] public async Task GenerateRecipe([FromBody] GenerateRecipeDTO recipeDTO) From edf8c73ec41eda87e6e08556a7e9c457d0bbce10 Mon Sep 17 00:00:00 2001 From: Jeas0001 Date: Wed, 7 May 2025 11:29:35 +0200 Subject: [PATCH 06/12] authorize atribute added to shppinglistcontroller --- backend/API/Controllers/ShoppingListController.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/API/Controllers/ShoppingListController.cs b/backend/API/Controllers/ShoppingListController.cs index 7db1bd4..b0df2d7 100644 --- a/backend/API/Controllers/ShoppingListController.cs +++ b/backend/API/Controllers/ShoppingListController.cs @@ -1,6 +1,7 @@  using API.BusinessLogic; using API.Models.ShoppingListModels; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Security.Claims; @@ -17,6 +18,7 @@ namespace API.Controllers _shoppingListLogic = shoppingListLogic; } + [Authorize] [HttpGet("get")] public async Task ReadShoppingList() { @@ -26,6 +28,7 @@ namespace API.Controllers return await _shoppingListLogic.ReadShoppingList(userId); } + [Authorize] [HttpPost("add")] public async Task AddItem([FromBody] ShoppingListItemDTO listItemDTO) { @@ -35,6 +38,7 @@ namespace API.Controllers return await _shoppingListLogic.AddItemToShoppingList(listItemDTO, userId); } + [Authorize] [HttpPut("check")] public async Task CheckItem(int itemId) { @@ -44,6 +48,7 @@ namespace API.Controllers return await _shoppingListLogic.CheckItemInShoppingList(userId, itemId); } + [Authorize] [HttpPut("update")] public async Task UpdateItem([FromBody] ShoppingListItemDTO listItemDTO, int itemId) { @@ -53,6 +58,7 @@ namespace API.Controllers return await _shoppingListLogic.UpdateItemInShoppingList(userId, itemId, listItemDTO); } + [Authorize] [HttpDelete("delete")] public async Task DeleteItem(int itemId) { @@ -62,6 +68,7 @@ namespace API.Controllers return await _shoppingListLogic.DeleteItemInShoppingList(userId, itemId); } + [Authorize] [HttpPost("recipeadd")] public async Task AddARecipesItems(int recipeId) { From 38cf388ea80f03b81dc37b8b581f0d20d9ff12d2 Mon Sep 17 00:00:00 2001 From: Jeas0001 Date: Wed, 7 May 2025 11:54:02 +0200 Subject: [PATCH 07/12] Shoppinglistcontroller comments --- .../API/Controllers/ShoppingListController.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/backend/API/Controllers/ShoppingListController.cs b/backend/API/Controllers/ShoppingListController.cs index b0df2d7..ddeb6c1 100644 --- a/backend/API/Controllers/ShoppingListController.cs +++ b/backend/API/Controllers/ShoppingListController.cs @@ -18,6 +18,10 @@ namespace API.Controllers _shoppingListLogic = shoppingListLogic; } + /// + /// Gets the entire shoppinglist connected to the user + /// + /// returns a list of shoppinglist items if it fails it returns a confliftobjectresult with a message of why it failed [Authorize] [HttpGet("get")] public async Task ReadShoppingList() @@ -28,6 +32,11 @@ namespace API.Controllers return await _shoppingListLogic.ReadShoppingList(userId); } + /// + /// Adds an item to the shopping list + /// + /// + /// [Authorize] [HttpPost("add")] public async Task AddItem([FromBody] ShoppingListItemDTO listItemDTO) @@ -38,6 +47,11 @@ namespace API.Controllers return await _shoppingListLogic.AddItemToShoppingList(listItemDTO, userId); } + /// + /// Checks/Unchecks an item on the shoppinglist + /// + /// The item to be checked/unchecked + /// returns a okobjectresult with a boolean that is true if it fails it returns a confliftobjectresult with a message of why it failed [Authorize] [HttpPut("check")] public async Task CheckItem(int itemId) @@ -48,6 +62,12 @@ namespace API.Controllers return await _shoppingListLogic.CheckItemInShoppingList(userId, itemId); } + /// + /// Edits an item on the shoppinglist + /// + /// The edited item + /// The item to be edited + /// returns a okobjectresult with a boolean that is true if it fails it returns a confliftobjectresult with a message of why it failed [Authorize] [HttpPut("update")] public async Task UpdateItem([FromBody] ShoppingListItemDTO listItemDTO, int itemId) @@ -58,6 +78,11 @@ namespace API.Controllers return await _shoppingListLogic.UpdateItemInShoppingList(userId, itemId, listItemDTO); } + /// + /// Deletes an item on the shoppinglist + /// + /// The item to be deleted + /// returns a okobjectresult with a boolean that is true if it fails it returns a confliftobjectresult with a message of why it failed [Authorize] [HttpDelete("delete")] public async Task DeleteItem(int itemId) @@ -68,6 +93,11 @@ namespace API.Controllers return await _shoppingListLogic.DeleteItemInShoppingList(userId, itemId); } + /// + /// Add an entire recipes ingredients to the shoppinglist + /// + /// The recipes ingredients to be added + /// returns a okobjectresult with a boolean that is true if it fails it returns a confliftobjectresult with a message of why it failed [Authorize] [HttpPost("recipeadd")] public async Task AddARecipesItems(int recipeId) From e30110bcff0d6f4db86b2fada9bba13ac796c476 Mon Sep 17 00:00:00 2001 From: Jeas0001 Date: Wed, 7 May 2025 12:30:12 +0200 Subject: [PATCH 08/12] Usercontrollers comments --- backend/API/Controllers/UserController.cs | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/backend/API/Controllers/UserController.cs b/backend/API/Controllers/UserController.cs index 8bb69eb..28e7ebe 100644 --- a/backend/API/Controllers/UserController.cs +++ b/backend/API/Controllers/UserController.cs @@ -17,6 +17,10 @@ namespace API.Controllers _userLogic = userLogic; } + /// + /// Gets the users email and username + /// + /// returns the users email, username and Id [Authorize] [HttpGet("get")] public async Task ReadUser() @@ -27,18 +31,33 @@ namespace API.Controllers return await _userLogic.GetUser(userId); } + /// + /// Logins a user + /// + /// The users login credentials + /// Returns a jwttoken their username, id and a refreshtoken [HttpPost("login")] public async Task Login([FromBody] LoginDTO loginDTO) { return await _userLogic.Login(loginDTO); } + /// + /// Create a new user + /// + /// contains the username email and password + /// returns a okobjectresult with a boolean that is true if it fails it returns a confliftobjectresult with a message of why it failed [HttpPost("create")] public async Task CreateUser([FromBody] CreateUserDTO userDTO) { return await _userLogic.RegisterUser(userDTO); } + /// + /// Changes the password of the user + /// + /// Contains the old password and the new one + /// returns a okobjectresult with a boolean that is true if it fails it returns a confliftobjectresult with a message of why it failed [Authorize] [HttpPut("change-password")] public async Task ChangePassword([FromBody] ChangePasswordDTO passwordDTO) @@ -49,6 +68,11 @@ namespace API.Controllers return await _userLogic.ChangePassword(passwordDTO, userId); } + /// + /// Edits the email and username of the user + /// + /// The updated username and email + /// returns a okobjectresult with a boolean that is true if it fails it returns a confliftobjectresult with a message of why it failed [Authorize] [HttpPut("update")] public async Task UpdateUser([FromBody] UpdateUserDTO userDTO) @@ -59,6 +83,10 @@ namespace API.Controllers return await _userLogic.EditProfile(userDTO, userId); } + /// + /// Deletes the user + /// + /// returns a okobjectresult with a boolean that is true if it fails it returns a confliftobjectresult with a message of why it failed [Authorize] [HttpDelete("delete")] public async Task DeleteUser() @@ -69,6 +97,11 @@ namespace API.Controllers return await _userLogic.DeleteUser(userId); } + /// + /// For when the jwt token is outdated + /// + /// contains a string with the refreshtoken + /// returns a new refreshtoken and new jwt token [HttpPost("refreshtoken")] public async Task RefreashToken([FromBody] RefreshTokenDTO refreshToken) { From fe90c243e2dd5aea9f60d1fbc074520d6d15dd88 Mon Sep 17 00:00:00 2001 From: Jeas0001 Date: Wed, 7 May 2025 13:05:59 +0200 Subject: [PATCH 09/12] RecipeDbAccess comments --- backend/API/DBAccess/RecipeDBaccess.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/API/DBAccess/RecipeDBaccess.cs b/backend/API/DBAccess/RecipeDBaccess.cs index b36b9be..403a878 100644 --- a/backend/API/DBAccess/RecipeDBaccess.cs +++ b/backend/API/DBAccess/RecipeDBaccess.cs @@ -14,6 +14,7 @@ namespace API.DBAccess _context = context; } + // Reads the users recipes public async Task> ReadRecipes(int userId) { var recipes = await _context.Users.Include(p => p.Recipes).FirstOrDefaultAsync(u => u.Id == userId); @@ -21,11 +22,13 @@ namespace API.DBAccess return recipes.Recipes; } + // Returns a specifik recipe public async Task ReadRecipe(int recipeId) { return await _context.Recipes.Include(r => r.Ingredients).Include(r => r.Directions).FirstOrDefaultAsync(r => r.Id == recipeId); } + // Adds a new recipe to the database public async Task CreateRecipe(Recipe recipe, int userId) { var recipes = await _context.Users.Include(p => p.Recipes).FirstOrDefaultAsync(u => u.Id == userId); @@ -39,6 +42,7 @@ namespace API.DBAccess return new ConflictObjectResult(new { message = "Could not save to database" }); } + // Updates the recipe in the database public async Task UpdateRecipe(Recipe recipe) { _context.Entry(recipe).State = EntityState.Modified; @@ -50,6 +54,7 @@ namespace API.DBAccess return new ConflictObjectResult(new { message = "Could not save to database" }); } + // Deletes the recipe from the database public async Task DeleteRecipe(Recipe recipe) { _context.Recipes.Remove(recipe); From dee0ea54a2c452d73495f21ed5e85dc7d5743e41 Mon Sep 17 00:00:00 2001 From: Jeas0001 Date: Wed, 7 May 2025 13:14:41 +0200 Subject: [PATCH 10/12] shoppinglistdbaccess comments --- backend/API/DBAccess/ShoppingListDBAccess.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/API/DBAccess/ShoppingListDBAccess.cs b/backend/API/DBAccess/ShoppingListDBAccess.cs index 6a6bb07..a9e7c68 100644 --- a/backend/API/DBAccess/ShoppingListDBAccess.cs +++ b/backend/API/DBAccess/ShoppingListDBAccess.cs @@ -14,6 +14,8 @@ namespace API.DBAccess { _context = context; } + + // Read the shoppinglist connected to the user public async Task ReadShoppingList(int userId) { var user = await _context.Users.Include(u => u.ShoppingList).FirstOrDefaultAsync(u => u.Id == userId); @@ -21,6 +23,7 @@ namespace API.DBAccess return user; } + // Updates the shoppinglist public async Task UpdateShoppingList(User user) { _context.Entry(user).State = EntityState.Modified; From 050ff896e16e927a27984ef7c93044b06e9cd7c0 Mon Sep 17 00:00:00 2001 From: Jeas0001 Date: Wed, 7 May 2025 13:33:36 +0200 Subject: [PATCH 11/12] UserDbAccess comments --- backend/API/DBAccess/UserDBAccess.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/backend/API/DBAccess/UserDBAccess.cs b/backend/API/DBAccess/UserDBAccess.cs index ac7711c..833db4e 100644 --- a/backend/API/DBAccess/UserDBAccess.cs +++ b/backend/API/DBAccess/UserDBAccess.cs @@ -13,16 +13,19 @@ namespace API.DBAccess _context = context; } + // Reads the user from the database public async Task ReadUser(int userId) { return await _context.Users.FirstOrDefaultAsync(u => u.Id == userId); } + // Reads all the users from the database public async Task> ReadAllUsers() { return await _context.Users.ToListAsync(); } + // Searches for a user with the username public async Task UserNameInUse(string username) { var user = await _context.Users.FirstOrDefaultAsync(u => u.UserName == username); @@ -30,6 +33,7 @@ namespace API.DBAccess return true; } + // Searches for a user with the email public async Task EmailInUse(string email) { var user = await _context.Users.FirstOrDefaultAsync(u => u.UserName == email); @@ -37,16 +41,19 @@ namespace API.DBAccess return true; } + // Searches for a user with refreshtoken and returns that user public async Task ReadUserByRefreshToken(string refreshToken) { return await _context.Users.FirstOrDefaultAsync(u => u.RefreshToken == refreshToken); } + // Gets all the data for a user public async Task ReadUserForDelete(int userId) { - return await _context.Users.FirstOrDefaultAsync(u => u.Id == userId); + return await _context.Users.Include(u => u.Recipes).ThenInclude(r => r.Ingredients).Include(u => u.Recipes).ThenInclude(r => r.Directions).Include(u => u.ShoppingList).FirstOrDefaultAsync(u => u.Id == userId); } + // Gets a user according to either the email or username public async Task ReadUserForLogin(string emailOrUsername) { if (emailOrUsername.Contains("@")) @@ -59,6 +66,7 @@ namespace API.DBAccess } } + // Adds a new user to the database public async Task CreateUser(User user) { _context.Users.Add(user); @@ -71,6 +79,7 @@ namespace API.DBAccess } + // Updates the user in the database public async Task UpdateUser(User user) { _context.Entry(user).State = EntityState.Modified; @@ -82,6 +91,7 @@ namespace API.DBAccess return new ConflictObjectResult(new { message = "Could not save to database" }); } + // Updates the password in the database public async Task UpdatePassword(User user) { _context.Entry(user).State = EntityState.Modified; @@ -93,6 +103,7 @@ namespace API.DBAccess return new ConflictObjectResult(new { message = "Could not save to database" }); } + // Deletes the user from the database public async Task DeleteUser(User user) { _context.Users.Remove(user); From f9296f05c1a2b685210574fafddcecc6c399d1d7 Mon Sep 17 00:00:00 2001 From: Reimar Date: Wed, 7 May 2025 12:04:41 +0200 Subject: [PATCH 12/12] Implement adding items to shopping list --- .../easyeat/helpers/shopping_list.kt | 20 +++++++++++ .../ui/shopping_list/ShoppingListFragment.kt | 35 ++++++++++++++++--- .../layout/dialog_add_to_shopping_list.xml | 6 +++- app/app/src/main/res/values/strings.xml | 1 + 4 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt diff --git a/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt b/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt new file mode 100644 index 0000000..740f9fb --- /dev/null +++ b/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt @@ -0,0 +1,20 @@ +package tech.mercantec.easyeat.helpers + +import android.content.Context +import kotlinx.serialization.Serializable + +@Serializable +data class ShoppingListItem(val id: Int, val name: String, val amount: Double?, val unit: String?, val checked: Boolean) + +fun getShoppingList(ctx: Context): Array { + return requestJson>(ctx, "GET", "/api/ShoppingList/get", null) +} + +@Serializable +data class AddShoppingItemRequest(val name: String, val amount: Double?, val unit: String?, val checked: Boolean) + +fun addShoppingItem(ctx: Context, name: String, amount: Double?, unit: String?) { + val request = AddShoppingItemRequest(name, amount, unit, false) + + requestJson(ctx, "POST", "/api/ShoppingList/add", request) +} diff --git a/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt b/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt index fef40ae..878cab7 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt @@ -1,15 +1,21 @@ package tech.mercantec.easyeat.ui.shopping_list import android.app.AlertDialog +import android.app.Dialog import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter +import android.widget.EditText import android.widget.Spinner +import android.widget.Toast import androidx.fragment.app.Fragment import tech.mercantec.easyeat.R import tech.mercantec.easyeat.databinding.FragmentShoppingListBinding +import tech.mercantec.easyeat.helpers.ApiRequestException +import tech.mercantec.easyeat.helpers.addShoppingItem +import kotlin.concurrent.thread class ShoppingListFragment : Fragment() { @@ -32,12 +38,33 @@ class ShoppingListFragment : Fragment() { val dialog = AlertDialog.Builder(activity) .setView(view) - .setPositiveButton(R.string.add_label, { dialog, id -> + .setPositiveButton(R.string.add_label) { dialog, id -> + val dialog = dialog as AlertDialog - }) - .setNegativeButton(R.string.cancel_label, { dialog, id -> + val amount = view.findViewById(R.id.amount).text.toString().toDouble() + val unit = view.findViewById(R.id.unit_selector).selectedItem.toString().ifEmpty { null } + val name = view.findViewById(R.id.name).text.toString() + + dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false + dialog.getButton(AlertDialog.BUTTON_NEGATIVE).isEnabled = false + + thread { + try { + addShoppingItem(requireContext(), name, amount, unit) + } catch (e: ApiRequestException) { + activity?.runOnUiThread { + Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() + } + } finally { + activity?.runOnUiThread { + dialog.dismiss() + } + } + } + } + .setNegativeButton(R.string.cancel_label) { dialog, _ -> dialog.cancel() - }) + } .create() dialog.show() diff --git a/app/app/src/main/res/layout/dialog_add_to_shopping_list.xml b/app/app/src/main/res/layout/dialog_add_to_shopping_list.xml index 3d9ae2d..db8d297 100644 --- a/app/app/src/main/res/layout/dialog_add_to_shopping_list.xml +++ b/app/app/src/main/res/layout/dialog_add_to_shopping_list.xml @@ -30,12 +30,14 @@ android:layout_height="wrap_content" android:layout_below="@id/amount_label" android:layout_alignParentStart="true" + android:inputType="numberDecimal" + android:importantForAutofill="no" android:hint="@string/ingredient_amount_hint" /> diff --git a/app/app/src/main/res/values/strings.xml b/app/app/src/main/res/values/strings.xml index e134a57..3c50a80 100644 --- a/app/app/src/main/res/values/strings.xml +++ b/app/app/src/main/res/values/strings.xml @@ -39,6 +39,7 @@ Change password Log out + g kg ml