Refined search code and wired up the client side search with server
This commit is contained in:
parent
ecccebe7c5
commit
1688e7fffa
3 changed files with 94 additions and 55 deletions
|
|
@ -64,9 +64,28 @@ public class ServerUtils {
|
||||||
return list; // JSON string-> List<Recipe> (Jackson)
|
return list; // JSON string-> List<Recipe> (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<Recipe> getRecipesFiltered(String filter, List<String> locales) throws IOException, InterruptedException {
|
public List<Recipe> getRecipesFiltered(String filter, List<String> locales) throws IOException, InterruptedException {
|
||||||
// TODO: implement filtering on server side
|
//TODO add limit integration
|
||||||
return this.getRecipes(locales);
|
String uri = SERVER + "/recipes?search=" + filter + "&locales=" + String.join(",", locales);
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(uri))
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
HttpResponse<String> 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<Recipe> list = objectMapper.readValue(response.body(), new TypeReference<List<Recipe>>() {});
|
||||||
|
logger.info("Received filtered recipes from server: " + list);
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,11 @@ import org.springframework.web.bind.annotation.RestController;
|
||||||
import server.database.RecipeRepository;
|
import server.database.RecipeRepository;
|
||||||
import server.service.RecipeService;
|
import server.service.RecipeService;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api")
|
@RequestMapping("/api")
|
||||||
|
|
@ -60,26 +62,6 @@ public class RecipeController {
|
||||||
.orElseGet(() -> ResponseEntity.notFound().build());
|
.orElseGet(() -> ResponseEntity.notFound().build());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Mapping for <code>GET /recipes(?limit=)(&locales=)</code>
|
|
||||||
* <p>
|
|
||||||
* 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<List<Recipe>> getRecipes(
|
|
||||||
@RequestParam Optional<List<String>> locales,
|
|
||||||
@RequestParam Optional<Integer> limit
|
|
||||||
) {
|
|
||||||
logger.info("GET /recipes called.");
|
|
||||||
|
|
||||||
List<Recipe> recipes = locales
|
|
||||||
.map(loc -> findWithLocales(loc, limit))
|
|
||||||
.orElseGet(() -> findAll(limit));
|
|
||||||
|
|
||||||
return ResponseEntity.ok(recipes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Recipe> findWithLocales(List<String> locales, Optional<Integer> limit) {
|
private List<Recipe> findWithLocales(List<String> locales, Optional<Integer> limit) {
|
||||||
return limit
|
return limit
|
||||||
|
|
@ -154,36 +136,68 @@ public class RecipeController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a search based on a case-insensitive partial match on
|
* 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 search - name of the recipe to be searched for.
|
||||||
* @param limit - limit of the results queried 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.
|
* @return - returns a ResponseEntity with a List of Recipes and an HTTP 200 ok status.
|
||||||
*/
|
*/
|
||||||
|
@GetMapping("/recipes")
|
||||||
public ResponseEntity<List<Recipe>> getRecipes(
|
public ResponseEntity<List<Recipe>> getRecipes(
|
||||||
@RequestParam Optional<String> search,
|
@RequestParam Optional<String> search,
|
||||||
@RequestParam Optional<Integer> limit,
|
@RequestParam Optional<Integer> limit,
|
||||||
@RequestParam Optional<String> lang){
|
@RequestParam Optional<List<String>> locales){
|
||||||
|
|
||||||
List<Recipe> recipes = recipeRepository.findAll();
|
logger.info("GET /recipes called ith search: " + search.orElse("none"));
|
||||||
|
|
||||||
List<Recipe> finalRecipes = recipes;
|
//Start the search with all the recipes (locale filtered)
|
||||||
recipes = search
|
List<Recipe> recipeList = locales.map(loc->findWithLocales(loc, Optional.empty()))
|
||||||
.filter(s -> !s.trim().isEmpty()) // filters recipes if the string is not empty by doing a lowercase search
|
.orElseGet(()->recipeService.findAll());
|
||||||
.map(s -> {
|
|
||||||
String lowercaseSearch = s.toLowerCase();
|
//check if limit is zero (and also below 0 for anomalous cases) and end the search early
|
||||||
return finalRecipes.stream()
|
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 ->
|
.filter(recipe ->
|
||||||
recipe.getName().toLowerCase().contains(lowercaseSearch)
|
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<String> searchTerms){
|
||||||
|
|
||||||
|
List<String> lowerCased = searchTerms.stream()
|
||||||
|
.map(String::toLowerCase)
|
||||||
|
.map(String::trim)
|
||||||
.toList();
|
.toList();
|
||||||
})
|
|
||||||
.orElse(recipes);
|
for (String s : lowerCased) {
|
||||||
recipes = limit // filters based on limit if provided
|
boolean foundInRecipeName = recipe.getName().trim().toLowerCase().contains(s); //searches recipe names
|
||||||
.filter(l -> l < finalRecipes.size())
|
boolean foundInIngredients = recipe.getIngredients().stream().anyMatch(ing -> ing.getIngredient()
|
||||||
.map(l -> finalRecipes.stream().limit(l).toList())
|
.getName().toLowerCase().contains(s)); // searches in ingredients
|
||||||
.orElse(recipes);
|
boolean foundInPrepSteps = recipe.getPreparationSteps().stream()
|
||||||
return ResponseEntity.ok(recipes);
|
.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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -116,18 +116,20 @@ public class RecipeControllerTest {
|
||||||
assertEquals(recipes.size(),
|
assertEquals(recipes.size(),
|
||||||
controller.getRecipes(
|
controller.getRecipes(
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty()).getBody().size());
|
Optional.empty(),
|
||||||
|
Optional.of(List.of("en", "nl"))).getBody().size());;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Tag("test-from-init-data")
|
@Tag("test-from-init-data")
|
||||||
public void getSomeRecipes() {
|
public void getSomeRecipes() {
|
||||||
final int LIMIT = 5;
|
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,
|
assertEquals(LIMIT,
|
||||||
controller.getRecipes(
|
controller.getRecipes(
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.of(LIMIT)).getBody().size());
|
Optional.of(LIMIT),
|
||||||
|
Optional.of(List.of("en"))).getBody().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -135,8 +137,10 @@ public class RecipeControllerTest {
|
||||||
public void getManyRecipesWithLocale() {
|
public void getManyRecipesWithLocale() {
|
||||||
// The number of recipes returned is the same as the entire input list
|
// The number of recipes returned is the same as the entire input list
|
||||||
assertEquals(recipes.size(),
|
assertEquals(recipes.size(),
|
||||||
controller.getRecipes(Optional.of(List.of("en", "nl")),
|
controller.getRecipes(
|
||||||
Optional.empty()).getBody().size());
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.of(List.of("en", "nl"))).getBody().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -144,18 +148,20 @@ public class RecipeControllerTest {
|
||||||
public void getNoRecipesWithLocale() {
|
public void getNoRecipesWithLocale() {
|
||||||
// should have NO Dutch recipes (thank god)
|
// should have NO Dutch recipes (thank god)
|
||||||
assertEquals(0,
|
assertEquals(0,
|
||||||
controller.getRecipes(Optional.of(List.of("nl")),
|
controller.getRecipes(
|
||||||
Optional.empty()).getBody().size());
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.of(List.of("nl"))).getBody().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Tag("test-from-init-data")
|
@Tag("test-from-init-data")
|
||||||
public void getSomeRecipesWithLocale() {
|
public void getSomeRecipesWithLocale() {
|
||||||
final int LIMIT = 5;
|
final int LIMIT = 5;
|
||||||
// The number of recipes returned is the same as the entire input list
|
|
||||||
assertEquals(LIMIT,
|
assertEquals(LIMIT,
|
||||||
controller.getRecipes(Optional.of(List.of("en", "nl")),
|
controller.getRecipes(
|
||||||
Optional.of(LIMIT)).getBody().size());
|
Optional.empty(),
|
||||||
|
Optional.of(LIMIT),
|
||||||
|
Optional.of(List.of("en", "nl"))).getBody().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue