From 987729d84eccf6b40deb137a1547973d65c34b36 Mon Sep 17 00:00:00 2001 From: Keith Wagner <244694+kpwags@users.noreply.github.com> Date: Mon, 3 Jul 2023 10:31:04 -0400 Subject: [PATCH] Added a Private/Public Filter Added private/public filters to not show private recipes unless the user is logged in. Closes #120 --- .../Interfaces/IRecipeRepository.cs | 24 +++---- .../Repositories/RecipeRepository.cs | 45 ++++++------ .../Extensions/HttpContextExtensions.cs | 14 ++++ .../Handlers/Queries/Recipes/GetAllRecipes.cs | 10 ++- .../Recipes/GetMostFavoritedRecipes.cs | 11 ++- .../Queries/Recipes/GetRecentRecipes.cs | 9 ++- .../Handlers/Queries/Recipes/GetRecipeById.cs | 2 +- .../Queries/Recipes/GetRecipesByCategory.cs | 10 ++- .../Queries/Recipes/GetRecipesByMeat.cs | 10 ++- .../Recipes/GetRecipesBySearchKeywords.cs | 11 ++- .../Queries/Recipes/GetRecipesByUser.cs | 10 ++- .../Queries/Recipes/GetUserRecipes.cs | 3 +- .../Queries/Recipes/QuickSearchRecipes.cs | 10 ++- .../Queries/Recipes/GetAllRecipesTests.cs | 15 ++-- .../Recipes/GetMostFavoritedRecipesTests.cs | 11 +-- .../Queries/Recipes/GetMostRecipesTests.cs | 11 +-- .../Recipes/GetRecipesByCategoryTests.cs | 17 +++-- .../Queries/Recipes/GetRecipesByMeatTests.cs | 17 +++-- .../GetRecipesBySearchKeywordsTests.cs | 15 ++-- .../Queries/Recipes/GetRecipesByUserTests.cs | 17 +++-- .../Recipes/GetRecipesForAdminTests.cs | 2 +- .../Queries/Recipes/GetUserRecipesTests.cs | 4 +- .../Recipes/QuickSearchRecipesTests.cs | 9 ++- frontend/src/App.tsx | 17 ++--- .../src/components/Elements/NavBar/NavBar.tsx | 70 ++++++++++--------- frontend/src/components/Pages/Login/Login.tsx | 9 ++- .../Pages/ViewRecipe/RecipeActions.tsx | 66 +++++++++-------- .../Pages/ViewRecipe/ViewRecipe.tsx | 11 ++- .../PrivateFilter/PrivateFilter.tsx | 23 ++++++ .../src/components/PrivateFilter/index.tsx | 2 + .../PrivateRecipe/PrivateRecipe.less | 13 ++++ .../PrivateRecipe/PrivateRecipe.tsx | 19 +++++ .../src/components/PrivateRecipe/index.tsx | 2 + .../ProtectedRoute/ProtectedRoute.tsx | 6 +- 34 files changed, 343 insertions(+), 182 deletions(-) create mode 100644 frontend/src/components/PrivateFilter/PrivateFilter.tsx create mode 100644 frontend/src/components/PrivateFilter/index.tsx create mode 100644 frontend/src/components/PrivateRecipe/PrivateRecipe.less create mode 100644 frontend/src/components/PrivateRecipe/PrivateRecipe.tsx create mode 100644 frontend/src/components/PrivateRecipe/index.tsx diff --git a/backend/src/DigitalFamilyCookbook.Data/Interfaces/IRecipeRepository.cs b/backend/src/DigitalFamilyCookbook.Data/Interfaces/IRecipeRepository.cs index 90c87d9..251f1e7 100644 --- a/backend/src/DigitalFamilyCookbook.Data/Interfaces/IRecipeRepository.cs +++ b/backend/src/DigitalFamilyCookbook.Data/Interfaces/IRecipeRepository.cs @@ -2,7 +2,7 @@ namespace DigitalFamilyCookbook.Data.Interfaces; public interface IRecipeRepository { - IEnumerable GetAll(); + IEnumerable GetAll(bool includePrivate = true); Recipe GetById(int recipeId); @@ -14,19 +14,19 @@ public interface IRecipeRepository Task DeleteRecipeImage(int recipeId); - IEnumerable GetRecipesForUser(string userAccountId); + IEnumerable GetRecipesForUser(string userAccountId, bool includePrivate); - (IEnumerable recipes, int totalRecipes) GetRecipesForUserPaginated(string userAcountId, int currentPage = 1, int recipesPerPage = 10); + (IEnumerable recipes, int totalRecipes) GetRecipesForUserPaginated(string userAcountId, bool includePrivate, int currentPage = 1, int recipesPerPage = 10); - IEnumerable GetRecipesForCategory(int categoryId); + IEnumerable GetRecipesForCategory(int categoryId, bool isLoggedIn); - (IEnumerable recipes, int totalRecipes) GetRecipesForCategoryPaginated(int categoryId, int currentPage = 1, int recipesPerPage = 10); + (IEnumerable recipes, int totalRecipes) GetRecipesForCategoryPaginated(int categoryId, bool includePrivate, int currentPage = 1, int recipesPerPage = 10); - IEnumerable GetRecipesForMeat(int meatId); + IEnumerable GetRecipesForMeat(int meatId, bool includePrivate); - (IEnumerable recipes, int totalRecipes) GetRecipesForMeatPaginated(int meatId, int currentPage = 1, int recipesPerPage = 10); + (IEnumerable recipes, int totalRecipes) GetRecipesForMeatPaginated(int meatId, bool includePrivate, int currentPage = 1, int recipesPerPage = 10); - (IEnumerable recipes, int totalRecipes) GetAllRecipesPaginated(int currentPage = 1, int recipesPerPage = 10); + (IEnumerable recipes, int totalRecipes) GetAllRecipesPaginated(bool includePrivate, int currentPage = 1, int recipesPerPage = 10); Task MarkRecipeAsFavorite(string userAccountId, int recipeId); @@ -36,11 +36,11 @@ public interface IRecipeRepository (IEnumerable recipes, int totalRecipes) GetFavoriteRecipesForUserPaginated(string userAccountId, int currentPage = 1, int recipesPerPage = 10); - IEnumerable GetRecentRecipes(int count); + IEnumerable GetRecentRecipes(int count, bool includePrivate); - IEnumerable GetMostFavoritedRecipes(int count); + IEnumerable GetMostFavoritedRecipes(int count, bool includePrivate); - (IEnumerable recipes, int totalRecipes) SearchRecipesPaginated(string keywords, int currentPage = 1, int recipesPerPage = 10); + (IEnumerable recipes, int totalRecipes) SearchRecipesPaginated(string keywords, bool includePrivate, int currentPage = 1, int recipesPerPage = 10); - IEnumerable QuickSearchRecipes(string keywords, int count = 10); + IEnumerable QuickSearchRecipes(string keywords, bool includePrivate, int count = 10); } diff --git a/backend/src/DigitalFamilyCookbook.Data/Repositories/RecipeRepository.cs b/backend/src/DigitalFamilyCookbook.Data/Repositories/RecipeRepository.cs index 2b3a49c..6a060d6 100644 --- a/backend/src/DigitalFamilyCookbook.Data/Repositories/RecipeRepository.cs +++ b/backend/src/DigitalFamilyCookbook.Data/Repositories/RecipeRepository.cs @@ -25,9 +25,10 @@ public RecipeRepository(ApplicationDbContext db, _stepRepository = stepRepository; } - public IEnumerable GetAll() + public IEnumerable GetAll(bool includePrivate = true) { return _db.Recipes + .Where(r => r.IsPublic || includePrivate) .Include(r => r.UserAccount) .Select(r => Recipe.FromDto(r)); } @@ -215,18 +216,18 @@ public async Task DeleteRecipeImage(int recipeId) await _db.SaveChangesAsync(); } - public IEnumerable GetRecipesForUser(string userAccountId) + public IEnumerable GetRecipesForUser(string userAccountId, bool includePrivate) { return _db.Recipes - .Where(r => r.UserAccountId == userAccountId) + .Where(r => r.UserAccountId == userAccountId && (includePrivate || r.IsPublic)) .Include(r => r.UserAccount) .Select(r => Recipe.FromDto(r)); } - public (IEnumerable recipes, int totalRecipes) GetRecipesForUserPaginated(string userAccountId, int currentPage = 1, int recipesPerPage = 10) + public (IEnumerable recipes, int totalRecipes) GetRecipesForUserPaginated(string userAccountId, bool includePrivate, int currentPage = 1, int recipesPerPage = 10) { var data = _db.Recipes - .Where(r => r.UserAccountId == userAccountId) + .Where(r => r.UserAccountId == userAccountId && (includePrivate || r.IsPublic)) .OrderBy(r => r.Name) .Skip(currentPage == 1 ? 0 : (currentPage - 1) * recipesPerPage) .Include(r => r.UserAccount) @@ -247,18 +248,18 @@ public IEnumerable GetRecipesForUser(string userAccountId) return (recipes, recipeCount); } - public IEnumerable GetRecipesForCategory(int categoryId) + public IEnumerable GetRecipesForCategory(int categoryId, bool includePrivate) { return _db.Recipes .Include(r => r.RecipeCategories) - .Where(r => r.RecipeCategories.Select(rc => rc.CategoryId).Contains(categoryId)) + .Where(r => r.RecipeCategories.Select(rc => rc.CategoryId).Contains(categoryId) && (includePrivate || r.IsPublic)) .Select(r => Recipe.FromDto(r)); } - public (IEnumerable recipes, int totalRecipes) GetRecipesForCategoryPaginated(int categoryId, int currentPage = 1, int recipesPerPage = 10) + public (IEnumerable recipes, int totalRecipes) GetRecipesForCategoryPaginated(int categoryId, bool includePrivate, int currentPage = 1, int recipesPerPage = 10) { var data = _db.Recipes - .Where(r => r.RecipeCategories.Select(rc => rc.CategoryId).Contains(categoryId)) + .Where(r => r.RecipeCategories.Select(rc => rc.CategoryId).Contains(categoryId) && (includePrivate || r.IsPublic)) .OrderBy(r => r.Name) .Skip(currentPage == 1 ? 0 : (currentPage - 1) * recipesPerPage) .Include(r => r.RecipeCategories) @@ -281,18 +282,18 @@ public IEnumerable GetRecipesForCategory(int categoryId) return (recipes, recipeCount); } - public IEnumerable GetRecipesForMeat(int meatId) + public IEnumerable GetRecipesForMeat(int meatId, bool includePrivate) { return _db.Recipes .Include(r => r.RecipeMeats) - .Where(r => r.RecipeMeats.Select(rm => rm.MeatId).Contains(meatId)) + .Where(r => r.RecipeMeats.Select(rm => rm.MeatId).Contains(meatId) && (includePrivate || r.IsPublic)) .Select(r => Recipe.FromDto(r)); } - public (IEnumerable recipes, int totalRecipes) GetRecipesForMeatPaginated(int meatId, int currentPage = 1, int recipesPerPage = 10) + public (IEnumerable recipes, int totalRecipes) GetRecipesForMeatPaginated(int meatId, bool includePrivate, int currentPage = 1, int recipesPerPage = 10) { var data = _db.Recipes - .Where(r => r.RecipeMeats.Select(rc => rc.MeatId).Contains(meatId)) + .Where(r => r.RecipeMeats.Select(rc => rc.MeatId).Contains(meatId) && (includePrivate || r.IsPublic)) .OrderBy(r => r.Name) .Skip(currentPage == 1 ? 0 : (currentPage - 1) * recipesPerPage) .Include(r => r.RecipeMeats) @@ -315,11 +316,12 @@ public IEnumerable GetRecipesForMeat(int meatId) return (recipes, recipeCount); } - public (IEnumerable recipes, int totalRecipes) GetAllRecipesPaginated(int currentPage = 1, int recipesPerPage = 10) + public (IEnumerable recipes, int totalRecipes) GetAllRecipesPaginated(bool includePrivate, int currentPage = 1, int recipesPerPage = 10) { var allRecipes = _db.Recipes.Include(r => r.UserAccount); var data = allRecipes + .Where(r => includePrivate || r.IsPublic) .OrderBy(r => r.Name) .Skip(currentPage == 1 ? 0 : (currentPage - 1) * recipesPerPage) .Include(r => r.UserAccount) @@ -403,9 +405,10 @@ public bool IsRecipeFavoriteForUser(string userAccountId, int recipeId) return (recipes, recipeCount); } - public IEnumerable GetRecentRecipes(int count) + public IEnumerable GetRecentRecipes(int count, bool includePrivate) { var data = _db.Recipes + .Where(r => includePrivate || r.IsPublic) .OrderByDescending(r => r.DateCreated) .Include(r => r.UserAccount) .Include(r => r.RecipeFavorites) @@ -425,9 +428,11 @@ public IEnumerable GetRecentRecipes(int count) return recipes; } - public IEnumerable GetMostFavoritedRecipes(int count) + public IEnumerable GetMostFavoritedRecipes(int count, bool includePrivate) { var favoriteRecipes = _db.RecipeFavorites + .Include(rf => rf.Recipe) + .Where(rf => rf.Recipe.IsPublic || includePrivate) .ToList() .GroupBy(rf => rf.RecipeId) .Select(r => new RecipeGrouping() { RecipeId = r.Key, RecipeCount = r.Count() }) @@ -468,10 +473,10 @@ public IEnumerable GetMostFavoritedRecipes(int count) return recipesOutput; } - public (IEnumerable recipes, int totalRecipes) SearchRecipesPaginated(string keywords, int currentPage = 1, int recipesPerPage = 10) + public (IEnumerable recipes, int totalRecipes) SearchRecipesPaginated(string keywords, bool includePrivate, int currentPage = 1, int recipesPerPage = 10) { var data = _db.Recipes - .Where(r => r.Name.ToLower().Contains(keywords.ToLower()) || (r.Description ?? "").ToLower().Contains(keywords.ToLower())) + .Where(r => (includePrivate || r.IsPublic) && (r.Name.ToLower().Contains(keywords.ToLower()) || (r.Description ?? "").ToLower().Contains(keywords.ToLower()))) .OrderBy(r => r.Name) .Skip(currentPage == 1 ? 0 : (currentPage - 1) * recipesPerPage) .Include(r => r.UserAccount) @@ -494,10 +499,10 @@ public IEnumerable GetMostFavoritedRecipes(int count) return (recipes, recipeCount); } - public IEnumerable QuickSearchRecipes(string keywords, int count = 10) + public IEnumerable QuickSearchRecipes(string keywords, bool includePrivate, int count = 10) { var recipes = _db.Recipes - .Where(r => r.Name.ToLower().Contains(keywords.ToLower()) || (r.Description ?? "").ToLower().Contains(keywords.ToLower())) + .Where(r => (includePrivate || r.IsPublic) && (r.Name.ToLower().Contains(keywords.ToLower()) || (r.Description ?? "").ToLower().Contains(keywords.ToLower()))) .OrderBy(r => r.Name) .Take(count); diff --git a/backend/src/DigitalFamilyCookbook/Extensions/HttpContextExtensions.cs b/backend/src/DigitalFamilyCookbook/Extensions/HttpContextExtensions.cs index c4724a7..1ec2357 100644 --- a/backend/src/DigitalFamilyCookbook/Extensions/HttpContextExtensions.cs +++ b/backend/src/DigitalFamilyCookbook/Extensions/HttpContextExtensions.cs @@ -34,6 +34,20 @@ public static UserAccountApiModel CurrentUser(this HttpContext context, bool thr return UserAccountApiModel.None(); } + public static bool IsUserLoggedIn(this HttpContext context) + { + if (context.Items.ContainsKey("User")) + { + if (context.Items["User"] is not null) + { + var user = context.Items["User"] as UserAccountApiModel; + + return user is not null; + } + } + + return false; + } public static string? GetAccessToken(this HttpContext context) { diff --git a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetAllRecipes.cs b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetAllRecipes.cs index 5cc9381..523170e 100644 --- a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetAllRecipes.cs +++ b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetAllRecipes.cs @@ -8,24 +8,28 @@ public class Handler : IRequestHandler> Handle(Query request, CancellationToken cancellationToken) { try { - var (data, totalRecipes) = await Task.FromResult(_recipeRepository.GetAllRecipesPaginated(request.PageNumber, request.RecipesPerPage)); + var includePrivateRecipes = _httpContextAccessor.HttpContext?.IsUserLoggedIn() ?? false; + var (data, totalRecipes) = await Task.FromResult(_recipeRepository.GetAllRecipesPaginated(includePrivateRecipes, request.PageNumber, request.RecipesPerPage)); var recipes = data - .Select(r => RecipeApiModel.FromDomainModel(r)) + .Select(RecipeApiModel.FromDomainModel) .OrderBy(r => r.Name) .ToList(); diff --git a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetMostFavoritedRecipes.cs b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetMostFavoritedRecipes.cs index ff989c6..ec3cd70 100644 --- a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetMostFavoritedRecipes.cs +++ b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetMostFavoritedRecipes.cs @@ -6,22 +6,27 @@ public class Handler : IRequestHandler> Handle(Query request, CancellationToken cancellationToken) { - var data = await Task.FromResult(_recipeRepository.GetMostFavoritedRecipes(request.Count)); + var includePrivateRecipes = _httpContextAccessor.HttpContext?.IsUserLoggedIn() ?? false; + + var data = await Task.FromResult(_recipeRepository.GetMostFavoritedRecipes(request.Count, includePrivateRecipes)); var recipes = data - .Select(r => RecipeApiModel.FromDomainModel(r)) + .Select(RecipeApiModel.FromDomainModel) .ToList(); if (!request.IncludeImages) diff --git a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecentRecipes.cs b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecentRecipes.cs index 04d49f7..184fea9 100644 --- a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecentRecipes.cs +++ b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecentRecipes.cs @@ -6,19 +6,22 @@ public class Handler : IRequestHandler> Handle(Query request, CancellationToken cancellationToken) { - var data = await Task.FromResult(_recipeRepository.GetRecentRecipes(request.Count)); + var includePrivateRecipes = _httpContextAccessor.HttpContext?.IsUserLoggedIn() ?? false; + var data = await Task.FromResult(_recipeRepository.GetRecentRecipes(request.Count, includePrivateRecipes)); var recipes = data - .Select(r => RecipeApiModel.FromDomainModel(r)) + .Select(RecipeApiModel.FromDomainModel) .OrderBy(r => r.Name) .ToList(); diff --git a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipeById.cs b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipeById.cs index ab77650..1a62062 100644 --- a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipeById.cs +++ b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipeById.cs @@ -53,7 +53,7 @@ public async Task> Handle(Query request, Cancell recipe.Categories = _categoryRepostory.GetForRecipe(recipe.RecipeId); recipe.Meats = _meatRepository.GetForRecipe(recipe.RecipeId); - var user = _httpContextAccessor.HttpContext?.CurrentUser(); + var user = _httpContextAccessor.HttpContext?.CurrentUser(false); if (user is not null) { diff --git a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipesByCategory.cs b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipesByCategory.cs index e213bc9..6678bbe 100644 --- a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipesByCategory.cs +++ b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipesByCategory.cs @@ -9,28 +9,32 @@ public class Handler : IRequestHandler> Handle(Query request, CancellationToken cancellationToken) { try { + var includePrivateRecipes = _httpContextAccessor.HttpContext?.IsUserLoggedIn() ?? false; var category = _categoryRepository.Get(request.CategoryId); - var (data, totalRecipes) = await Task.FromResult(_recipeRepository.GetRecipesForCategoryPaginated(request.CategoryId, request.PageNumber, request.RecipesPerPage)); + var (data, totalRecipes) = await Task.FromResult(_recipeRepository.GetRecipesForCategoryPaginated(request.CategoryId, includePrivateRecipes, request.PageNumber, request.RecipesPerPage)); var recipes = data - .Select(r => RecipeApiModel.FromDomainModel(r)) + .Select(RecipeApiModel.FromDomainModel) .OrderBy(r => r.Name) .ToList(); diff --git a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipesByMeat.cs b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipesByMeat.cs index 49fdcf2..7495a24 100644 --- a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipesByMeat.cs +++ b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipesByMeat.cs @@ -9,26 +9,30 @@ public class Handler : IRequestHandler> Handle(Query request, CancellationToken cancellationToken) { try { + var includePrivateRecipes = _httpContextAccessor.HttpContext?.IsUserLoggedIn() ?? false; var meat = _meatRepository.Get(request.MeatId); - var (data, totalRecipes) = await Task.FromResult(_recipeRepository.GetRecipesForMeatPaginated(request.MeatId, request.PageNumber, request.RecipesPerPage)); + var (data, totalRecipes) = await Task.FromResult(_recipeRepository.GetRecipesForMeatPaginated(request.MeatId, includePrivateRecipes, request.PageNumber, request.RecipesPerPage)); var recipes = data - .Select(r => RecipeApiModel.FromDomainModel(r)) + .Select(RecipeApiModel.FromDomainModel) .OrderBy(r => r.Name) .ToList(); diff --git a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipesBySearchKeywords.cs b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipesBySearchKeywords.cs index 927a580..c2e528b 100644 --- a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipesBySearchKeywords.cs +++ b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipesBySearchKeywords.cs @@ -6,12 +6,15 @@ public class Handler : IRequestHandler> Handle(Query request, CancellationToken cancellationToken) @@ -23,10 +26,12 @@ public async Task> Handle(Query request, throw new Exception("No search keywords provided"); } - var (data, totalRecipes) = await Task.FromResult(_recipeRepository.SearchRecipesPaginated(request.Keywords, request.PageNumber, request.RecipesPerPage)); + var includePrivateRecipes = _httpContextAccessor.HttpContext?.IsUserLoggedIn() ?? false; + + var (data, totalRecipes) = await Task.FromResult(_recipeRepository.SearchRecipesPaginated(request.Keywords, includePrivateRecipes, request.PageNumber, request.RecipesPerPage)); var recipes = data - .Select(r => RecipeApiModel.FromDomainModel(r)) + .Select(RecipeApiModel.FromDomainModel) .OrderBy(r => r.Name) .ToList(); diff --git a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipesByUser.cs b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipesByUser.cs index 838d714..ef3e5dd 100644 --- a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipesByUser.cs +++ b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetRecipesByUser.cs @@ -9,27 +9,31 @@ public class Handler : IRequestHandler> Handle(Query request, CancellationToken cancellationToken) { try { + var includePrivateRecipes = _httpContextAccessor.HttpContext?.IsUserLoggedIn() ?? false; var userAccount = await _userAccountRepository.GetUserAccountById(request.UserAccountId); - var (data, totalRecipes) = await Task.FromResult(_recipeRepository.GetRecipesForUserPaginated(request.UserAccountId, request.PageNumber, request.RecipesPerPage)); + var (data, totalRecipes) = await Task.FromResult(_recipeRepository.GetRecipesForUserPaginated(request.UserAccountId, includePrivateRecipes, request.PageNumber, request.RecipesPerPage)); var recipes = data - .Select(r => RecipeApiModel.FromDomainModel(r)) + .Select(RecipeApiModel.FromDomainModel) .OrderBy(r => r.Name) .ToList(); diff --git a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetUserRecipes.cs b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetUserRecipes.cs index d827ba8..ef22db5 100644 --- a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetUserRecipes.cs +++ b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/GetUserRecipes.cs @@ -17,6 +17,7 @@ public async Task>> Handle(Q { try { + var includePrivateRecipes = _httpContextAccessor.HttpContext?.IsUserLoggedIn() ?? false; var userAccountId = request.UserAccountId; if (userAccountId == string.Empty) @@ -31,7 +32,7 @@ public async Task>> Handle(Q userAccountId = user.Id; } - var data = await Task.FromResult(_recipeRepository.GetRecipesForUser(userAccountId)); + var data = await Task.FromResult(_recipeRepository.GetRecipesForUser(userAccountId, includePrivateRecipes)); var recipes = data .Select(r => RecipeApiModel.FromDomainModel(r)) diff --git a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/QuickSearchRecipes.cs b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/QuickSearchRecipes.cs index 574a3da..973f697 100644 --- a/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/QuickSearchRecipes.cs +++ b/backend/src/DigitalFamilyCookbook/Handlers/Queries/Recipes/QuickSearchRecipes.cs @@ -5,10 +5,12 @@ public class QuickSearchRecipes public class Handler : IRequestHandler> { private readonly IRecipeRepository _recipeRepository; + private readonly IHttpContextAccessor _httpContextAccessor; - public Handler(IRecipeRepository recipeRepository) + public Handler(IRecipeRepository recipeRepository, IHttpContextAccessor httpContextAccessor) { _recipeRepository = recipeRepository; + _httpContextAccessor = httpContextAccessor; } public async Task> Handle(Query request, CancellationToken cancellationToken) @@ -20,10 +22,12 @@ public async Task> Handle(Query request, Can throw new Exception("No search keywords provided"); } - var recipes = await Task.FromResult(_recipeRepository.QuickSearchRecipes(request.Keywords, request.MaxRecipes)); + var includePrivateRecipes = _httpContextAccessor.HttpContext?.IsUserLoggedIn() ?? false; + + var recipes = await Task.FromResult(_recipeRepository.QuickSearchRecipes(request.Keywords, includePrivateRecipes, request.MaxRecipes)); return recipes - .Select(r => RecipeApiModel.FromDomainModel(r)) + .Select(RecipeApiModel.FromDomainModel) .OrderBy(r => r.Name) .ToList(); } diff --git a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetAllRecipesTests.cs b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetAllRecipesTests.cs index 80bdd7c..9c70469 100644 --- a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetAllRecipesTests.cs +++ b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetAllRecipesTests.cs @@ -1,4 +1,5 @@ using DigitalFamilyCookbook.Handlers.Queries.Recipes; +using Microsoft.AspNetCore.Http; namespace DigitalFamilyCookbook.Tests.Handlers.Queries.Recipes; @@ -6,11 +7,13 @@ public class GetAllRecipesTests { private Mock _recipeRepository; private Mock _fileService; + private Mock _httpContextAccessor; public GetAllRecipesTests() { - _recipeRepository = new Mock(); - _fileService = new Mock(); + _recipeRepository = new(); + _fileService = new(); + _httpContextAccessor = new(); } [Fact] @@ -19,7 +22,7 @@ public async Task ItReturnsRecipes() var recipes = MockRecipe.GenerateDomainModelList(10); _recipeRepository - .Setup(r => r.GetAllRecipesPaginated(It.IsAny(), It.IsAny())) + .Setup(r => r.GetAllRecipesPaginated(It.IsAny(), It.IsAny(), It.IsAny())) .Returns((recipes, 10)); var query = new GetAllRecipes.Query @@ -28,7 +31,7 @@ public async Task ItReturnsRecipes() IncludeImages = false, }; - var handler = new GetAllRecipes.Handler(_recipeRepository.Object, _fileService.Object); + var handler = new GetAllRecipes.Handler(_recipeRepository.Object, _fileService.Object, _httpContextAccessor.Object); var result = await handler.Handle(query, new CancellationToken()); @@ -45,7 +48,7 @@ public async Task ItReturnsRecipesWithImages() var recipes = MockRecipe.GenerateDomainModelList(10); _recipeRepository - .Setup(r => r.GetAllRecipesPaginated(It.IsAny(), It.IsAny())) + .Setup(r => r.GetAllRecipesPaginated(It.IsAny(), It.IsAny(), It.IsAny())) .Returns((recipes, 10)); _fileService @@ -58,7 +61,7 @@ public async Task ItReturnsRecipesWithImages() IncludeImages = true, }; - var handler = new GetAllRecipes.Handler(_recipeRepository.Object, _fileService.Object); + var handler = new GetAllRecipes.Handler(_recipeRepository.Object, _fileService.Object, _httpContextAccessor.Object); var result = await handler.Handle(query, new CancellationToken()); diff --git a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetMostFavoritedRecipesTests.cs b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetMostFavoritedRecipesTests.cs index 8385c2c..0fef4ff 100644 --- a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetMostFavoritedRecipesTests.cs +++ b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetMostFavoritedRecipesTests.cs @@ -1,4 +1,5 @@ using DigitalFamilyCookbook.Handlers.Queries.Recipes; +using Microsoft.AspNetCore.Http; namespace DigitalFamilyCookbook.Tests.Handlers.Queries.Recipes; @@ -6,11 +7,13 @@ public class GetMostFavoritedRecipesTests { private readonly Mock _recipeRepository; private readonly Mock _fileService; + private readonly Mock _httpContextAccessor; public GetMostFavoritedRecipesTests() { - _recipeRepository = new Mock(); - _fileService = new Mock(); + _recipeRepository = new(); + _fileService = new(); + _httpContextAccessor = new(); } [Fact] @@ -19,7 +22,7 @@ public async Task ItRetrievesTheMostRecentRecipes() var recipes = MockRecipe.GenerateDomainModelList(10); _recipeRepository - .Setup(r => r.GetMostFavoritedRecipes(10)) + .Setup(r => r.GetMostFavoritedRecipes(10, It.IsAny())) .Returns(recipes); var query = new GetMostFavoritedRecipes.Query @@ -28,7 +31,7 @@ public async Task ItRetrievesTheMostRecentRecipes() IncludeImages = false, }; - var handler = new GetMostFavoritedRecipes.Handler(_recipeRepository.Object, _fileService.Object); + var handler = new GetMostFavoritedRecipes.Handler(_recipeRepository.Object, _fileService.Object, _httpContextAccessor.Object); var result = await handler.Handle(query, new CancellationToken()); diff --git a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetMostRecipesTests.cs b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetMostRecipesTests.cs index dee0f6e..4281ebd 100644 --- a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetMostRecipesTests.cs +++ b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetMostRecipesTests.cs @@ -1,4 +1,5 @@ using DigitalFamilyCookbook.Handlers.Queries.Recipes; +using Microsoft.AspNetCore.Http; namespace DigitalFamilyCookbook.Tests.Handlers.Queries.Recipes; @@ -6,11 +7,13 @@ public class GetMostRecipesTests { private readonly Mock _recipeRepository; private readonly Mock _fileService; + private readonly Mock _httpContextAccessor; public GetMostRecipesTests() { - _recipeRepository = new Mock(); - _fileService = new Mock(); + _recipeRepository = new(); + _fileService = new(); + _httpContextAccessor = new(); } [Fact] @@ -19,7 +22,7 @@ public async Task ItRetrievesTheMostRecentRecipes() var recipes = MockRecipe.GenerateDomainModelList(10); _recipeRepository - .Setup(r => r.GetRecentRecipes(10)) + .Setup(r => r.GetRecentRecipes(10, It.IsAny())) .Returns(recipes); var query = new GetRecentRecipes.Query @@ -28,7 +31,7 @@ public async Task ItRetrievesTheMostRecentRecipes() IncludeImages = false, }; - var handler = new GetRecentRecipes.Handler(_recipeRepository.Object, _fileService.Object); + var handler = new GetRecentRecipes.Handler(_recipeRepository.Object, _fileService.Object, _httpContextAccessor.Object); var result = await handler.Handle(query, new CancellationToken()); diff --git a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesByCategoryTests.cs b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesByCategoryTests.cs index b5bccb8..c14e83e 100644 --- a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesByCategoryTests.cs +++ b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesByCategoryTests.cs @@ -1,4 +1,5 @@ using DigitalFamilyCookbook.Handlers.Queries.Recipes; +using Microsoft.AspNetCore.Http; namespace DigitalFamilyCookbook.Tests.Handlers.Queries.Recipes; @@ -7,12 +8,14 @@ public class GetRecipesByCategoryTests private Mock _recipeRepository; private Mock _categoryRepository; private Mock _fileService; + private Mock _httpContextAccessor; public GetRecipesByCategoryTests() { - _recipeRepository = new Mock(); - _categoryRepository = new Mock(); - _fileService = new Mock(); + _recipeRepository = new(); + _categoryRepository = new(); + _fileService = new(); + _httpContextAccessor = new(); } [Fact] @@ -21,7 +24,7 @@ public async Task ItReturnsRecipesForAGivenCategory() var recipes = MockRecipe.GenerateDomainModelList(10); _recipeRepository - .Setup(r => r.GetRecipesForCategoryPaginated(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(r => r.GetRecipesForCategoryPaginated(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns((recipes, 10)); _categoryRepository @@ -34,7 +37,7 @@ public async Task ItReturnsRecipesForAGivenCategory() IncludeImages = false, }; - var handler = new GetRecipesByCategory.Handler(_recipeRepository.Object, _categoryRepository.Object, _fileService.Object); + var handler = new GetRecipesByCategory.Handler(_recipeRepository.Object, _categoryRepository.Object, _fileService.Object, _httpContextAccessor.Object); var result = await handler.Handle(query, new CancellationToken()); @@ -51,7 +54,7 @@ public async Task ItReturnsRecipesForAGivenCategoryWithImages() var recipes = MockRecipe.GenerateDomainModelList(10); _recipeRepository - .Setup(r => r.GetRecipesForCategoryPaginated(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(r => r.GetRecipesForCategoryPaginated(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns((recipes, 10)); _categoryRepository @@ -68,7 +71,7 @@ public async Task ItReturnsRecipesForAGivenCategoryWithImages() IncludeImages = true, }; - var handler = new GetRecipesByCategory.Handler(_recipeRepository.Object, _categoryRepository.Object, _fileService.Object); + var handler = new GetRecipesByCategory.Handler(_recipeRepository.Object, _categoryRepository.Object, _fileService.Object, _httpContextAccessor.Object); var result = await handler.Handle(query, new CancellationToken()); diff --git a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesByMeatTests.cs b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesByMeatTests.cs index 5f58169..f98fa19 100644 --- a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesByMeatTests.cs +++ b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesByMeatTests.cs @@ -1,4 +1,5 @@ using DigitalFamilyCookbook.Handlers.Queries.Recipes; +using Microsoft.AspNetCore.Http; namespace DigitalFamilyCookbook.Tests.Handlers.Queries.Recipes; @@ -7,12 +8,14 @@ public class GetRecipesByMeatTests private Mock _recipeRepository; private Mock _meatRepository; private Mock _fileService; + private Mock _httpContextAccessor; public GetRecipesByMeatTests() { - _recipeRepository = new Mock(); - _meatRepository = new Mock(); - _fileService = new Mock(); + _recipeRepository = new(); + _meatRepository = new(); + _fileService = new(); + _httpContextAccessor = new(); } [Fact] @@ -21,7 +24,7 @@ public async Task ItReturnsRecipesForAGivenMeat() var recipes = MockRecipe.GenerateDomainModelList(10); _recipeRepository - .Setup(r => r.GetRecipesForMeatPaginated(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(r => r.GetRecipesForMeatPaginated(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns((recipes, 10)); _meatRepository @@ -34,7 +37,7 @@ public async Task ItReturnsRecipesForAGivenMeat() IncludeImages = false, }; - var handler = new GetRecipesByMeat.Handler(_recipeRepository.Object, _meatRepository.Object, _fileService.Object); + var handler = new GetRecipesByMeat.Handler(_recipeRepository.Object, _meatRepository.Object, _fileService.Object, _httpContextAccessor.Object); var result = await handler.Handle(query, new CancellationToken()); @@ -51,7 +54,7 @@ public async Task ItReturnsRecipesForAGivenMeatWithImages() var recipes = MockRecipe.GenerateDomainModelList(10); _recipeRepository - .Setup(r => r.GetRecipesForMeatPaginated(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(r => r.GetRecipesForMeatPaginated(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns((recipes, 10)); _meatRepository @@ -68,7 +71,7 @@ public async Task ItReturnsRecipesForAGivenMeatWithImages() IncludeImages = true, }; - var handler = new GetRecipesByMeat.Handler(_recipeRepository.Object, _meatRepository.Object, _fileService.Object); + var handler = new GetRecipesByMeat.Handler(_recipeRepository.Object, _meatRepository.Object, _fileService.Object, _httpContextAccessor.Object); var result = await handler.Handle(query, new CancellationToken()); diff --git a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesBySearchKeywordsTests.cs b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesBySearchKeywordsTests.cs index 2ce85e4..0e7ee54 100644 --- a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesBySearchKeywordsTests.cs +++ b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesBySearchKeywordsTests.cs @@ -1,4 +1,5 @@ using DigitalFamilyCookbook.Handlers.Queries.Recipes; +using Microsoft.AspNetCore.Http; namespace DigitalFamilyCookbook.Tests.Handlers.Queries.Recipes; @@ -6,11 +7,13 @@ public class GetRecipesBySearchKeywordsTests { private Mock _recipeRepository; private Mock _fileService; + private Mock _httpContextAccessor; public GetRecipesBySearchKeywordsTests() { - _recipeRepository = new Mock(); - _fileService = new Mock(); + _recipeRepository = new(); + _fileService = new(); + _httpContextAccessor = new(); } [Fact] @@ -19,7 +22,7 @@ public async Task ItReturnsRecipesForAGivenMeat() var recipes = MockRecipe.GenerateDomainModelList(10); _recipeRepository - .Setup(r => r.SearchRecipesPaginated(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(r => r.SearchRecipesPaginated(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns((recipes, 10)); var query = new GetRecipesBySearchKeywords.Query @@ -28,7 +31,7 @@ public async Task ItReturnsRecipesForAGivenMeat() IncludeImages = false, }; - var handler = new GetRecipesBySearchKeywords.Handler(_recipeRepository.Object, _fileService.Object); + var handler = new GetRecipesBySearchKeywords.Handler(_recipeRepository.Object, _fileService.Object, _httpContextAccessor.Object); var result = await handler.Handle(query, new CancellationToken()); @@ -45,7 +48,7 @@ public async Task ItReturnsRecipesForAGivenMeatWithImages() var recipes = MockRecipe.GenerateDomainModelList(10); _recipeRepository - .Setup(r => r.SearchRecipesPaginated(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(r => r.SearchRecipesPaginated(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns((recipes, 10)); _fileService @@ -58,7 +61,7 @@ public async Task ItReturnsRecipesForAGivenMeatWithImages() IncludeImages = true, }; - var handler = new GetRecipesBySearchKeywords.Handler(_recipeRepository.Object, _fileService.Object); + var handler = new GetRecipesBySearchKeywords.Handler(_recipeRepository.Object, _fileService.Object, _httpContextAccessor.Object); var result = await handler.Handle(query, new CancellationToken()); diff --git a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesByUserTests.cs b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesByUserTests.cs index 423d883..e2031c8 100644 --- a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesByUserTests.cs +++ b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesByUserTests.cs @@ -1,4 +1,5 @@ using DigitalFamilyCookbook.Handlers.Queries.Recipes; +using Microsoft.AspNetCore.Http; namespace DigitalFamilyCookbook.Tests.Handlers.Queries.Recipes; @@ -7,12 +8,14 @@ public class GetRecipesByUserTests private Mock _recipeRepository; private Mock _userAccountRepository; private Mock _fileService; + private Mock _httpContextAccessor; public GetRecipesByUserTests() { - _recipeRepository = new Mock(); - _userAccountRepository = new Mock(); - _fileService = new Mock(); + _recipeRepository = new(); + _userAccountRepository = new (); + _fileService = new (); + _httpContextAccessor = new(); } [Fact] @@ -21,7 +24,7 @@ public async Task ItReturnsRecipesForAGivenUser() var recipes = MockRecipe.GenerateDomainModelList(10); _recipeRepository - .Setup(r => r.GetRecipesForUserPaginated(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(r => r.GetRecipesForUserPaginated(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns((recipes, 10)); _userAccountRepository @@ -34,7 +37,7 @@ public async Task ItReturnsRecipesForAGivenUser() IncludeImages = false, }; - var handler = new GetRecipesByUser.Handler(_recipeRepository.Object, _userAccountRepository.Object, _fileService.Object); + var handler = new GetRecipesByUser.Handler(_recipeRepository.Object, _userAccountRepository.Object, _fileService.Object, _httpContextAccessor.Object); var result = await handler.Handle(query, new CancellationToken()); @@ -51,7 +54,7 @@ public async Task ItReturnsRecipesForAGivenUserWithImages() var recipes = MockRecipe.GenerateDomainModelList(10); _recipeRepository - .Setup(r => r.GetRecipesForUserPaginated(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(r => r.GetRecipesForUserPaginated(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns((recipes, 10)); _userAccountRepository @@ -68,7 +71,7 @@ public async Task ItReturnsRecipesForAGivenUserWithImages() IncludeImages = true, }; - var handler = new GetRecipesByUser.Handler(_recipeRepository.Object, _userAccountRepository.Object, _fileService.Object); + var handler = new GetRecipesByUser.Handler(_recipeRepository.Object, _userAccountRepository.Object, _fileService.Object, _httpContextAccessor.Object); var result = await handler.Handle(query, new CancellationToken()); diff --git a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesForAdminTests.cs b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesForAdminTests.cs index 2bc3811..9d26811 100644 --- a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesForAdminTests.cs +++ b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetRecipesForAdminTests.cs @@ -17,7 +17,7 @@ public async Task ItReturnsAllRecipes() var recipes = MockRecipe.GenerateDomainModelList(10); _recipeRepository - .Setup(r => r.GetAll()) + .Setup(r => r.GetAll(It.IsAny())) .Returns(recipes); var query = new GetRecipesForAdmin.Query(); diff --git a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetUserRecipesTests.cs b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetUserRecipesTests.cs index 8ce0f41..894b184 100644 --- a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetUserRecipesTests.cs +++ b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/GetUserRecipesTests.cs @@ -18,7 +18,7 @@ public async Task ItReturnsAUsersRecipesWhenTheIdIsPassedInTheQuery() var recipes = MockRecipe.GenerateDomainModelList(10); _recipeRepository - .Setup(r => r.GetRecipesForUser(It.IsAny())) + .Setup(r => r.GetRecipesForUser(It.IsAny(), It.IsAny())) .Returns(recipes); var query = new GetUserRecipes.Query { UserAccountId = MockDataGenerator.RandomId() }; @@ -38,7 +38,7 @@ public async Task ItReturnsAUsersRecipesWhenTheIdIsPulledFromHttpContext() var recipes = MockRecipe.GenerateDomainModelList(10); _recipeRepository - .Setup(r => r.GetRecipesForUser(userAccount.Id)) + .Setup(r => r.GetRecipesForUser(userAccount.Id, It.IsAny())) .Returns(recipes); var httpContextAccessor = MockSession.BuildSession(userAccount); diff --git a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/QuickSearchRecipesTests.cs b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/QuickSearchRecipesTests.cs index 8aaa4ec..b68a660 100644 --- a/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/QuickSearchRecipesTests.cs +++ b/backend/tests/DigitalFamilyCookbook.Tests/Handlers/Queries/Recipes/QuickSearchRecipesTests.cs @@ -1,14 +1,17 @@ using DigitalFamilyCookbook.Handlers.Queries.Recipes; +using Microsoft.AspNetCore.Http; namespace DigitalFamilyCookbook.Tests.Handlers.Queries.Recipes; public class QuickSearchRecipesTests { - private Mock _recipeRepository; + private readonly Mock _recipeRepository; + private readonly Mock _httpContextAccessor; public QuickSearchRecipesTests() { _recipeRepository = new(); + _httpContextAccessor = new(); } [Fact] @@ -17,7 +20,7 @@ public async Task ItReturnsAListOfRecipes() var recipes = MockRecipe.GenerateDomainModelList(10); _recipeRepository - .Setup(r => r.QuickSearchRecipes(It.IsAny(), It.IsAny())) + .Setup(r => r.QuickSearchRecipes(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(recipes); var query = new QuickSearchRecipes.Query() @@ -26,7 +29,7 @@ public async Task ItReturnsAListOfRecipes() MaxRecipes = 10 }; - var handler = new QuickSearchRecipes.Handler(_recipeRepository.Object); + var handler = new QuickSearchRecipes.Handler(_recipeRepository.Object, _httpContextAccessor.Object); var result = await handler.Handle(query, new CancellationToken()); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6108f72..ff32641 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -22,9 +22,10 @@ import PrintRecipe from '@components/Pages/PrintRecipe'; import ManageMeats from '@components/Pages/ManageMeats'; import ManageRecipes from '@components/Pages/ManageRecipes'; import RecipeListing from '@components/Pages/RecipeListing'; +import Search from '@components/Pages/Search'; +import PrivateFilter from '@components/PrivateFilter/PrivateFilter'; import './styles/App.less'; -import Search from '@components/Pages/Search'; const App = (): JSX.Element => ( @@ -101,26 +102,26 @@ const App = (): JSX.Element => ( + )} /> + )} /> )}> - )} /> + )} /> )}> - )} /> + )} /> )}> - )} /> + )} /> )}> - )} /> + )} /> ( )} /> - )} /> + )} /> } /> diff --git a/frontend/src/components/Elements/NavBar/NavBar.tsx b/frontend/src/components/Elements/NavBar/NavBar.tsx index 8e733da..551bae5 100644 --- a/frontend/src/components/Elements/NavBar/NavBar.tsx +++ b/frontend/src/components/Elements/NavBar/NavBar.tsx @@ -31,10 +31,12 @@ const NavBar = ({ logout, } = useContext(AppContext); + const showForAnonymousUser = siteSettings.isPublic || (user && user.id !== ''); + const buildMenu = (): ItemType[] => { const menuItems: ItemType[] = []; - if (siteSettings.isPublic || (user && user.id !== '')) { + if (showForAnonymousUser) { menuItems.push({ key: 'search-form', className: 'quicksearch-form', @@ -57,10 +59,12 @@ const NavBar = ({ }); } - menuItems.push({ - key: 'all-recipes', - label: All Recipes, - }); + if (showForAnonymousUser) { + menuItems.push({ + key: 'all-recipes', + label: All Recipes, + }); + } if (user && user.id !== '') { menuItems.push({ @@ -69,33 +73,35 @@ const NavBar = ({ }); } - menuItems.push({ - key: 'categories', - label: 'Categories', - children: categories.length === 0 - ? [{ - key: 'no-categories', - label: <>No Categories, - }] - : categories.map((c) => ({ - key: `category-${c.categoryId}`, - label: {c.name}, - })), - }); - - menuItems.push({ - key: 'meats', - label: 'Meats', - children: meats.length === 0 - ? [{ - key: 'no-meats', - label: <>No Meats, - }] - : meats.map((m) => ({ - key: `meat-${m.meatId}`, - label: {m.name}, - })), - }); + if (showForAnonymousUser) { + menuItems.push({ + key: 'categories', + label: 'Categories', + children: categories.length === 0 + ? [{ + key: 'no-categories', + label: <>No Categories, + }] + : categories.map((c) => ({ + key: `category-${c.categoryId}`, + label: {c.name}, + })), + }); + + menuItems.push({ + key: 'meats', + label: 'Meats', + children: meats.length === 0 + ? [{ + key: 'no-meats', + label: <>No Meats, + }] + : meats.map((m) => ({ + key: `meat-${m.meatId}`, + label: {m.name}, + })), + }); + } if (user && user.id !== '') { const userMenuItems: ItemType[] = []; diff --git a/frontend/src/components/Pages/Login/Login.tsx b/frontend/src/components/Pages/Login/Login.tsx index 638e7a2..99d64f6 100644 --- a/frontend/src/components/Pages/Login/Login.tsx +++ b/frontend/src/components/Pages/Login/Login.tsx @@ -1,5 +1,6 @@ import { Row, Col, Typography } from 'antd'; import { useNavigate } from 'react-router-dom'; +import useQueryParameters from '@hooks/useQueryParameters'; import LoginForm from '@components/Forms/LoginForm'; import { useContext, useEffect } from 'react'; import AppContext from '@contexts/AppContext'; @@ -18,14 +19,18 @@ const Login = ({ const { user, loginUser, siteSettings } = useContext(AppContext); + const query = useQueryParameters(); + const urlRedirect = query.get('redirect'); + const completeLoginProcess = (authResult: AuthResult) => { + console.log({ redirectTo, urlRedirect }); loginUser(authResult.accessToken); - navigate(redirectTo); + navigate(urlRedirect || redirectTo); }; useEffect(() => { if (user !== null) { - navigate('/'); + navigate(urlRedirect || redirectTo); } }, [user]); diff --git a/frontend/src/components/Pages/ViewRecipe/RecipeActions.tsx b/frontend/src/components/Pages/ViewRecipe/RecipeActions.tsx index b393d39..506b2c1 100644 --- a/frontend/src/components/Pages/ViewRecipe/RecipeActions.tsx +++ b/frontend/src/components/Pages/ViewRecipe/RecipeActions.tsx @@ -5,46 +5,54 @@ import { EditOutlined, PrinterOutlined, } from '@ant-design/icons'; +import { useContext } from 'react'; +import AppContext from '@contexts/AppContext'; type RecipeActionsProps = { isFavorite: boolean; - onToggle: () => void; + onToggleFavorite: () => void; onEdit?: (() => void) | null; onPrint?: (() => void) | null; } const RecipeActions = ({ isFavorite, - onToggle, + onToggleFavorite, onEdit = null, onPrint = null, -}: RecipeActionsProps): JSX.Element => ( - - - {onEdit ? ( - - ) : null} +}: RecipeActionsProps): JSX.Element => { + const { user } = useContext(AppContext); - {onPrint ? ( - - ) : null} - -); + return ( + + {user ? ( + + ) : null} + {onEdit ? ( + + ) : null} + + {onPrint ? ( + + ) : null} + + ); +}; export default RecipeActions; diff --git a/frontend/src/components/Pages/ViewRecipe/ViewRecipe.tsx b/frontend/src/components/Pages/ViewRecipe/ViewRecipe.tsx index 455f775..522e3e8 100644 --- a/frontend/src/components/Pages/ViewRecipe/ViewRecipe.tsx +++ b/frontend/src/components/Pages/ViewRecipe/ViewRecipe.tsx @@ -15,6 +15,7 @@ import { useNavigate, useParams } from 'react-router'; import { useEffect, useState, useContext } from 'react'; import AppContext from '@contexts/AppContext'; import HtmlViewer from '@components/HtmlViewer'; +import PrivateRecipe from '@components/PrivateRecipe'; import BasicInfo from './BasicInfo'; import NutritionInfo from './NutritionInfo'; import RecipeActions from './RecipeActions'; @@ -115,6 +116,10 @@ const ViewRecipe = (): JSX.Element => { return ; } + if (recipe && (!user || user.userId === '') && !recipe.isPublic) { + return ; + } + return (
@@ -160,13 +165,13 @@ const ViewRecipe = (): JSX.Element => { { navigate(`/recipes/edit/${recipe.recipeId}`); } : null} - onPrint={canEditRecipe() ? () => { + onPrint={() => { navigate(`/recipes/print/${recipe.recipeId}`); - } : null} + }} /> diff --git a/frontend/src/components/PrivateFilter/PrivateFilter.tsx b/frontend/src/components/PrivateFilter/PrivateFilter.tsx new file mode 100644 index 0000000..f010cad --- /dev/null +++ b/frontend/src/components/PrivateFilter/PrivateFilter.tsx @@ -0,0 +1,23 @@ +import { ReactElement, useContext } from 'react'; +import AppContext from '@contexts/AppContext'; +import Login from '@components/Pages/Login'; + +type PrivateFilterProps = { + children: ReactElement; + redirectTo?: string; +} + +const PrivateFilter = ({ + redirectTo = '/', + children, +}: PrivateFilterProps): JSX.Element => { + const { user, siteSettings } = useContext(AppContext); + + if (!siteSettings.isPublic && (!user || user.id === '')) { + return ; + } + + return children; +}; + +export default PrivateFilter; diff --git a/frontend/src/components/PrivateFilter/index.tsx b/frontend/src/components/PrivateFilter/index.tsx new file mode 100644 index 0000000..c282732 --- /dev/null +++ b/frontend/src/components/PrivateFilter/index.tsx @@ -0,0 +1,2 @@ +export * from './PrivateFilter'; +export { default } from './PrivateFilter'; diff --git a/frontend/src/components/PrivateRecipe/PrivateRecipe.less b/frontend/src/components/PrivateRecipe/PrivateRecipe.less new file mode 100644 index 0000000..3a1429c --- /dev/null +++ b/frontend/src/components/PrivateRecipe/PrivateRecipe.less @@ -0,0 +1,13 @@ +@import (reference) '../../styles/Theme.less'; + +.private-recipe { + width: 100%; + margin: 4rem 0; + text-align: center; + + a.ant-btn-primary { + background-color: @color-green-01; + border-color: @color-green-01; + color: @color-white; + } +} \ No newline at end of file diff --git a/frontend/src/components/PrivateRecipe/PrivateRecipe.tsx b/frontend/src/components/PrivateRecipe/PrivateRecipe.tsx new file mode 100644 index 0000000..6b72a64 --- /dev/null +++ b/frontend/src/components/PrivateRecipe/PrivateRecipe.tsx @@ -0,0 +1,19 @@ +import { Button, Space, Typography } from 'antd'; + +import './PrivateRecipe.less'; + +const { Title, Paragraph } = Typography; + +interface PrivateRecipeProps { + id: number; +} + +const PrivateRecipe = ({ id }: PrivateRecipeProps): JSX.Element => ( + + Private + Sorry, but the recipe you're looking for is marked as private. Please sign in to view. + + +); + +export default PrivateRecipe; diff --git a/frontend/src/components/PrivateRecipe/index.tsx b/frontend/src/components/PrivateRecipe/index.tsx new file mode 100644 index 0000000..e39925f --- /dev/null +++ b/frontend/src/components/PrivateRecipe/index.tsx @@ -0,0 +1,2 @@ +export * from './PrivateRecipe'; +export { default } from './PrivateRecipe'; diff --git a/frontend/src/components/ProtectedRoute/ProtectedRoute.tsx b/frontend/src/components/ProtectedRoute/ProtectedRoute.tsx index 4cd7212..8de85e1 100644 --- a/frontend/src/components/ProtectedRoute/ProtectedRoute.tsx +++ b/frontend/src/components/ProtectedRoute/ProtectedRoute.tsx @@ -4,9 +4,9 @@ import Login from '@components/Pages/Login'; import NoAccess from '@components/Pages/NoAccess'; type ProtectedRouteProps = { - children: ReactElement - redirectTo?: string - requiredRoles?: string[] + children: ReactElement; + redirectTo?: string; + requiredRoles?: string[]; } const ProtectedRoute = ({