Merge remote-tracking branch 'origin/main' into fix-item-input

This commit is contained in:
Karthik Vempati
2022-07-17 12:12:54 +02:00
24 changed files with 1404 additions and 195 deletions

3
.env.example Normal file
View File

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

6
.gitignore vendored
View File

@@ -92,3 +92,9 @@ out
!gradle/wrapper/gradle-wrapper.jar !gradle/wrapper/gradle-wrapper.jar
frontend/src/index.js frontend/src/index.js
.env
.DS_Store
backend/src/resources/application.yml

View File

@@ -0,0 +1,20 @@
package spoonaccular;
import io.github.cdimascio.dotenv.Dotenv;
import okhttp3.Request;
public class APIAuthentication {
private static final Dotenv dotenv = Dotenv.configure().ignoreIfMissing().ignoreIfMalformed().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"));
}
}

View File

@@ -0,0 +1,59 @@
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.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class RecipeInformation {
private final static Dotenv dotenv = Dotenv.configure().ignoreIfMissing().ignoreIfMalformed().load();
private final static OkHttpClient client = new OkHttpClient();
private RecipeInformation(){
}
public static List<Recipe> getRecipeFromIds(List<Integer> ids) throws IOException {
String idsString = ids.stream().map(String::valueOf)
.collect(Collectors.joining(","));
return new ObjectMapper().readValue(queryInformationBulk(idsString).body().string(), new TypeReference<>(){});
}
public static List<ExtendedRecipeByIngredient> getExtendedRecipeFromIds(List<Integer> ids) throws IOException {
String idsString = ids.stream().map(String::valueOf)
.collect(Collectors.joining(","));
return new ObjectMapper().readValue(queryInformationBulk(idsString).body().string(), new TypeReference<>() {});
}
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))
.build();
return client.newCall(request).execute();
}
public static List<ExtendedRecipeByIngredient> getRecepieByIngredientsExtended(List<RecipeByIngredient> recipeByIngredients) throws IOException {
List<Integer> ids = recipeByIngredients.stream().map(RecipeByIngredient::getId).toList();
List<ExtendedRecipeByIngredient> extendedRecipeByIngredients = getExtendedRecipeFromIds(ids);
Iterator<RecipeByIngredient> recipeByIngredientIterator = recipeByIngredients.iterator();
Iterator<ExtendedRecipeByIngredient> extendedRecipeByIngredientIterator = extendedRecipeByIngredients.iterator();
while(recipeByIngredientIterator.hasNext() && extendedRecipeByIngredientIterator.hasNext()){
extendedRecipeByIngredientIterator.next().addMissingInfo(recipeByIngredientIterator.next());
}
return extendedRecipeByIngredients;
}
}

View File

@@ -0,0 +1,69 @@
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.util.LinkedList;
import java.util.List;
import java.util.Random;
@Component
public class RecipeSearch {
private static final boolean IGNOREPANTRY = true;
private static final Random rnd = new Random();
private static final Dotenv dotenv = Dotenv.configure().ignoreIfMissing().ignoreIfMalformed().load();
private static final OkHttpClient client = new OkHttpClient();
private RecipeSearch(){
}
public static List<ExtendedRecipeByIngredient> 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);
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();
Response response = client.newCall(request).execute();
String responseString = response.body().string();
List<RecipeByIngredient> recipeByIngredients = new ObjectMapper().readValue(responseString, new TypeReference<>(){});
return RecipeInformation.getRecepieByIngredientsExtended(recipeByIngredients);
}
public static ExtendedRecipeByIngredient getOneForIngridients(Iterable<Item> items, int number) throws IOException {
return getForIngridients(items, number).get(rnd.nextInt(number));
}
public static List<Recipe> getRandom(List<String> 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<>(){});
}
}

View File

