From 1688e7fffa29b76369853392956e38549f2090d0 Mon Sep 17 00:00:00 2001 From: Rithvik Sriram Date: Fri, 16 Jan 2026 22:05:13 +0100 Subject: [PATCH] Refined search code and wired up the client side search with server --- .../main/java/client/utils/ServerUtils.java | 23 ++++- .../java/server/api/RecipeController.java | 96 +++++++++++-------- .../java/server/api/RecipeControllerTest.java | 30 +++--- 3 files changed, 94 insertions(+), 55 deletions(-) diff --git a/client/src/main/java/client/utils/ServerUtils.java b/client/src/main/java/client/utils/ServerUtils.java index 606afd5..3c408f1 100644 --- a/client/src/main/java/client/utils/ServerUtils.java +++ b/client/src/main/java/client/utils/ServerUtils.java @@ -64,9 +64,28 @@ public class ServerUtils { return list; // JSON string-> List (Jackson) } + /** + * The method used by the search bar to get filtered recipes. + * @param filter - filter string + * @param locales - locales of the user + * @return filtered recipe list + */ public List getRecipesFiltered(String filter, List locales) throws IOException, InterruptedException { - // TODO: implement filtering on server side - return this.getRecipes(locales); + //TODO add limit integration + String uri = SERVER + "/recipes?search=" + filter + "&locales=" + String.join(",", locales); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(uri)) + .GET() + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + if(response.statusCode() != statusOK){ + throw new IOException("Failed to get filtered recipes. Server responds with " + response.body()); + } + + List list = objectMapper.readValue(response.body(), new TypeReference>() {}); + logger.info("Received filtered recipes from server: " + list); + return list; } /** diff --git a/server/src/main/java/server/api/RecipeController.java b/server/src/main/java/server/api/RecipeController.java index fe64067..908fd20 100644 --- a/server/src/main/java/server/api/RecipeController.java +++ b/server/src/main/java/server/api/RecipeController.java @@ -23,9 +23,11 @@ import org.springframework.web.bind.annotation.RestController; import server.database.RecipeRepository; import server.service.RecipeService; +import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.logging.Logger; +import java.util.stream.Collectors; @RestController @RequestMapping("/api") @@ -60,26 +62,6 @@ public class RecipeController { .orElseGet(() -> ResponseEntity.notFound().build()); } - /** - * Mapping for GET /recipes(?limit=)(&locales=) - *

- * If the limit parameter is unspecified, return all recipes in the repository. - * @param limit Integer limit of items you want to get - * @return The list of recipes - */ - @GetMapping("/recipes") - public ResponseEntity> getRecipes( - @RequestParam Optional> locales, - @RequestParam Optional limit - ) { - logger.info("GET /recipes called."); - - List recipes = locales - .map(loc -> findWithLocales(loc, limit)) - .orElseGet(() -> findAll(limit)); - - return ResponseEntity.ok(recipes); - } private List findWithLocales(List locales, Optional limit) { return limit @@ -154,36 +136,68 @@ public class RecipeController { /** * Performs a search based on a case-insensitive partial match on - * Recipe name and limits the result to a set amount of results. + * Recipe name, ingredients and preparations steps + * and limits the result to a set amount of results. * @param search - name of the recipe to be searched for. * @param limit - limit of the results queried for. - * @param lang - stores the info of the language of the user to provide server support/ + * @param locales - stores the info of the language of the user to provide server support/ * @return - returns a ResponseEntity with a List of Recipes and an HTTP 200 ok status. */ + @GetMapping("/recipes") public ResponseEntity> getRecipes( @RequestParam Optional search, @RequestParam Optional limit, - @RequestParam Optional lang){ + @RequestParam Optional> locales){ - List recipes = recipeRepository.findAll(); + logger.info("GET /recipes called ith search: " + search.orElse("none")); - List finalRecipes = recipes; - recipes = search - .filter(s -> !s.trim().isEmpty()) // filters recipes if the string is not empty by doing a lowercase search - .map(s -> { - String lowercaseSearch = s.toLowerCase(); - return finalRecipes.stream() - .filter(recipe -> - recipe.getName().toLowerCase().contains(lowercaseSearch) - ) - .toList(); - }) - .orElse(recipes); - recipes = limit // filters based on limit if provided - .filter(l -> l < finalRecipes.size()) - .map(l -> finalRecipes.stream().limit(l).toList()) - .orElse(recipes); - return ResponseEntity.ok(recipes); + //Start the search with all the recipes (locale filtered) + List recipeList = locales.map(loc->findWithLocales(loc, Optional.empty())) + .orElseGet(()->recipeService.findAll()); + + //check if limit is zero (and also below 0 for anomalous cases) and end the search early + if(limit.isPresent() && limit.get() <= 0){ + recipeList.clear(); + return ResponseEntity.ok(recipeList); + } + + //Apply Search filter + + if(search.isPresent() && !search.get().trim().isEmpty()) { + recipeList = recipeList.stream() + .filter(recipe -> + matchesSearchTerms(recipe, Arrays.stream(search.get().split(" ")).toList())) + .collect(Collectors.toList()); + } + //Apply limit + if(limit.isPresent() && limit.get()< recipeList.size()){ + recipeList = recipeList.stream() + .limit(limit.get()) + .collect(Collectors.toList()); + } + + return ResponseEntity.ok(recipeList); + + } + + public boolean matchesSearchTerms(Recipe recipe, List searchTerms){ + + List lowerCased = searchTerms.stream() + .map(String::toLowerCase) + .map(String::trim) + .toList(); + + for (String s : lowerCased) { + boolean foundInRecipeName = recipe.getName().trim().toLowerCase().contains(s); //searches recipe names + boolean foundInIngredients = recipe.getIngredients().stream().anyMatch(ing -> ing.getIngredient() + .getName().toLowerCase().contains(s)); // searches in ingredients + boolean foundInPrepSteps = recipe.getPreparationSteps().stream() + .anyMatch(step -> step.toLowerCase().contains(s)); //searches preparation steps + if(!foundInRecipeName && !foundInIngredients && !foundInPrepSteps){ + return false; // returns false if not met all criteria. this meets the backlog criteria + } + } + return true; // all terms found } diff --git a/server/src/test/java/server/api/RecipeControllerTest.java b/server/src/test/java/server/api/RecipeControllerTest.java index 563d9ec..f4d8c77 100644 --- a/server/src/test/java/server/api/RecipeControllerTest.java +++ b/server/src/test/java/server/api/RecipeControllerTest.java @@ -115,19 +115,21 @@ public class RecipeControllerTest { // The number of recipes returned is the same as the entire input list assertEquals(recipes.size(), controller.getRecipes( + Optional.empty(), Optional.empty(), - Optional.empty()).getBody().size()); + Optional.of(List.of("en", "nl"))).getBody().size());; } @Test @Tag("test-from-init-data") public void getSomeRecipes() { final int LIMIT = 5; - // The number of recipes returned is the same as the entire input list + // The number of recipes returned is the same as the limit assertEquals(LIMIT, controller.getRecipes( - Optional.empty(), - Optional.of(LIMIT)).getBody().size()); + Optional.empty(), + Optional.of(LIMIT), + Optional.of(List.of("en"))).getBody().size()); } @Test @@ -135,8 +137,10 @@ public class RecipeControllerTest { public void getManyRecipesWithLocale() { // The number of recipes returned is the same as the entire input list assertEquals(recipes.size(), - controller.getRecipes(Optional.of(List.of("en", "nl")), - Optional.empty()).getBody().size()); + controller.getRecipes( + Optional.empty(), + Optional.empty(), + Optional.of(List.of("en", "nl"))).getBody().size()); } @Test @@ -144,18 +148,20 @@ public class RecipeControllerTest { public void getNoRecipesWithLocale() { // should have NO Dutch recipes (thank god) assertEquals(0, - controller.getRecipes(Optional.of(List.of("nl")), - Optional.empty()).getBody().size()); + controller.getRecipes( + Optional.empty(), + Optional.empty(), + Optional.of(List.of("nl"))).getBody().size()); } - @Test @Tag("test-from-init-data") public void getSomeRecipesWithLocale() { final int LIMIT = 5; - // The number of recipes returned is the same as the entire input list assertEquals(LIMIT, - controller.getRecipes(Optional.of(List.of("en", "nl")), - Optional.of(LIMIT)).getBody().size()); + controller.getRecipes( + Optional.empty(), + Optional.of(LIMIT), + Optional.of(List.of("en", "nl"))).getBody().size()); } @Test