From c5014c4a11c2020e4b127472208be26ba65ac98c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bu=C3=9Fmann?= Date: Wed, 13 Jul 2022 04:25:40 +0200 Subject: [PATCH] Finished restructuring and changed way of querying the API --- .../main/spoonaccular/APIAuthentication.java | 20 ++++++ .../main/spoonaccular/RecipeInformation.java | 47 ++++++++++---- .../src/main/spoonaccular/RecipeSearch.java | 61 ++++++++++++------- .../ExtendedRecipeByIngredient.java | 6 ++ .../controller/SpoonacularController.java | 15 ++--- build.gradle | 3 + 6 files changed, 106 insertions(+), 46 deletions(-) create mode 100644 backend/src/main/spoonaccular/APIAuthentication.java diff --git a/backend/src/main/spoonaccular/APIAuthentication.java b/backend/src/main/spoonaccular/APIAuthentication.java new file mode 100644 index 0000000..fd48826 --- /dev/null +++ b/backend/src/main/spoonaccular/APIAuthentication.java @@ -0,0 +1,20 @@ +package spoonaccular; + +import io.github.cdimascio.dotenv.Dotenv; +import okhttp3.Request; + +public class APIAuthentication { + + private static Dotenv dotenv = Dotenv.load(); + + private APIAuthentication(){ + } + + public static Request.Builder addAuthHeaders(Request.Builder builder){ + return builder + .get() + .addHeader("X-RapidAPI-Key", dotenv.get("X-RapidAPI-Key")) + .addHeader("X-RapidAPI-Host", dotenv.get("X-RapidAPI-Host")); + } + +} diff --git a/backend/src/main/spoonaccular/RecipeInformation.java b/backend/src/main/spoonaccular/RecipeInformation.java index 812e874..613dde5 100644 --- a/backend/src/main/spoonaccular/RecipeInformation.java +++ b/backend/src/main/spoonaccular/RecipeInformation.java @@ -1,40 +1,61 @@ package spoonaccular; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import io.github.cdimascio.dotenv.Dotenv; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; import org.springframework.stereotype.Component; import spoonaccular.models.recipe_by_ingredient.ExtendedRecipeByIngredient; import spoonaccular.models.recipe_by_ingredient.RecipeByIngredient; import spoonaccular.models.recipe_information.Recipe; import java.io.IOException; -import java.net.URL; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; @Component public class RecipeInformation { private final Dotenv dotenv; + private final OkHttpClient client; public RecipeInformation(){ dotenv = Dotenv.load(); + client = new OkHttpClient(); } - public Recipe getRecipeFromId(int id) throws IOException { - String urlString = dotenv.get("SPOONACCULAR_API_URL") + "/recipes/" + id + "/information?apiKey="+dotenv.get("SPOONACCULAR_API_KEY")+"&includeNutrition=false"; - URL url = new URL(urlString); - return new ObjectMapper().readValue(url, Recipe.class); + public List getRecipeFromIds(List ids) throws IOException { + String idsString = ids.stream().map(String::valueOf) + .collect(Collectors.joining(",")); + return new ObjectMapper().readValue(queryInformationBulk(idsString).body().string(), new TypeReference<>(){}); } - public ExtendedRecipeByIngredient getExtendedRecipeFromId(int id) throws IOException { - String urlString = dotenv.get("SPOONACCULAR_API_URL") + "/recipes/" + id + "/information?apiKey="+dotenv.get("SPOONACCULAR_API_KEY")+"&includeNutrition=false"; - URL url = new URL(urlString); - return new ObjectMapper().readValue(url, ExtendedRecipeByIngredient.class); + public List getExtendedRecipeFromIds(List ids) throws IOException { + String idsString = ids.stream().map(String::valueOf) + .collect(Collectors.joining(",")); + return new ObjectMapper().readValue(queryInformationBulk(idsString).body().string(), new TypeReference<>() {}); } - public ExtendedRecipeByIngredient getRecepieByIngredientsExtended(RecipeByIngredient recipeByIngredient) throws IOException { - ExtendedRecipeByIngredient extendedRecipeByIngredient = getExtendedRecipeFromId(recipeByIngredient.getId()); - extendedRecipeByIngredient.setMissedIngredients(recipeByIngredient.getMissedIngredients()); - return extendedRecipeByIngredient; + private Response queryInformationBulk(String idsString) throws IOException { + Request request = APIAuthentication.addAuthHeaders(new Request.Builder() + .url("https://" + dotenv.get("X-RapidAPI-Host") + + "/recipes/informationBulk?ids=" + idsString)) + .build(); + return client.newCall(request).execute(); + } + + public List getRecepieByIngredientsExtended(List recipeByIngredients) throws IOException { + List ids = recipeByIngredients.stream().map(RecipeByIngredient::getId).toList(); + List extendedRecipeByIngredients = getExtendedRecipeFromIds(ids); + Iterator recipeByIngredientIterator = recipeByIngredients.iterator(); + Iterator extendedRecipeByIngredientIterator = extendedRecipeByIngredients.iterator(); + while(recipeByIngredientIterator.hasNext() && extendedRecipeByIngredientIterator.hasNext()){ + extendedRecipeByIngredientIterator.next().addMissingInfo(recipeByIngredientIterator.next()); + } + return extendedRecipeByIngredients; } } diff --git a/backend/src/main/spoonaccular/RecipeSearch.java b/backend/src/main/spoonaccular/RecipeSearch.java index 970a5e8..c0ce223 100644 --- a/backend/src/main/spoonaccular/RecipeSearch.java +++ b/backend/src/main/spoonaccular/RecipeSearch.java @@ -1,20 +1,20 @@ package spoonaccular; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import io.github.cdimascio.dotenv.Dotenv; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; import org.json.JSONException; import org.json.JSONObject; import org.springframework.stereotype.Component; +import spoonaccular.models.recipe_by_ingredient.ExtendedRecipeByIngredient; import spoonaccular.models.recipe_by_ingredient.RecipeByIngredient; import spoonaccular.models.recipe_information.Recipe; import whattocook.models.Item; import java.io.IOException; -import java.net.URI; -import java.net.URL; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; import java.util.LinkedList; import java.util.List; import java.util.Random; @@ -25,33 +25,50 @@ public class RecipeSearch { private final Random rnd; private final Dotenv dotenv; + private final RecipeInformation recipeInformation; + + private final OkHttpClient client; public RecipeSearch(){ rnd = new Random(); dotenv = Dotenv.load(); + recipeInformation = new RecipeInformation(); + client = new OkHttpClient(); } - public RecipeByIngredient[] getForIngridients(Iterable items, int number) throws java.io.IOException { + public List getForIngridients(Iterable items, int number) throws java.io.IOException { List itemNames = new LinkedList<>(); items.forEach(item -> itemNames.add(item.getName())); String ingridients = String.join(",", itemNames); - String urlString = dotenv.get("SPOONACCULAR_API_URL") + "/recipes/findByIngredients?apiKey=" + dotenv.get("SPOONACCULAR_API_KEY") + "&ingredients=" + ingridients + "&ranking=" + dotenv.get("SPOONACCULAR_API_SEARCH_RANKING") + "&ignorePantry=" + IGNOREPANTRY + "&number=" + number; - URL url = new URL(urlString); - return new ObjectMapper().readValue(url, RecipeByIngredient[].class); - } - public RecipeByIngredient getOneForIngridients(Iterable items, int number) throws IOException { - return getForIngridients(items, number)[rnd.nextInt(number)]; - } - - public Recipe[] getRandom(List tags, int number) throws java.io.IOException, InterruptedException, JSONException { - String tagString = String.join(",", tags); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(dotenv.get("SPOONACCULAR_API_URL") + "/recipes/random?apiKey=" + dotenv.get("SPOONACCULAR_API_KEY") + "&number=" + number + "&tags=" + tagString)) - .method("GET", HttpRequest.BodyPublishers.noBody()) + Request request = + APIAuthentication.addAuthHeaders(new Request.Builder() + .url("https://" + dotenv.get("X-RapidAPI-Host") + + "/recipes/findByIngredients?ingredients=" + + ingridients + "&number=" + number + "&ignorePantry=" + + IGNOREPANTRY + "&ranking=" + dotenv.get("X-RapidAPI-SearchRanking"))) .build(); - HttpResponse response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); - JSONObject jsonObject = new JSONObject(response.body()); - return new ObjectMapper().readValue(jsonObject.getJSONArray("recipes").toString(), Recipe[].class); + + Response response = client.newCall(request).execute(); + String responseString = response.body().string(); + + List recipeByIngredients = new ObjectMapper().readValue(responseString, new TypeReference<>(){}); + return recipeInformation.getRecepieByIngredientsExtended(recipeByIngredients); + } + + public ExtendedRecipeByIngredient getOneForIngridients(Iterable items, int number) throws IOException { + return this.getForIngridients(items, number).get(rnd.nextInt(number)); + } + + public List getRandom(List tags, int number) throws java.io.IOException, JSONException { + String tagString = String.join(",", tags); + Request request = APIAuthentication.addAuthHeaders(new Request.Builder() + .url("https://" + dotenv.get("X-RapidAPI-Host") + + "/recipes/random?number=" + number + "&tags=" + tagString)) + .build(); + + Response response = client.newCall(request).execute(); + JSONObject jsonObject = new JSONObject(response.body().string()); + return new ObjectMapper().readValue(jsonObject.getJSONArray("recipes").toString(), new TypeReference<>(){}); } } diff --git a/backend/src/main/spoonaccular/models/recipe_by_ingredient/ExtendedRecipeByIngredient.java b/backend/src/main/spoonaccular/models/recipe_by_ingredient/ExtendedRecipeByIngredient.java index d423728..5377a5d 100644 --- a/backend/src/main/spoonaccular/models/recipe_by_ingredient/ExtendedRecipeByIngredient.java +++ b/backend/src/main/spoonaccular/models/recipe_by_ingredient/ExtendedRecipeByIngredient.java @@ -56,4 +56,10 @@ public class ExtendedRecipeByIngredient extends Recipe { this.usedIngredients = usedIngredients; } + public void addMissingInfo(RecipeByIngredient recipeByIngredient){ + this.setMissedIngredientCount(recipeByIngredient.getMissedIngredientCount()); + this.setUsedIngredientCount(recipeByIngredient.getUsedIngredientCount()); + this.setMissedIngredients(recipeByIngredient.getMissedIngredients()); + this.setUsedIngredients(recipeByIngredient.getUsedIngredients()); + } } diff --git a/backend/src/main/whattocook/controller/SpoonacularController.java b/backend/src/main/whattocook/controller/SpoonacularController.java index b517559..ea63e9d 100644 --- a/backend/src/main/whattocook/controller/SpoonacularController.java +++ b/backend/src/main/whattocook/controller/SpoonacularController.java @@ -3,18 +3,17 @@ package whattocook.controller; import org.json.JSONException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import spoonaccular.RecipeInformation; import spoonaccular.RecipeSearch; import spoonaccular.models.recipe_by_ingredient.ExtendedRecipeByIngredient; -import spoonaccular.models.recipe_by_ingredient.RecipeByIngredient; import spoonaccular.models.recipe_information.Recipe; import whattocook.repositories.ItemRepository; import java.io.IOException; import java.util.LinkedList; +import java.util.List; @RestController() @RequestMapping(path = "recipe") @@ -29,24 +28,18 @@ public class SpoonacularController { private RecipeSearch recipeSearch; @GetMapping("/forFridge") - public RecipeByIngredient[] getForFridge() throws IOException { + public List getForFridge() throws IOException { return recipeSearch.getForIngridients(itemRepository.findAll(), nextRecepies); } @GetMapping("/random") - public Recipe[] getRandom() throws IOException, InterruptedException, JSONException { + public List getRandom() throws IOException, InterruptedException, JSONException { return recipeSearch.getRandom(new LinkedList<>(), nextRecepies); //when user has food preferences apply instead of linked list. } @GetMapping("/oneFridge") public ExtendedRecipeByIngredient getOneFridge() throws IOException { - RecipeByIngredient recipe = recipeSearch.getOneForIngridients(itemRepository.findAll(), nextRecepiesForOneRandom); - return recipeInformation.getRecepieByIngredientsExtended(recipe); - } - - @GetMapping("/info/{id}") - public Recipe getInfo(@PathVariable int id) throws IOException { - return recipeInformation.getRecipeFromId(id); + return recipeSearch.getOneForIngridients(itemRepository.findAll(), nextRecepiesForOneRandom); } } diff --git a/build.gradle b/build.gradle index 244c645..6a8174c 100644 --- a/build.gradle +++ b/build.gradle @@ -29,6 +29,9 @@ dependencies { // https://mvnrepository.com/artifact/com.h2database/h2 implementation group: 'com.h2database', name: 'h2', version: '1.3.148' + // okhttp + implementation("com.squareup.okhttp3:okhttp:4.10.0") + // lombok compileOnly 'org.projectlombok:lombok:1.18.24' annotationProcessor 'org.projectlombok:lombok:1.18.24'