From f7455019c3764dd4ff1484cafce673e673e5d8f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bu=C3=9Fmann?= Date: Tue, 12 Jul 2022 17:24:57 +0200 Subject: [PATCH 1/9] [WIP] Reworked the way the spoonaccular api response gets consumed - Added pojos for jackson - Filtering unneeded information from spoonaccular response --- .env.example | 3 + .gitignore | 4 + .../main/spoonaccular/RecipeInformation.java | 40 +++++ .../src/main/spoonaccular/RecipeSearch.java | 57 +++++++ .../ExtendedRecipeByIngredient.java | 59 +++++++ .../MissedIngredient.java | 29 ++++ .../RecipeByIngredient.java | 108 +++++++++++++ .../recipe_by_ingredient/UsedIngredient.java | 29 ++++ .../ExtendedIngredient.java | 42 +++++ .../models/recipe_information/Measures.java | 31 ++++ .../models/recipe_information/Metric.java | 42 +++++ .../models/recipe_information/Recipe.java | 150 ++++++++++++++++++ backend/src/main/whattocook/Application.java | 2 +- .../Controller/SpoonacularController.java | 49 ------ .../controller/SpoonacularController.java | 52 ++++++ .../SpoonacularApiServiceImpl.java | 86 ---------- .../services/SpoonacularApiService.java | 13 -- build.gradle | 4 +- 18 files changed, 650 insertions(+), 150 deletions(-) create mode 100644 .env.example create mode 100644 backend/src/main/spoonaccular/RecipeInformation.java create mode 100644 backend/src/main/spoonaccular/RecipeSearch.java create mode 100644 backend/src/main/spoonaccular/models/recipe_by_ingredient/ExtendedRecipeByIngredient.java create mode 100644 backend/src/main/spoonaccular/models/recipe_by_ingredient/MissedIngredient.java create mode 100644 backend/src/main/spoonaccular/models/recipe_by_ingredient/RecipeByIngredient.java create mode 100644 backend/src/main/spoonaccular/models/recipe_by_ingredient/UsedIngredient.java create mode 100644 backend/src/main/spoonaccular/models/recipe_information/ExtendedIngredient.java create mode 100644 backend/src/main/spoonaccular/models/recipe_information/Measures.java create mode 100644 backend/src/main/spoonaccular/models/recipe_information/Metric.java create mode 100644 backend/src/main/spoonaccular/models/recipe_information/Recipe.java delete mode 100644 backend/src/main/whattocook/Controller/SpoonacularController.java create mode 100644 backend/src/main/whattocook/controller/SpoonacularController.java delete mode 100644 backend/src/main/whattocook/implementation/SpoonacularApiServiceImpl.java delete mode 100644 backend/src/main/whattocook/services/SpoonacularApiService.java diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0c0af2c --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +SPOONACCULAR_API_URL= +SPOONACCULAR_API_KEY= +SPOONACCULAR_API_SEARCH_RANKING= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0e580e5..ef5a5f4 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,7 @@ out !gradle/wrapper/gradle-wrapper.jar frontend/src/index.js + +.env + +.DS_Store diff --git a/backend/src/main/spoonaccular/RecipeInformation.java b/backend/src/main/spoonaccular/RecipeInformation.java new file mode 100644 index 0000000..812e874 --- /dev/null +++ b/backend/src/main/spoonaccular/RecipeInformation.java @@ -0,0 +1,40 @@ +package spoonaccular; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.cdimascio.dotenv.Dotenv; +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; + +@Component +public class RecipeInformation { + + private final Dotenv dotenv; + + public RecipeInformation(){ + dotenv = Dotenv.load(); + } + + 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 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 ExtendedRecipeByIngredient getRecepieByIngredientsExtended(RecipeByIngredient recipeByIngredient) throws IOException { + ExtendedRecipeByIngredient extendedRecipeByIngredient = getExtendedRecipeFromId(recipeByIngredient.getId()); + extendedRecipeByIngredient.setMissedIngredients(recipeByIngredient.getMissedIngredients()); + return extendedRecipeByIngredient; + } + +} diff --git a/backend/src/main/spoonaccular/RecipeSearch.java b/backend/src/main/spoonaccular/RecipeSearch.java new file mode 100644 index 0000000..970a5e8 --- /dev/null +++ b/backend/src/main/spoonaccular/RecipeSearch.java @@ -0,0 +1,57 @@ +package spoonaccular; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.cdimascio.dotenv.Dotenv; +import org.json.JSONException; +import org.json.JSONObject; +import org.springframework.stereotype.Component; +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; + +@Component +public class RecipeSearch { + private static final boolean IGNOREPANTRY = true; + + private final Random rnd; + private final Dotenv dotenv; + + public RecipeSearch(){ + rnd = new Random(); + dotenv = Dotenv.load(); + } + + public RecipeByIngredient[] 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()) + .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); + } +} diff --git a/backend/src/main/spoonaccular/models/recipe_by_ingredient/ExtendedRecipeByIngredient.java b/backend/src/main/spoonaccular/models/recipe_by_ingredient/ExtendedRecipeByIngredient.java new file mode 100644 index 0000000..d423728 --- /dev/null +++ b/backend/src/main/spoonaccular/models/recipe_by_ingredient/ExtendedRecipeByIngredient.java @@ -0,0 +1,59 @@ +package spoonaccular.models.recipe_by_ingredient; + +import com.fasterxml.jackson.annotation.JsonProperty; +import spoonaccular.models.recipe_information.Recipe; + +import java.util.List; + +public class ExtendedRecipeByIngredient extends Recipe { + + @JsonProperty("usedIngredientCount") + private Integer usedIngredientCount; + @JsonProperty("missedIngredientCount") + private Integer missedIngredientCount; + @JsonProperty("missedIngredients") + private List missedIngredients = null; + @JsonProperty("usedIngredients") + private List usedIngredients = null; + + @JsonProperty("usedIngredientCount") + public Integer getUsedIngredientCount() { + return usedIngredientCount; + } + + @JsonProperty("usedIngredientCount") + public void setUsedIngredientCount(Integer usedIngredientCount) { + this.usedIngredientCount = usedIngredientCount; + } + + @JsonProperty("missedIngredientCount") + public Integer getMissedIngredientCount() { + return missedIngredientCount; + } + + @JsonProperty("missedIngredientCount") + public void setMissedIngredientCount(Integer missedIngredientCount) { + this.missedIngredientCount = missedIngredientCount; + } + + @JsonProperty("missedIngredients") + public List getMissedIngredients() { + return missedIngredients; + } + + @JsonProperty("missedIngredients") + public void setMissedIngredients(List missedIngredients) { + this.missedIngredients = missedIngredients; + } + + @JsonProperty("usedIngredients") + public List getUsedIngredients() { + return usedIngredients; + } + + @JsonProperty("usedIngredients") + public void setUsedIngredients(List usedIngredients) { + this.usedIngredients = usedIngredients; + } + +} diff --git a/backend/src/main/spoonaccular/models/recipe_by_ingredient/MissedIngredient.java b/backend/src/main/spoonaccular/models/recipe_by_ingredient/MissedIngredient.java new file mode 100644 index 0000000..cbb953b --- /dev/null +++ b/backend/src/main/spoonaccular/models/recipe_by_ingredient/MissedIngredient.java @@ -0,0 +1,29 @@ + +package spoonaccular.models.recipe_by_ingredient; + +import com.fasterxml.jackson.annotation.*; + +import javax.annotation.Generated; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonPropertyOrder({ + "name" +}) +@Generated("jsonschema2pojo") +public class MissedIngredient { + + @JsonProperty("name") + private String name; + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("name") + public void setName(String name) { + this.name = name; + } + +} diff --git a/backend/src/main/spoonaccular/models/recipe_by_ingredient/RecipeByIngredient.java b/backend/src/main/spoonaccular/models/recipe_by_ingredient/RecipeByIngredient.java new file mode 100644 index 0000000..1249f5c --- /dev/null +++ b/backend/src/main/spoonaccular/models/recipe_by_ingredient/RecipeByIngredient.java @@ -0,0 +1,108 @@ + +package spoonaccular.models.recipe_by_ingredient; + +import com.fasterxml.jackson.annotation.*; + +import javax.annotation.Generated; +import java.util.List; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonPropertyOrder({ + "id", + "title", + "image", + "usedIngredientCount", + "missedIngredientCount", + "missedIngredients", + "usedIngredients", +}) +@Generated("jsonschema2pojo") +public class RecipeByIngredient { + + @JsonProperty("id") + private Integer id; + @JsonProperty("title") + private String title; + @JsonProperty("image") + private String image; + @JsonProperty("usedIngredientCount") + private Integer usedIngredientCount; + @JsonProperty("missedIngredientCount") + private Integer missedIngredientCount; + @JsonProperty("missedIngredients") + private List missedIngredients = null; + @JsonProperty("usedIngredients") + private List usedIngredients = null; + + @JsonProperty("id") + public Integer getId() { + return id; + } + + @JsonProperty("id") + public void setId(Integer id) { + this.id = id; + } + + @JsonProperty("title") + public String getTitle() { + return title; + } + + @JsonProperty("title") + public void setTitle(String title) { + this.title = title; + } + + @JsonProperty("image") + public String getImage() { + return image; + } + + @JsonProperty("image") + public void setImage(String image) { + this.image = image; + } + + @JsonProperty("usedIngredientCount") + public Integer getUsedIngredientCount() { + return usedIngredientCount; + } + + @JsonProperty("usedIngredientCount") + public void setUsedIngredientCount(Integer usedIngredientCount) { + this.usedIngredientCount = usedIngredientCount; + } + + @JsonProperty("missedIngredientCount") + public Integer getMissedIngredientCount() { + return missedIngredientCount; + } + + @JsonProperty("missedIngredientCount") + public void setMissedIngredientCount(Integer missedIngredientCount) { + this.missedIngredientCount = missedIngredientCount; + } + + @JsonProperty("missedIngredients") + public List getMissedIngredients() { + return missedIngredients; + } + + @JsonProperty("missedIngredients") + public void setMissedIngredients(List missedIngredients) { + this.missedIngredients = missedIngredients; + } + + @JsonProperty("usedIngredients") + public List getUsedIngredients() { + return usedIngredients; + } + + @JsonProperty("usedIngredients") + public void setUsedIngredients(List usedIngredients) { + this.usedIngredients = usedIngredients; + } + +} diff --git a/backend/src/main/spoonaccular/models/recipe_by_ingredient/UsedIngredient.java b/backend/src/main/spoonaccular/models/recipe_by_ingredient/UsedIngredient.java new file mode 100644 index 0000000..c3f20d5 --- /dev/null +++ b/backend/src/main/spoonaccular/models/recipe_by_ingredient/UsedIngredient.java @@ -0,0 +1,29 @@ + +package spoonaccular.models.recipe_by_ingredient; + +import com.fasterxml.jackson.annotation.*; + +import javax.annotation.Generated; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonPropertyOrder({ + "name" +}) +@Generated("jsonschema2pojo") +public class UsedIngredient { + + @JsonProperty("name") + private String name; + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("name") + public void setName(String name) { + this.name = name; + } + +} diff --git a/backend/src/main/spoonaccular/models/recipe_information/ExtendedIngredient.java b/backend/src/main/spoonaccular/models/recipe_information/ExtendedIngredient.java new file mode 100644 index 0000000..6b2aa58 --- /dev/null +++ b/backend/src/main/spoonaccular/models/recipe_information/ExtendedIngredient.java @@ -0,0 +1,42 @@ + +package spoonaccular.models.recipe_information; + +import javax.annotation.Generated; + +import com.fasterxml.jackson.annotation.*; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonPropertyOrder({ + "name", + "measures" +}) +@Generated("jsonschema2pojo") +public class ExtendedIngredient { + + @JsonProperty("name") + private String name; + @JsonProperty("measures") + private Measures measures; + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("name") + public void setName(String name) { + this.name = name; + } + + @JsonProperty("measures") + public Measures getMeasures() { + return measures; + } + + @JsonProperty("measures") + public void setMeasures(Measures measures) { + this.measures = measures; + } + +} diff --git a/backend/src/main/spoonaccular/models/recipe_information/Measures.java b/backend/src/main/spoonaccular/models/recipe_information/Measures.java new file mode 100644 index 0000000..960f417 --- /dev/null +++ b/backend/src/main/spoonaccular/models/recipe_information/Measures.java @@ -0,0 +1,31 @@ + +package spoonaccular.models.recipe_information; + +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Generated; + +import com.fasterxml.jackson.annotation.*; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonPropertyOrder({ + "metric" +}) +@Generated("jsonschema2pojo") +public class Measures { + + @JsonProperty("metric") + private Metric metric; + + @JsonProperty("metric") + public Metric getMetric() { + return metric; + } + + @JsonProperty("metric") + public void setMetric(Metric metric) { + this.metric = metric; + } + +} diff --git a/backend/src/main/spoonaccular/models/recipe_information/Metric.java b/backend/src/main/spoonaccular/models/recipe_information/Metric.java new file mode 100644 index 0000000..8443e22 --- /dev/null +++ b/backend/src/main/spoonaccular/models/recipe_information/Metric.java @@ -0,0 +1,42 @@ + +package spoonaccular.models.recipe_information; + +import javax.annotation.Generated; + +import com.fasterxml.jackson.annotation.*; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonPropertyOrder({ + "amount", + "unitShort" +}) +@Generated("jsonschema2pojo") +public class Metric { + + @JsonProperty("amount") + private Double amount; + @JsonProperty("unitShort") + private String unitShort; + + @JsonProperty("amount") + public Double getAmount() { + return amount; + } + + @JsonProperty("amount") + public void setAmount(Double amount) { + this.amount = amount; + } + + @JsonProperty("unitShort") + public String getUnitShort() { + return unitShort; + } + + @JsonProperty("unitShort") + public void setUnitShort(String unitShort) { + this.unitShort = unitShort; + } + +} diff --git a/backend/src/main/spoonaccular/models/recipe_information/Recipe.java b/backend/src/main/spoonaccular/models/recipe_information/Recipe.java new file mode 100644 index 0000000..130a869 --- /dev/null +++ b/backend/src/main/spoonaccular/models/recipe_information/Recipe.java @@ -0,0 +1,150 @@ + +package spoonaccular.models.recipe_information; + +import java.util.List; +import javax.annotation.Generated; + +import com.fasterxml.jackson.annotation.*; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonPropertyOrder({ + "vegetarian", + "vegan", + "glutenFree", + "dairyFree", + "extendedIngredients", + "id", + "title", + "readyInMinutes", + "servings", + "image", + "spoonacularSourceUrl" +}) +@Generated("jsonschema2pojo") +public class Recipe { + + @JsonProperty("vegetarian") + private Boolean vegetarian; + @JsonProperty("vegan") + private Boolean vegan; + @JsonProperty("glutenFree") + private Boolean glutenFree; + @JsonProperty("dairyFree") + private Boolean diaryFree; + @JsonProperty("extendedIngredients") + private List extendedIngredients = null; + @JsonProperty("id") + private Integer id; + @JsonProperty("title") + private String title; + @JsonProperty("readyInMinutes") + private Integer readyInMinutes; + @JsonProperty("servings") + private Integer servings; + @JsonProperty("image") + private String image; + @JsonProperty("spoonacularSourceUrl") + private String spoonacularSourceUrl; + + @JsonProperty("vegetarian") + public Boolean getVegetarian() { + return vegetarian; + } + + @JsonProperty("vegetarian") + public void setVegetarian(Boolean vegetarian) { + this.vegetarian = vegetarian; + } + + @JsonProperty("vegan") + public Boolean getVegan() { + return vegan; + } + + @JsonProperty("vegan") + public void setVegan(Boolean vegan) { + this.vegan = vegan; + } + + @JsonProperty("glutenFree") + public Boolean getGlutenFree() { + return glutenFree; + } + + @JsonProperty("glutenFree") + public void setGlutenFree(Boolean glutenFree) { + this.glutenFree = glutenFree; + } + + @JsonProperty("extendedIngredients") + public List getExtendedIngredients() { + return extendedIngredients; + } + + @JsonProperty("extendedIngredients") + public void setExtendedIngredients(List extendedIngredients) { + this.extendedIngredients = extendedIngredients; + } + + @JsonProperty("id") + public Integer getId() { + return id; + } + + @JsonProperty("id") + public void setId(Integer id) { + this.id = id; + } + + @JsonProperty("title") + public String getTitle() { + return title; + } + + @JsonProperty("title") + public void setTitle(String title) { + this.title = title; + } + + @JsonProperty("readyInMinutes") + public Integer getReadyInMinutes() { + return readyInMinutes; + } + + @JsonProperty("readyInMinutes") + public void setReadyInMinutes(Integer readyInMinutes) { + this.readyInMinutes = readyInMinutes; + } + + @JsonProperty("servings") + public Integer getServings() { + return servings; + } + + @JsonProperty("servings") + public void setServings(Integer servings) { + this.servings = servings; + } + + @JsonProperty("image") + public String getImage() { + return image; + } + + @JsonProperty("image") + public void setImage(String image) { + this.image = image; + } + + @JsonProperty("spoonacularSourceUrl") + public String getSpoonacularSourceUrl() { + return spoonacularSourceUrl; + } + + @JsonProperty("spoonacularSourceUrl") + public void setSpoonacularSourceUrl(String spoonacularSourceUrl) { + this.spoonacularSourceUrl = spoonacularSourceUrl; + } + +} diff --git a/backend/src/main/whattocook/Application.java b/backend/src/main/whattocook/Application.java index e6c2f04..51ce46f 100644 --- a/backend/src/main/whattocook/Application.java +++ b/backend/src/main/whattocook/Application.java @@ -11,7 +11,7 @@ import org.springframework.web.filter.CorsFilter; import java.util.Collections; -@SpringBootApplication +@SpringBootApplication(scanBasePackages = {"spoonaccular", "whattocook"}) public class Application { public static void main(String[] args) { diff --git a/backend/src/main/whattocook/Controller/SpoonacularController.java b/backend/src/main/whattocook/Controller/SpoonacularController.java deleted file mode 100644 index bfbf3f7..0000000 --- a/backend/src/main/whattocook/Controller/SpoonacularController.java +++ /dev/null @@ -1,49 +0,0 @@ -package whattocook.Controller; - - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpEntity; - -import org.springframework.web.bind.annotation.GetMapping; - -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import whattocook.services.SpoonacularApiService; -import whattocook.repositories.ItemRepository; - -import java.io.IOException; -import java.util.LinkedList; - -@RestController -@RequestMapping("/recipes") -public class SpoonacularController { - private int nextRecepies=10; - private int nextRecepiesForOneRandom=20; - @Autowired - private ItemRepository itemRepository; - @Autowired - private SpoonacularApiService service; - - @GetMapping("/forFridge") - public HttpEntity getForFridge() throws IOException, InterruptedException, JSONException { - return new HttpEntity(service.getForIngridients(itemRepository.findAll(), nextRecepies)); - } - - @GetMapping("/random") - public HttpEntity getRandom() throws IOException, InterruptedException, JSONException { - return new HttpEntity(service.getRandom(new LinkedList<>(), nextRecepies)); - //when user has food preferences apply instead of linked list. - } - - @GetMapping("/oneFridge") - public HttpEntity getOneFridge() throws IOException, InterruptedException, JSONException { - return new HttpEntity(service.getOneForIngridients(itemRepository.findAll(), nextRecepiesForOneRandom)); - } - - public void setNextRecepies(int nextRecepies) { - this.nextRecepies = nextRecepies; - } -} diff --git a/backend/src/main/whattocook/controller/SpoonacularController.java b/backend/src/main/whattocook/controller/SpoonacularController.java new file mode 100644 index 0000000..b517559 --- /dev/null +++ b/backend/src/main/whattocook/controller/SpoonacularController.java @@ -0,0 +1,52 @@ +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; + +@RestController() +@RequestMapping(path = "recipe") +public class SpoonacularController { + private int nextRecepies=10; + private int nextRecepiesForOneRandom=20; + @Autowired + private ItemRepository itemRepository; + @Autowired + private RecipeInformation recipeInformation; + @Autowired + private RecipeSearch recipeSearch; + + @GetMapping("/forFridge") + public RecipeByIngredient[] getForFridge() throws IOException { + return recipeSearch.getForIngridients(itemRepository.findAll(), nextRecepies); + } + + @GetMapping("/random") + public Recipe[] 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); + } +} diff --git a/backend/src/main/whattocook/implementation/SpoonacularApiServiceImpl.java b/backend/src/main/whattocook/implementation/SpoonacularApiServiceImpl.java deleted file mode 100644 index 557f2cc..0000000 --- a/backend/src/main/whattocook/implementation/SpoonacularApiServiceImpl.java +++ /dev/null @@ -1,86 +0,0 @@ -package whattocook.implementation; - - -import org.springframework.stereotype.Service; -import whattocook.models.Item; -import whattocook.services.SpoonacularApiService; -import org.json.*; - -import java.io.IOException; -import java.net.http.HttpResponse; -import java.net.http.HttpRequest; -import java.net.http.HttpClient; -import java.net.URI; -import java.util.Iterator; -import java.util.Random; - -@Service -public class SpoonacularApiServiceImpl implements SpoonacularApiService { - private final String KEY = "85cc006d508b447a88e659cd748899db"; - private final String RANKING = "2"; - private final boolean IGNOREPANTRY = true; - Random rnd=new Random(); - - public JSONArray getForIngridients(Iterable items, int number) throws java.io.IOException, InterruptedException, JSONException { - Iterator itemIterator = items.iterator(); - if (!itemIterator.hasNext()) { - return getRandom(new java.util.LinkedList(), number); - } else { - String ingridients = itemIterator.next().getName(); - for (Iterator it = itemIterator; it.hasNext(); ) { - Item curryItem = it.next(); - - - ingridients += "," + curryItem.getName(); - } - java.net.http.HttpRequest request = java.net.http.HttpRequest.newBuilder() - .uri(java.net.URI.create("https://api.spoonacular.com/recipes/findByIngredients?apiKey=" + KEY + "&ingredients=" + ingridients + "&ranking=" + RANKING + "&ignorePantry=" + IGNOREPANTRY + "&number=" + number)) - .method("GET", java.net.http.HttpRequest.BodyPublishers.noBody()) - .build(); - java.net.http.HttpResponse response = java.net.http.HttpClient.newHttpClient().send(request, java.net.http.HttpResponse.BodyHandlers.ofString()); - - JSONArray array=new JSONArray(response.body()); - return array; - - - } - } - - @Override - public JSONObject getOneForIngridients(Iterable items, int number) throws IOException, InterruptedException, JSONException { - JSONArray array= getForIngridients(items, number); - - - return array.getJSONObject(rnd.nextInt(20)); - } - - public JSONArray getRandom(java.util.List tags, int number) throws java.io.IOException, InterruptedException, JSONException { - if (tags.isEmpty()) { - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("https://api.spoonacular.com/recipes/random?apiKey=" + KEY + "&number=" + number)) - .method("GET", HttpRequest.BodyPublishers.noBody()) - .build(); - HttpResponse response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); - - JSONArray array=new JSONArray(response.body()); - return array; - - } else { - String tagString = tags.get(0); - for (int i = 1; i < tags.size(); i++) { - tagString += "," + tags.get(i); - } - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("https://api.spoonacular.com/recipes/random?apiKey=" + KEY + "&number=" + number + "&tags=" + tagString)) - .method("GET", HttpRequest.BodyPublishers.noBody()) - .build(); - HttpResponse response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); - JSONArray array=new JSONArray(response.body()); - return array; - } - } - - - - -} diff --git a/backend/src/main/whattocook/services/SpoonacularApiService.java b/backend/src/main/whattocook/services/SpoonacularApiService.java deleted file mode 100644 index 8e18814..0000000 --- a/backend/src/main/whattocook/services/SpoonacularApiService.java +++ /dev/null @@ -1,13 +0,0 @@ -package whattocook.services; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import whattocook.models.Item; - -public interface SpoonacularApiService { - JSONArray getForIngridients(Iterable items, int number) throws java.io.IOException, InterruptedException, JSONException; - JSONObject getOneForIngridients(Iterable items, int number) throws java.io.IOException, InterruptedException, JSONException; - - JSONArray getRandom(java.util.List tags, int number) throws java.io.IOException, InterruptedException, JSONException; -} diff --git a/build.gradle b/build.gradle index 26ce9e8..244c645 100644 --- a/build.gradle +++ b/build.gradle @@ -29,10 +29,12 @@ dependencies { // https://mvnrepository.com/artifact/com.h2database/h2 implementation group: 'com.h2database', name: 'h2', version: '1.3.148' - //lombok + // lombok compileOnly 'org.projectlombok:lombok:1.18.24' annotationProcessor 'org.projectlombok:lombok:1.18.24' + implementation 'io.github.cdimascio:dotenv-java:2.2.4' + testCompileOnly 'org.projectlombok:lombok:1.18.24' testAnnotationProcessor 'org.projectlombok:lombok:1.18.24' From 28109f0d359223bcdbd3a7d66324074fc7442d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bu=C3=9Fmann?= Date: Wed, 13 Jul 2022 00:21:33 +0200 Subject: [PATCH 2/9] added frontend to gradle build --- build.gradle | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/build.gradle b/build.gradle index 26ce9e8..d629644 100644 --- a/build.gradle +++ b/build.gradle @@ -10,10 +10,35 @@ sourceCompatibility = '17' compileJava.options.encoding = 'UTF-8' compileTestJava.options.encoding = 'UTF-8' +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + repositories { mavenCentral() } +processResources{ + dependsOn 'copyFrontendToBuild' +} + +bootJar{ + archiveFileName = "app.jar" +} + +task copyFrontendToBuild(type: Copy) { + dependsOn 'npmBuild' + from "$projectDir/frontend/dist/" + into "$buildDir/resources/main/static" +} + +task npmBuild(type: Exec) { + workingDir './frontend/' + commandLine 'npm','run', 'build' +} + dependencies { implementation 'org.springframework.boot:spring-boot-starter-test' implementation 'org.springframework.boot:spring-boot-starter' From 806b6945ec60ed9be7ff31669d0a7f9ff09d1a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bu=C3=9Fmann?= Date: Wed, 13 Jul 2022 00:21:33 +0200 Subject: [PATCH 3/9] Revert "added frontend to gradle build" This reverts commit 28109f0d359223bcdbd3a7d66324074fc7442d59. --- build.gradle | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/build.gradle b/build.gradle index d629644..26ce9e8 100644 --- a/build.gradle +++ b/build.gradle @@ -10,35 +10,10 @@ sourceCompatibility = '17' compileJava.options.encoding = 'UTF-8' compileTestJava.options.encoding = 'UTF-8' -configurations { - compileOnly { - extendsFrom annotationProcessor - } -} - repositories { mavenCentral() } -processResources{ - dependsOn 'copyFrontendToBuild' -} - -bootJar{ - archiveFileName = "app.jar" -} - -task copyFrontendToBuild(type: Copy) { - dependsOn 'npmBuild' - from "$projectDir/frontend/dist/" - into "$buildDir/resources/main/static" -} - -task npmBuild(type: Exec) { - workingDir './frontend/' - commandLine 'npm','run', 'build' -} - dependencies { implementation 'org.springframework.boot:spring-boot-starter-test' implementation 'org.springframework.boot:spring-boot-starter' 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 4/9] 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' From 5c0de10446084fa5827375dc5cca2b3b4f9ad831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bu=C3=9Fmann?= Date: Wed, 13 Jul 2022 05:58:48 +0200 Subject: [PATCH 5/9] updated paths of SpoonacularController to match the rest --- .../whattocook/controller/SpoonacularController.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/main/whattocook/controller/SpoonacularController.java b/backend/src/main/whattocook/controller/SpoonacularController.java index ea63e9d..8fe7fb6 100644 --- a/backend/src/main/whattocook/controller/SpoonacularController.java +++ b/backend/src/main/whattocook/controller/SpoonacularController.java @@ -2,8 +2,8 @@ package whattocook.controller; import org.json.JSONException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.BasePathAwareController; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import spoonaccular.RecipeInformation; import spoonaccular.RecipeSearch; @@ -16,7 +16,7 @@ import java.util.LinkedList; import java.util.List; @RestController() -@RequestMapping(path = "recipe") +@BasePathAwareController() public class SpoonacularController { private int nextRecepies=10; private int nextRecepiesForOneRandom=20; @@ -27,18 +27,18 @@ public class SpoonacularController { @Autowired private RecipeSearch recipeSearch; - @GetMapping("/forFridge") + @GetMapping("/recipe/forFridge") public List getForFridge() throws IOException { return recipeSearch.getForIngridients(itemRepository.findAll(), nextRecepies); } - @GetMapping("/random") + @GetMapping("/recipe/random") 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") + @GetMapping("/recipe/oneFridge") public ExtendedRecipeByIngredient getOneFridge() throws IOException { return recipeSearch.getOneForIngridients(itemRepository.findAll(), nextRecepiesForOneRandom); } From 33fe0e0d792186ac46f42b46d9340d10be92bfad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bu=C3=9Fmann?= Date: Wed, 13 Jul 2022 15:42:20 +0200 Subject: [PATCH 6/9] Program no longer terminates if .env file is not present (need for testing) --- backend/src/main/spoonaccular/APIAuthentication.java | 2 +- backend/src/main/spoonaccular/RecipeInformation.java | 2 +- backend/src/main/spoonaccular/RecipeSearch.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/spoonaccular/APIAuthentication.java b/backend/src/main/spoonaccular/APIAuthentication.java index fd48826..29991a5 100644 --- a/backend/src/main/spoonaccular/APIAuthentication.java +++ b/backend/src/main/spoonaccular/APIAuthentication.java @@ -5,7 +5,7 @@ import okhttp3.Request; public class APIAuthentication { - private static Dotenv dotenv = Dotenv.load(); + private static final Dotenv dotenv = Dotenv.configure().ignoreIfMissing().ignoreIfMalformed().load(); private APIAuthentication(){ } diff --git a/backend/src/main/spoonaccular/RecipeInformation.java b/backend/src/main/spoonaccular/RecipeInformation.java index 613dde5..7fc7a4f 100644 --- a/backend/src/main/spoonaccular/RecipeInformation.java +++ b/backend/src/main/spoonaccular/RecipeInformation.java @@ -23,7 +23,7 @@ public class RecipeInformation { private final OkHttpClient client; public RecipeInformation(){ - dotenv = Dotenv.load(); + dotenv = Dotenv.configure().ignoreIfMissing().ignoreIfMalformed().load(); client = new OkHttpClient(); } diff --git a/backend/src/main/spoonaccular/RecipeSearch.java b/backend/src/main/spoonaccular/RecipeSearch.java index c0ce223..a501623 100644 --- a/backend/src/main/spoonaccular/RecipeSearch.java +++ b/backend/src/main/spoonaccular/RecipeSearch.java @@ -31,7 +31,7 @@ public class RecipeSearch { public RecipeSearch(){ rnd = new Random(); - dotenv = Dotenv.load(); + dotenv = Dotenv.configure().ignoreIfMissing().ignoreIfMalformed().load(); recipeInformation = new RecipeInformation(); client = new OkHttpClient(); } From ac223093fe3c35feb564fa7dcb170255bd5f8f3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bu=C3=9Fmann?= Date: Wed, 13 Jul 2022 16:22:36 +0200 Subject: [PATCH 7/9] Converted api classes to be only statically accessible | Renamed SpoonacularController to RecipeSearchController --- .../main/spoonaccular/RecipeInformation.java | 16 ++++++------- .../src/main/spoonaccular/RecipeSearch.java | 23 ++++++++----------- ...oller.java => RecipeSearchController.java} | 20 +++++++--------- 3 files changed, 24 insertions(+), 35 deletions(-) rename backend/src/main/whattocook/controller/{SpoonacularController.java => RecipeSearchController.java} (62%) diff --git a/backend/src/main/spoonaccular/RecipeInformation.java b/backend/src/main/spoonaccular/RecipeInformation.java index 7fc7a4f..186ad77 100644 --- a/backend/src/main/spoonaccular/RecipeInformation.java +++ b/backend/src/main/spoonaccular/RecipeInformation.java @@ -19,27 +19,25 @@ import java.util.stream.Collectors; @Component public class RecipeInformation { - private final Dotenv dotenv; - private final OkHttpClient client; + private final static Dotenv dotenv = Dotenv.configure().ignoreIfMissing().ignoreIfMalformed().load(); + private final static OkHttpClient client = new OkHttpClient(); - public RecipeInformation(){ - dotenv = Dotenv.configure().ignoreIfMissing().ignoreIfMalformed().load(); - client = new OkHttpClient(); + private RecipeInformation(){ } - public List getRecipeFromIds(List ids) throws IOException { + public static 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 List getExtendedRecipeFromIds(List ids) throws IOException { + public static 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<>() {}); } - private Response queryInformationBulk(String idsString) throws IOException { + private static Response queryInformationBulk(String idsString) throws IOException { Request request = APIAuthentication.addAuthHeaders(new Request.Builder() .url("https://" + dotenv.get("X-RapidAPI-Host") + "/recipes/informationBulk?ids=" + idsString)) @@ -47,7 +45,7 @@ public class RecipeInformation { return client.newCall(request).execute(); } - public List getRecepieByIngredientsExtended(List recipeByIngredients) throws IOException { + public static List getRecepieByIngredientsExtended(List recipeByIngredients) throws IOException { List ids = recipeByIngredients.stream().map(RecipeByIngredient::getId).toList(); List extendedRecipeByIngredients = getExtendedRecipeFromIds(ids); Iterator recipeByIngredientIterator = recipeByIngredients.iterator(); diff --git a/backend/src/main/spoonaccular/RecipeSearch.java b/backend/src/main/spoonaccular/RecipeSearch.java index a501623..b055dea 100644 --- a/backend/src/main/spoonaccular/RecipeSearch.java +++ b/backend/src/main/spoonaccular/RecipeSearch.java @@ -23,20 +23,15 @@ import java.util.Random; public class RecipeSearch { private static final boolean IGNOREPANTRY = true; - private final Random rnd; - private final Dotenv dotenv; - private final RecipeInformation recipeInformation; + private static final Random rnd = new Random(); + private static final Dotenv dotenv = Dotenv.configure().ignoreIfMissing().ignoreIfMalformed().load(); - private final OkHttpClient client; + private static final OkHttpClient client = new OkHttpClient(); - public RecipeSearch(){ - rnd = new Random(); - dotenv = Dotenv.configure().ignoreIfMissing().ignoreIfMalformed().load(); - recipeInformation = new RecipeInformation(); - client = new OkHttpClient(); + private RecipeSearch(){ } - public List getForIngridients(Iterable items, int number) throws java.io.IOException { + public static 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); @@ -53,14 +48,14 @@ public class RecipeSearch { String responseString = response.body().string(); List recipeByIngredients = new ObjectMapper().readValue(responseString, new TypeReference<>(){}); - return recipeInformation.getRecepieByIngredientsExtended(recipeByIngredients); + return RecipeInformation.getRecepieByIngredientsExtended(recipeByIngredients); } - public ExtendedRecipeByIngredient getOneForIngridients(Iterable items, int number) throws IOException { - return this.getForIngridients(items, number).get(rnd.nextInt(number)); + public static ExtendedRecipeByIngredient getOneForIngridients(Iterable items, int number) throws IOException { + return getForIngridients(items, number).get(rnd.nextInt(number)); } - public List getRandom(List tags, int number) throws java.io.IOException, JSONException { + public static 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") + diff --git a/backend/src/main/whattocook/controller/SpoonacularController.java b/backend/src/main/whattocook/controller/RecipeSearchController.java similarity index 62% rename from backend/src/main/whattocook/controller/SpoonacularController.java rename to backend/src/main/whattocook/controller/RecipeSearchController.java index 8fe7fb6..59a3eec 100644 --- a/backend/src/main/whattocook/controller/SpoonacularController.java +++ b/backend/src/main/whattocook/controller/RecipeSearchController.java @@ -5,7 +5,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.rest.webmvc.BasePathAwareController; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -import spoonaccular.RecipeInformation; import spoonaccular.RecipeSearch; import spoonaccular.models.recipe_by_ingredient.ExtendedRecipeByIngredient; import spoonaccular.models.recipe_information.Recipe; @@ -17,29 +16,26 @@ import java.util.List; @RestController() @BasePathAwareController() -public class SpoonacularController { - private int nextRecepies=10; - private int nextRecepiesForOneRandom=20; +public class RecipeSearchController { + private final int nextRecipes=10; + private final int nextRecipesForOneRandom = 20; + @Autowired private ItemRepository itemRepository; - @Autowired - private RecipeInformation recipeInformation; - @Autowired - private RecipeSearch recipeSearch; @GetMapping("/recipe/forFridge") public List getForFridge() throws IOException { - return recipeSearch.getForIngridients(itemRepository.findAll(), nextRecepies); + return RecipeSearch.getForIngridients(itemRepository.findAll(), nextRecipes); } @GetMapping("/recipe/random") - public List getRandom() throws IOException, InterruptedException, JSONException { - return recipeSearch.getRandom(new LinkedList<>(), nextRecepies); + public List getRandom() throws IOException, JSONException { + return RecipeSearch.getRandom(new LinkedList<>(), nextRecipes); //when user has food preferences apply instead of linked list. } @GetMapping("/recipe/oneFridge") public ExtendedRecipeByIngredient getOneFridge() throws IOException { - return recipeSearch.getOneForIngridients(itemRepository.findAll(), nextRecepiesForOneRandom); + return RecipeSearch.getOneForIngridients(itemRepository.findAll(), nextRecipesForOneRandom); } } From d9dcda19df6454851a5f2046b5476888acc6cff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bu=C3=9Fmann?= Date: Thu, 14 Jul 2022 01:15:39 +0200 Subject: [PATCH 8/9] added support for postgres --- .gitignore | 2 ++ .../resources/{application.yml => application.yml.example} | 6 +++--- build.gradle | 3 +++ 3 files changed, 8 insertions(+), 3 deletions(-) rename backend/src/resources/{application.yml => application.yml.example} (61%) diff --git a/.gitignore b/.gitignore index ef5a5f4..a08c3b2 100644 --- a/.gitignore +++ b/.gitignore @@ -96,3 +96,5 @@ frontend/src/index.js .env .DS_Store + +backend/src/resources/application.yml diff --git a/backend/src/resources/application.yml b/backend/src/resources/application.yml.example similarity index 61% rename from backend/src/resources/application.yml rename to backend/src/resources/application.yml.example index 32275c8..d64ad57 100644 --- a/backend/src/resources/application.yml +++ b/backend/src/resources/application.yml.example @@ -3,10 +3,10 @@ server: spring: datasource: - url: jdbc:h2:mem:whattocook - username: sa + url: + username: password: - driverClassName: org.h2.Driver + driver-class-name: data: rest: base-path: /api/v1 diff --git a/build.gradle b/build.gradle index 6a8174c..57002e6 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' + // https://mvnrepository.com/artifact/org.postgresql/postgresql + implementation group: 'org.postgresql', name: 'postgresql', version: '42.4.0' + // okhttp implementation("com.squareup.okhttp3:okhttp:4.10.0") From 6a34c67f56f76eb154f0cc9b03caec939fe9ba36 Mon Sep 17 00:00:00 2001 From: "Luis S. Ruisinger" Date: Thu, 14 Jul 2022 22:41:26 +0200 Subject: [PATCH 9/9] [Page Navigation] - working page navigation between ItemModel and LoginPage - ItemModel first loaded on website access - resizing variables because of client sided loading change in sizes - cleanup of links in navbar (only ItemModel / LoginPage) --- frontend/src/App.vue | 40 +- frontend/src/components/ItemModel.vue | 101 +++-- frontend/src/components/LoginPage.vue | 626 ++++++++++++++++++++++++++ frontend/src/styling/navbar.scss | 46 +- 4 files changed, 743 insertions(+), 70 deletions(-) create mode 100644 frontend/src/components/LoginPage.vue diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 1ef206f..003db3e 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,17 +1,41 @@ - + + + + \ No newline at end of file diff --git a/frontend/src/components/LoginPage.vue b/frontend/src/components/LoginPage.vue new file mode 100644 index 0000000..00f2238 --- /dev/null +++ b/frontend/src/components/LoginPage.vue @@ -0,0 +1,626 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/src/styling/navbar.scss b/frontend/src/styling/navbar.scss index 15b39f9..263f10c 100644 --- a/frontend/src/styling/navbar.scss +++ b/frontend/src/styling/navbar.scss @@ -18,8 +18,6 @@ } .nav-links { - display: flex; - list-style: none; a { margin: 0.2vh; @@ -30,17 +28,11 @@ a:hover { font-size: 3vh; transition: all 300ms; + color: brown; } } } -.divider{ - flex-grow: 1; - border-bottom: 0.1vh solid black; - margin: 5px; - -} - .menu-icon { position: relative; padding: 0.5vh 0.5vh; @@ -81,6 +73,7 @@ } .logo{ + position: relative; padding: 0.5vh 0.5vh; } @@ -118,8 +111,9 @@ left: 0; opacity: 0; flex-direction: column; - justify-content: space-evenly; + justify-content: center; align-items: center; + display: flex; padding: 5vh 0; width: 100vw; height: 120vh; @@ -127,9 +121,8 @@ letter-spacing: 0.25vh; color: white; background: #272727; - - transition: opacity 0.8s 0.5s, - clip-path 1s 0.5s; + align-content: center; + transition: opacity 0.8s 0.5s, clip-path 1s 0.5s; clip-path: circle(9.615384615384615vh at top right); .nav-links { @@ -137,6 +130,8 @@ transform: translateX(100%); width: 100%; text-align: center; + justify-content: center; + display: grid; a { display: block; @@ -156,9 +151,21 @@ clip-path: circle(100% at center); .nav-link { + justify-content: center; + align-content: center; + display: flex; opacity: 1; transform: translateX(0); } + + a { + position: relative; + align-content: center; + justify-content: center; + display: flex; + right: -0.2vh; + top: -11.5vh; + } } .menu-btn:checked ~ .menu-icon { @@ -179,6 +186,19 @@ } } } + + .logo{ + a { + position: absolute; + left: 0; + font-size: 4vh; + top: -0.68vh; + } + a:hover{ + font-size: 4vh; + cursor: default; + } + } } @keyframes openButtonBefore {