[WIP] Reworked the way the spoonaccular api response gets consumed

- Added pojos for jackson
- Filtering unneeded information from spoonaccular response
This commit is contained in:
2022-07-12 17:24:57 +02:00
parent e969c32f83
commit f7455019c3
18 changed files with 650 additions and 150 deletions

3
.env.example Normal file
View File

@@ -0,0 +1,3 @@
SPOONACCULAR_API_URL=
SPOONACCULAR_API_KEY=
SPOONACCULAR_API_SEARCH_RANKING=

4
.gitignore vendored
View File

@@ -92,3 +92,7 @@ out
!gradle/wrapper/gradle-wrapper.jar
frontend/src/index.js
.env
.DS_Store

View File

@@ -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;
}
}

View File

@@ -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<Item> items, int number) throws java.io.IOException {
List<String> 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<Item> items, int number) throws IOException {
return getForIngridients(items, number)[rnd.nextInt(number)];
}
public Recipe[] getRandom(List<String> 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<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
JSONObject jsonObject = new JSONObject(response.body());
return new ObjectMapper().readValue(jsonObject.getJSONArray("recipes").toString(), Recipe[].class);
}
}

View File

@@ -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<MissedIngredient> missedIngredients = null;
@JsonProperty("usedIngredients")
private List<UsedIngredient> 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<MissedIngredient> getMissedIngredients() {
return missedIngredients;
}
@JsonProperty("missedIngredients")
public void setMissedIngredients(List<MissedIngredient> missedIngredients) {
this.missedIngredients = missedIngredients;
}
@JsonProperty("usedIngredients")
public List<UsedIngredient> getUsedIngredients() {
return usedIngredients;
}
@JsonProperty("usedIngredients")
public void setUsedIngredients(List<UsedIngredient> usedIngredients) {
this.usedIngredients = usedIngredients;
}
}

View File

@@ -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;
}
}

View File

@@ -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<MissedIngredient> missedIngredients = null;
@JsonProperty("usedIngredients")
private List<UsedIngredient> 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<MissedIngredient> getMissedIngredients() {
return missedIngredients;
}
@JsonProperty("missedIngredients")
public void setMissedIngredients(List<MissedIngredient> missedIngredients) {
this.missedIngredients = missedIngredients;
}
@JsonProperty("usedIngredients")
public List<UsedIngredient> getUsedIngredients() {
return usedIngredients;
}
@JsonProperty("usedIngredients")
public void setUsedIngredients(List<UsedIngredient> usedIngredients) {
this.usedIngredients = usedIngredients;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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<ExtendedIngredient> 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<ExtendedIngredient> getExtendedIngredients() {
return extendedIngredients;
}
@JsonProperty("extendedIngredients")
public void setExtendedIngredients(List<ExtendedIngredient> 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;
}
}

View File

@@ -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) {

View File

@@ -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<JSONArray> getForFridge() throws IOException, InterruptedException, JSONException {
return new HttpEntity<JSONArray>(service.getForIngridients(itemRepository.findAll(), nextRecepies));
}
@GetMapping("/random")
public HttpEntity<JSONArray> getRandom() throws IOException, InterruptedException, JSONException {
return new HttpEntity<JSONArray>(service.getRandom(new LinkedList<>(), nextRecepies));
//when user has food preferences apply instead of linked list.
}
@GetMapping("/oneFridge")
public HttpEntity<JSONObject> getOneFridge() throws IOException, InterruptedException, JSONException {
return new HttpEntity<JSONObject>(service.getOneForIngridients(itemRepository.findAll(), nextRecepiesForOneRandom));
}
public void setNextRecepies(int nextRecepies) {
this.nextRecepies = nextRecepies;
}
}

View File

@@ -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);
}
}

View File

@@ -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<Item> items, int number) throws java.io.IOException, InterruptedException, JSONException {
Iterator<Item> itemIterator = items.iterator();
if (!itemIterator.hasNext()) {
return getRandom(new java.util.LinkedList<String>(), number);
} else {
String ingridients = itemIterator.next().getName();
for (Iterator<Item> 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<String> 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<Item> items, int number) throws IOException, InterruptedException, JSONException {
JSONArray array= getForIngridients(items, number);
return array.getJSONObject(rnd.nextInt(20));
}
public JSONArray getRandom(java.util.List<String> 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<String> 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<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
JSONArray array=new JSONArray(response.body());
return array;
}
}
}

View File

@@ -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<Item> items, int number) throws java.io.IOException, InterruptedException, JSONException;
JSONObject getOneForIngridients(Iterable<Item> items, int number) throws java.io.IOException, InterruptedException, JSONException;
JSONArray getRandom(java.util.List<String> tags, int number) throws java.io.IOException, InterruptedException, JSONException;
}

View File

@@ -33,6 +33,8 @@ dependencies {
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'