@@ -0,0 +1,65 @@
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;
}
public void addMissingInfo(RecipeByIngredient recipeByIngredient){
this.setMissedIngredientCount(recipeByIngredient.getMissedIngredientCount());
this.setUsedIngredientCount(recipeByIngredient.getUsedIngredientCount());
this.setMissedIngredients(recipeByIngredient.getMissedIngredients());
this.setUsedIngredients(recipeByIngredient.getUsedIngredients());
}
}

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; import java.util.Collections;
@SpringBootApplication @SpringBootApplication(scanBasePackages = {"spoonaccular", "whattocook"})
public class Application { public class Application {
public static void main(String[] args) { 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,41 @@
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.RestController;
import spoonaccular.RecipeSearch;
import spoonaccular.models.recipe_by_ingredient.ExtendedRecipeByIngredient;
import spoonaccular.models.recipe_information.Recipe;
import whattocook.repositories.ItemRepository;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
@RestController()
@BasePathAwareController()
public class RecipeSearchController {
private final int nextRecipes=10;
private final int nextRecipesForOneRandom = 20;
@Autowired
private ItemRepository itemRepository;
@GetMapping("/recipe/forFridge")
public List<ExtendedRecipeByIngredient> getForFridge() throws IOException {
return RecipeSearch.getForIngridients(itemRepository.findAll(), nextRecipes);
}
@GetMapping("/recipe/random")
public List<Recipe> 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(), nextRecipesForOneRandom);
}
}

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

@@ -3,10 +3,10 @@ server:
spring: spring:
datasource: datasource:
url: jdbc:h2:mem:whattocook url:
username: sa username:
password: password:
driverClassName: org.h2.Driver driver-class-name:
data: data:
rest: rest:
base-path: /api/v1 base-path: /api/v1

View File

@@ -29,10 +29,18 @@ dependencies {
// https://mvnrepository.com/artifact/com.h2database/h2 // https://mvnrepository.com/artifact/com.h2database/h2
implementation group: 'com.h2database', name: 'h2', version: '1.3.148' implementation group: 'com.h2database', name: 'h2', version: '1.3.148'
//lombok // 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")
// lombok
compileOnly 'org.projectlombok:lombok:1.18.24' compileOnly 'org.projectlombok:lombok:1.18.24'
annotationProcessor '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' testCompileOnly 'org.projectlombok:lombok:1.18.24'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.24' testAnnotationProcessor 'org.projectlombok:lombok:1.18.24'

View File

@@ -1,17 +1,41 @@
<template>
<div id="app">
<ItemModel/>
</div>
</template>
<script> <script>
import ItemModel from "@/components/ItemModel"; import ItemModel from "@/components/ItemModel";
import LoginPage from "@/components/LoginPage";
import Custom404Page from "@/components/Custom404Page";
const routes = {
'/': ItemModel,
'/login': LoginPage
}
export default { export default {
components: { data() {
ItemModel return {
currentPath: window.location.hash
}
},
computed: {
currentView() {
return routes[this.currentPath.slice(1) || '/'] || Custom404Page
}
},
mounted() {
window.addEventListener('hashchange', () => {
this.currentPath = window.location.hash
})
} }
}; }
</script> </script>
<template>
<component :is="currentView" />
</template>
<style> <style>
[v-cloak] { [v-cloak] {
display: none; display: none;

View File

@@ -15,8 +15,6 @@
<a>Storage</a> <a>Storage</a>
</div> </div>
<span class="divider"></span>
<input type="checkbox" class="menu-btn" id="menu-btn"> <input type="checkbox" class="menu-btn" id="menu-btn">
<label for="menu-btn" class="menu-icon"> <label for="menu-btn" class="menu-icon">
<span class="menu-icon__line"></span> <span class="menu-icon__line"></span>
@@ -24,16 +22,7 @@
<ul class="nav-links"> <ul class="nav-links">
<li class="nav-link"> <li class="nav-link">
<router-link to="/">Home</router-link> <a href="/#/login">Sign up</a>
</li>
<li class="nav-link">
<router-link to="/profile">Profile</router-link>
</li>
<li class="nav-link">
<router-link to="/recipes">Recipes</router-link>
</li>
<li class="nav-link">
<router-link to="/shoppinglist">Shoppinglist</router-link>
</li> </li>
</ul> </ul>
</div> </div>
@@ -44,7 +33,7 @@
<div class="field-header-box"> <div class="field-header-box">
<div class="inputField-header"> <div class="inputField-header">
<input class="newItemName" id="inputTextField" autofocus autocomplete="off" placeholder=" " v-model="newItem" <input class="newItemName" id="inputTextField" autofocus autocomplete="off" placeholder="Add here..." v-model="newItem"
@keyup.enter="addItem"/> @keyup.enter="addItem"/>
<label for="inputTextField" class="formLabel"> <label for="inputTextField" class="formLabel">
Add here ... Add here ...
@@ -205,7 +194,7 @@ export default Items
body{ body{
margin: 0; margin: 0;
padding: 0; padding: 0;
background-color:darkcyan; background-color: #213737;
} }
.main { .main {
@@ -216,7 +205,7 @@ body{
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-size: 1.25vh; font-size: 1.25vh;
background-color: darkcyan; background-color: #213737;
} }
/* navbar-background */ /* navbar-background */
@@ -226,7 +215,7 @@ body{
left: 0; left: 0;
top: 0; top: 0;
z-index: 3; z-index: 3;
background: darkcyan; background: #213737;
width: 100vw; width: 100vw;
height: 15vh; height: 15vh;
} }
@@ -262,11 +251,11 @@ body{
border-radius: 0.4411764705882353vh; // times 2 of border border-radius: 0.4411764705882353vh; // times 2 of border
font-family: inherit; font-family: inherit;
font-size: inherit; font-size: inherit;
color: black; color: white;
outline: none; outline: none;
padding: 0.5vh;// size of font padding: 0.5vh;// size of font
box-shadow: 10px 10px 30px rgba(0, 0, 0, 0.4); // 0.5 size of font and 1.5 size of font box-shadow: 10px 10px 30px rgba(0, 0, 0, 0.4); // 0.5 size of font and 1.5 size of font
background: darkcyan; background: #213737;
} }
.newItemName:hover { .newItemName:hover {
@@ -546,6 +535,4 @@ body{
} }
} }
</style> </style>

View File

@@ -0,0 +1,626 @@
<template>
<div class="main">
<div v-if="loading">
<h1 class="loading">Loading...</h1>
</div>
<div v-else>
<!-- navbar -->
<header class="navbar-header">
<div class="logo">
<a>Login</a>
</div>
<input id="menu-btn" class="menu-btn" type="checkbox">
<label class="menu-icon" for="menu-btn">
<span class="menu-icon__line"></span>
</label>
<ul class="nav-links">
<li class="nav-link">
<a href="/">Storage</a>
</li>
</ul>
</header>
<section>
<div class="container">
<div id="user-sign-in" class="user-login">
<div class="site-background"/>
<div class="form-background">
<form>
<h2>Sign In</h2>
<input placeholder="Username" type="text">
<input placeholder="Password" type="password">
<input class="login-btn-apple" type="submit" value="Login Apple">
<input class="login-btn" type="submit" value="Login">
<p class="signup">Don't have an account?
<a href="#/login" v-on:click="isSignUp = !isSignUp">Sign up</a>
</p>
</form>
</div>
</div>
<div :style="{'display': isSignUp ? 'none' : ''}" class="user-signup">
<div class="site-background"/>
<div class="form-background">
<form>
<h2>Create Account</h2>
<input placeholder="Username" type="text">
<input placeholder="Email Address" type="text">
<input placeholder="Create Password" type="password">
<input placeholder="Confirm Password" type="password">
<input type="submit" value="Sign Up" class="submit">
<p class="signup">Have an account?
<br>
<a href="#/login" v-on:click="isSignUp = !isSignUp">Sign in</a>
</p>
</form>
</div>
</div>
</div>
</section>
</div>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.css" rel="stylesheet"
type='text/css'/>
</div>
</template>
<script>
export default {
name: "LoginPage",
data() {
return {
isSignUp: true
}
}
}
</script>
<style lang="scss">
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@100;200&display=swap');
@import 'src/styling/navbar';
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
list-style: none;
}
.main {
font-family: 'Montserrat', sans-serif;
height: 100vh;
display: grid;
justify-content: center;
align-items: center;
background: #213737;
}
.logo {
left: -3vh;
top: 1vh;
}
.logo a {
color: white;
}
.menu-icon {
left: 3vh;
top: 1vh;
&__line, &__line:before, &__line::after {
background-color: white;
}
}
/* login / signin styling */
section {
position: relative;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 40px;
.container {
position: relative;
width: 2800px;
height: 1700px;
background: white;
box-shadow: 0 15px 50px rgba(0, 0, 0, 0.6);
}
.user-login {
position: relative;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
.site-background {
position: relative;
width: 50%;
height: 100%;
transition: 0.5s;
//background: url('../ressouce/5008bf96009ea69ba157815061bce4f2.png');
background: brown;
}
.form-background {
position: relative;
width: 50%;
height: 100%;
background: white;
display: flex;
justify-content: center;
align-items: center;
padding: 160px;
}
h2 {
position: relative;
font-size: 60px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 8px;
text-align: center;
width: 100%;
margin-bottom: 20px;
color: black;
top: -30px;
}
h2:after {
content: '';
display: block;
border-bottom: 4px solid #000;
height: 0;
position: relative;
top: 50px;
}
input {
position: relative;
width: 100%;
padding: 40px;
background: lightgrey;
border: none;
outline: none;
box-shadow: none;
font-size: 1vh;
letter-spacing: 4px;
margin: 20px 0;
top: 50px;
color: black;
}
input[type="submit"] {
cursor: pointer;
max-width: 520px;
transition: 0.5s;
background-color: darkslategrey;
color: white;
}
.login-btn {
position: relative;
left: 40px;
box-shadow: 10px 10px 30px rgba(0, 0, 0, 0.2);
}
.login-btn-apple {
box-shadow: 10px 10px 30px rgba(0, 0, 0, 0.2);
}
.login-btn:hover, .login-btn-apple:hover {
background: brown;
color: black;
}
.signup {
text-align: center;
justify-content: right;
display: flex;
position: relative;
margin-top: 550px;
top: 50px;
font-size: 40px;
text-transform: uppercase;
letter-spacing: 4px;
//border: 1px solid black;
}
a {
font-weight: 600;
text-decoration: none;
color: darkslategrey;
letter-spacing: 2px;
font-size: 50px;
transition: color 0.5s ease-in-out;
//border: 1px solid black;
width: 100%;
text-align: left;
justify-content: center;
display: flex;
left: 30%;
top: -8px;
}
a:hover{
color: brown;
}
}
.user-signup {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transition: display 0.5s ease-in-out;
.site-background {
position: relative;
width: 50%;
height: 100%;
left: 50%;
transition: 0.5s;
//background: url('../ressouce/5008bf96009ea69ba157815061bce4f2.png');
background: brown;
}
.form-background {
position: relative;
width: 50%;
height: 100%;
background: white;
display: flex;
justify-content: center;
align-items: center;
padding: 160px;
top: -100%;
}
h2 {
position: relative;
font-size: 60px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 8px;
text-align: center;
width: 100%;
margin-bottom: 20px;
color: black;
top: 150px;
}
h2:after {
content: '';
display: block;
border-bottom: 4px solid #000;
height: 0;
position: relative;
top: 50px;
}
input {
position: relative;
width: 100%;
padding: 40px;
background: lightgrey;
border: none;
outline: none;
box-shadow: none;
font-size: 40px;
letter-spacing: 4px;
margin: 20px 0;
top: 230px;
color: black;
}
.submit{
box-shadow: 10px 10px 30px rgba(0, 0, 0, 0.2);
}
.submit:hover{
background: brown;
color: black;
}
input[type="submit"] {
cursor: pointer;
max-width: 520px;
transition: 0.5s;
background-color: darkslategrey;
color: white;
left: 51.8%;
}
.signup {
text-align: center;
justify-content: left;
display: flex;
position: relative;
margin-top: 560px;
font-size: 20px;
text-transform: uppercase;
letter-spacing: 4px;
top: -130px;
//border: 1px solid black;
}
a {
text-align: center;
justify-content: center;
display: flex;
width: 100%;
font-weight: 600;
text-decoration: none;
color: darkslategrey;
letter-spacing: 2px;
font-size: 50px;
top: -10px;
left: 30%;
transition: color 0.5s ease-in-out;
//border: 1px solid black;
}
a:hover{
color: brown;
}
}
}
@media (max-device-height: 1440px) {
/* login / signin styling */
section {
padding: 52px;
.container {
width: 1800px;
height: 1200px;
box-shadow: 0 15px 50px rgba(0, 0, 0, 0.6);
}
.user-login {
.form-background {
padding: 106px;
}
h2 {
font-size: 40px;
letter-spacing: 6px;
margin-bottom: 14px;
top: -40px;
}
h2:after {
border-bottom: 4px solid #000;
top: 26px;
}
input {
padding: 26px;
letter-spacing: 4px;
margin: 14px 0;
top: -4px;
}
input[type="submit"] {
max-width: 330px;
}
.login-btn {
left: 28px;
}
.signup {
margin-top: 400px;
letter-spacing: 2px;
font-size: 20px;
top: 20px;
justify-content: center;
}
a {
letter-spacing: 4px;
font-size: 40px;
top: 50px;
width: 100%;
left: 0;
}
}
.user-signup {
.form-background {
padding: 106px;
}
h2 {
font-size: 40px;
letter-spacing: 6px;
margin-bottom: 140px;
top: 120px;
}
h2:after {
border-bottom: 4px solid #000;
top: 26px;
}
input {
padding: 26px;
font-size: 26px;
letter-spacing: 4px;
margin: 14px 0;
top: 27px;
}
input[type="submit"] {
max-width: 330px;
left: 52%;
}
.signup {
margin-top: 374px;
letter-spacing: 2px;
top: -154px;
font-size: 20px;
justify-content: center;
}
a {
letter-spacing: 4px;
width: 100%;
left: 0;
font-size: 40px;
top: 50px;
}
}
}
}
@media (max-device-height: 1080px) {
/* login / signin styling */
section {
padding: 40px;
.container {
width: 1500px;
height: 900px;
box-shadow: 0 7.5px 25px rgba(0, 0, 0, 0.6);
}
.user-login {
.form-background {
padding: 80px;
}
h2 {
font-size: 30px;
letter-spacing: 4px;
margin-bottom: 10px;
top: -25px;
}
h2:after {
border-bottom: 2px solid #000;
top: 25px;
}
input {
padding: 12px;
font-size: 12px;
letter-spacing: 2px;
margin: 10px 0;
top: 15px;
}
input[type="submit"] {
max-width: 285px;
}
.login-btn {
left: 20px;
}
.signup {
position: relative;
margin-top: 300px;
letter-spacing: 2px;
font-size: 0.05vh;
align-content: center;
}
a {
letter-spacing: 1px;
align-content: center;
font-size: 40px;
}
}
.user-signup {
.form-background {
padding: 80px;
}
h2 {
font-size: 30px;
letter-spacing: 4px;
margin-bottom: 10px;
top: 50px;
}
h2:after {
border-bottom: 2px solid #000;
top: 25px;
}
input {
padding: 12px;
font-size: 12px;
letter-spacing: 2px;
margin: 10px 0;
top: 90px;
}
input[type="submit"] {
max-width: 260px;
left: 55.8%;
}
.signup {
margin-top: 280px;
letter-spacing: 2px;
top: -60px;
font-size: 30px;
}
a {
letter-spacing: 2px;
font-size: 40px;
align-content: center;
}
}
}
}
</style>

View File

@@ -18,8 +18,6 @@
} }
.nav-links { .nav-links {
display: flex;
list-style: none;
a { a {
margin: 0.2vh; margin: 0.2vh;
@@ -30,18 +28,11 @@
a:hover { a:hover {
font-size: 3vh; font-size: 3vh;
transition: all 300ms; transition: all 300ms;
color: brown;
} }
} }
} }
.divider{
position: relative;
flex-grow: 1;
border-bottom: 0.1vh solid black;
margin: 5px;
top: 2vh;
}
.menu-icon { .menu-icon {
position: relative; position: relative;
padding: 0.5vh 0.5vh; padding: 0.5vh 0.5vh;
@@ -122,8 +113,9 @@
left: 0; left: 0;
opacity: 0; opacity: 0;
flex-direction: column; flex-direction: column;
justify-content: space-evenly; justify-content: center;
align-items: center; align-items: center;
display: flex;
padding: 5vh 0; padding: 5vh 0;
width: 100vw; width: 100vw;
height: 120vh; height: 120vh;
@@ -131,9 +123,8 @@
letter-spacing: 0.25vh; letter-spacing: 0.25vh;
color: white; color: white;
background: #272727; background: #272727;
align-content: center;
transition: opacity 0.8s 0.5s, transition: opacity 0.8s 0.5s, clip-path 1s 0.5s;
clip-path 1s 0.5s;
clip-path: circle(9.615384615384615vh at top right); clip-path: circle(9.615384615384615vh at top right);
.nav-links { .nav-links {
@@ -141,6 +132,8 @@
transform: translateX(100%); transform: translateX(100%);
width: 100%; width: 100%;
text-align: center; text-align: center;
justify-content: center;
display: grid;
a { a {
display: block; display: block;
@@ -160,9 +153,21 @@
clip-path: circle(100% at center); clip-path: circle(100% at center);
.nav-link { .nav-link {
justify-content: center;
align-content: center;
display: flex;
opacity: 1; opacity: 1;
transform: translateX(0); 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 { .menu-btn:checked ~ .menu-icon {
@@ -183,6 +188,19 @@
} }
} }
} }
.logo{
a {
position: absolute;
left: 0;
font-size: 4vh;
top: -0.68vh;
}
a:hover{
font-size: 4vh;
cursor: default;
}
}
} }
@keyframes openButtonBefore { @keyframes openButtonBefore {