Skip to content
This repository was archived by the owner on Jul 24, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7123aed
add images to recipes
tgomezzzz Jul 28, 2020
3027c6c
correct package statement
tgomezzzz Jul 29, 2020
dedc827
add support for additional recipe data
tgomezzzz Jul 29, 2020
69466b3
add support for spin-offs pre-loading new recipe data and image
tgomezzzz Jul 29, 2020
921e37a
delete TestDisplayRecipesServlet
tgomezzzz Jul 29, 2020
93a5c7e
Merge branch 'create-recipe-html' into create-recipe-images
tgomezzzz Jul 29, 2020
59e11bf
Remove debugging print statements
tgomezzzz Jul 29, 2020
25fcc64
Remove spin-off tester input
tgomezzzz Jul 29, 2020
b78e172
change image uploading so Blobstore won't crash on live server
tgomezzzz Jul 29, 2020
3cbdf21
Merge branch 'create-recipe-images' of https://github.com/googleinter…
tgomezzzz Jul 29, 2020
254ef7c
add IngredientInput class to javascript, get buttons working
tgomezzzz Jul 30, 2020
049f6dd
change doPost to support cool ingredients
tgomezzzz Jul 31, 2020
4c57125
add conversion scale factors
tgomezzzz Jul 31, 2020
bda5d8a
clarify javadoc
tgomezzzz Jul 31, 2020
d18d649
implement Ingredient class
tgomezzzz Aug 1, 2020
1c70c05
update dependent files with new Ingredient class
tgomezzzz Aug 1, 2020
0804dd2
position ingredients properly
tgomezzzz Aug 1, 2020
26490e2
fix property casting errors
tgomezzzz Aug 1, 2020
117c7d9
fix ingredient population to work correctly
tgomezzzz Aug 1, 2020
d5a519e
update other classes to use new Ingredients
tgomezzzz Aug 1, 2020
0df5eb7
add scaling that affects serving size and ingredient amount
tgomezzzz Aug 2, 2020
63117a1
implement scaling a recipe
tgomezzzz Aug 3, 2020
17ff787
label scale input
tgomezzzz Aug 4, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions src/main/java/shef/data/Ingredient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package shef.data;

import com.google.appengine.api.datastore.EmbeddedEntity;

public class Ingredient {

private double amount;
private String unit;
private String name;

public Ingredient(double amount, String unit, String name) {
this.amount = amount;
this.unit = unit;
this.name = name;
}

public Ingredient(EmbeddedEntity entity) {
this.amount = (double) entity.getProperty("amount");
this.unit = (String) entity.getProperty("unit");
this.name = (String) entity.getProperty("name");
}

public double amount() {
return amount;
}

public String unit() {
return unit;
}

public String name() {
return name;
}

@Override
public boolean equals(Object other) {
return other instanceof Ingredient && this.amount == ((Ingredient) other).amount() && this.unit.equals(((Ingredient) other).unit()) && this.name.equals(((Ingredient) other).name());
}

@Override
public String toString() {
return "[Ingredient: " + amount + ", " + unit + ", " + name + "]";
}

@Override
public int hashCode() {
return (int) amount >> 2 + name.length();
}
}
41 changes: 31 additions & 10 deletions src/main/java/shef/data/Recipe.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.logging.*;
import java.util.Iterator;
import com.google.appengine.api.datastore.Entity;
Expand All @@ -32,8 +33,12 @@ public class Recipe {
private String key;
private String name;
private String description;
private double time;
private double servings;
private String imageKey;
private Set<String> tags;
private Set<String> ingredients;
private Set<Ingredient> ingredients;
private Set<String> equipment;
private List<Step> steps;
private Set<SpinOff> spinOffs;
private long timestamp;
Expand All @@ -52,7 +57,7 @@ public Recipe(Recipe recipe) {
}

/** Default constructor called when creating a new recipe. */
public Recipe(String name, String description, Set<String> tags, Set<String> ingredients, List<Step> steps, long timestamp) {
public Recipe(String name, String description, Set<String> tags, Set<Ingredient> ingredients, List<Step> steps, long timestamp) {
this.name = name;
this.description = description;
this.tags = tags;
Expand All @@ -62,18 +67,25 @@ public Recipe(String name, String description, Set<String> tags, Set<String> ing
this.timestamp = timestamp;
}

/** Creates a Recipe from a Datastore entity. */
/**
* Creates a Recipe from a Datastore entity.
* This constructor is used to easily convert an Entity into an object, which can be sent as JSON.
*/
public Recipe(Entity recipeEntity) {
this.name = (String) recipeEntity.getProperty("name");
this.description = (String) recipeEntity.getProperty("description");
this.time = (double) recipeEntity.getProperty("time");
this.servings = (double) recipeEntity.getProperty("servings");
this.imageKey = (String) recipeEntity.getProperty("imageKey");
this.tags = getTagsFromEntity((Collection<EmbeddedEntity>) recipeEntity.getProperty("tags"));
this.ingredients = getIngredientsFromEntity((Collection<EmbeddedEntity>) recipeEntity.getProperty("ingredients"));
this.equipment = getEquipmentFromEntity((Collection<EmbeddedEntity>) recipeEntity.getProperty("equipment"));
this.steps = getStepsFromEntity((Collection<EmbeddedEntity>) recipeEntity.getProperty("steps"));
this.timestamp = (long) recipeEntity.getProperty("timestamp");
}

/** Constructor called when creating a recipe to display on the recipe feed. */
public Recipe(String key, String name, String description, Set<String> tags, Set<String> ingredients, List<Step> steps, long timestamp) {
public Recipe(String key, String name, String description, Set<String> tags, Set<Ingredient> ingredients, List<Step> steps, long timestamp) {
this.key = key;
this.name = name;
this.tags = tags;
Expand Down Expand Up @@ -112,7 +124,7 @@ public Set<String> getTags() {
}

/** Gets the recipe's ingredients. */
public Set<String> getIngredients() {
public Set<Ingredient> getIngredients() {
return ingredients;
}

Expand All @@ -127,7 +139,7 @@ public void addTag(String tag) {
}

/** Adds an ingredient to the recipe. */
public void addIngredient(String ingredient) {
public void addIngredient(Ingredient ingredient) {
ingredients.add(ingredient);
}

Expand All @@ -142,7 +154,7 @@ public void removeTag(String tag) {
}

/** Removes an ingredient from the recipe. */
public void removeIngredient(String ingredient) {
public void removeIngredient(Ingredient ingredient) {
ingredients.remove(ingredient);
}

Expand Down Expand Up @@ -244,14 +256,23 @@ private Set<String> getTagsFromEntity(Collection<EmbeddedEntity> entityTags) {
}

/** Returns the ingredients of an EmbeddedEntity as a Set. */
private Set<String> getIngredientsFromEntity(Collection<EmbeddedEntity> entityIngredients) {
Set<String> ingredientsSet = new HashSet<>();
private Set<Ingredient> getIngredientsFromEntity(Collection<EmbeddedEntity> entityIngredients) {
Set<Ingredient> ingredientsSet = new HashSet<>();
for (EmbeddedEntity ingredient : entityIngredients) {
ingredientsSet.add((String) ingredient.getProperty("ingredient"));
ingredientsSet.add(new Ingredient(ingredient));
}
return ingredientsSet;
}

/** Returns the equipment of an EmbeddedEntity as a Set. */
private Set<String> getEquipmentFromEntity(Collection<EmbeddedEntity> entityEquipment) {
Set<String> equipmentSet = new HashSet<>();
for (EmbeddedEntity equipment : entityEquipment) {
equipmentSet.add((String) equipment.getProperty("equipment"));
}
return equipmentSet;
}

/** Returns the steps of an EmbeddedEntity as a List. */
private List<Step> getStepsFromEntity(Collection<EmbeddedEntity> entitySteps) {
List<Step> stepsList = new LinkedList<>();
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/shef/servlets/BlobServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@
import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;
import com.google.appengine.api.blobstore.BlobInfo;
import com.google.appengine.api.blobstore.BlobInfoFactory;
import java.io.IOException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;

/** Servlet that serves blobstore files. */
@WebServlet("/blob")
Expand All @@ -33,4 +37,30 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro
BlobKey blobKey = new BlobKey(request.getParameter("blob-key"));
blobstoreService.serve(blobKey, response);
}

/*
* Returns the String representation of the BlobKey of the uploaded file, or null if the user didn't upload a file.
* Used in alternative to getting the blob's direct URL, which causes crashes on the live server.
*/
public static String getUploadedFileBlobKey(HttpServletRequest request, String formInputElementName) {
BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
Map<String, List<BlobKey>> blobs = blobstoreService.getUploads(request);
List<BlobKey> blobKeys = blobs.get(formInputElementName);

// User submitted form without selecting a file, so we can't get a URL. (dev server)
if (blobKeys == null || blobKeys.isEmpty()) {
return null;
}

// Our form only contains a single file input, so get the first index.
BlobKey blobKey = blobKeys.get(0);

// User submitted form without selecting a file, so we can't get a URL. (live server)
BlobInfo blobInfo = new BlobInfoFactory().loadBlobInfo(blobKey);
if (blobInfo.getSize() == 0) {
blobstoreService.delete(blobKey);
return null;
}
return blobKey.getKeyString();
}
}
24 changes: 1 addition & 23 deletions src/main/java/shef/servlets/DisplayRecipesServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,34 +53,12 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro

List<Recipe> recipes = new ArrayList<>();
for (Entity entity : results.asIterable()) {
recipes.add(entityToRecipe(entity));
recipes.add(new Recipe(entity));
}

Gson gson = new Gson();

response.setContentType("application/json;");
response.getWriter().println(gson.toJson(recipes));
}

/** Converts a Datastore entity into a Recipe. */
public Recipe entityToRecipe(Entity recipeEntity) {
String key = KeyFactory.keyToString(recipeEntity.getKey());
String name = (String) recipeEntity.getProperty("name");
String description = (String) recipeEntity.getProperty("description");
LinkedHashSet<String> tags = new LinkedHashSet<>((LinkedList<String>) (LinkedList<?>) getDataAsList(recipeEntity.getProperty("tags"), "tag"));
LinkedHashSet<String> ingredients = new LinkedHashSet<>((LinkedList<String>) (LinkedList<?>) getDataAsList(recipeEntity.getProperty("ingredients"), "ingredient"));
LinkedList<Step> steps = (LinkedList<Step>) (LinkedList<?>) getDataAsList(recipeEntity.getProperty("steps"), "step");
long timestamp = (long) recipeEntity.getProperty("timestamp");
return new Recipe(key, name, description, tags, ingredients, steps, timestamp);
}

/** Gets a list of Recipe parameters from a Datastore property. */
public Collection<Object> getDataAsList(Object propertiesObject, String field) {
Collection<EmbeddedEntity> properties = (Collection<EmbeddedEntity>) propertiesObject;
Collection<Object> dataAsList = new LinkedList<>();
for (EmbeddedEntity property : properties) {
dataAsList.add(property.getProperty(field));
}
return dataAsList;
}
}
49 changes: 35 additions & 14 deletions src/main/java/shef/servlets/NewRecipeServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public class NewRecipeServlet extends HttpServlet {
private DatastoreService datastore;
private final String TAG = "tag";
private final String INGREDIENT = "ingredient";
private final String EQUIPMENT = "equipment";
private final String STEP = "step";

@Override
Expand All @@ -62,7 +63,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro
}
Recipe original = new Recipe(recipeEntity);
response.setContentType("application/json;");
response.getWriter().println(convertToJsonUsingGson(original));
response.getWriter().println(new Gson().toJson(original));
}

/** Posts a new recipe to the servlet. */
Expand All @@ -71,30 +72,33 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr
Collection<String> searchStrings = new HashSet<>();
String name = request.getParameter("name");
searchStrings.add(name.toUpperCase());
double time = request.getParameter("time").equals("") ? 0 : Double.parseDouble(request.getParameter("time"));
double servings = request.getParameter("servings").equals("") ? 0 : Double.parseDouble(request.getParameter("servings"));
String imageKey = BlobServlet.getUploadedFileBlobKey(request, "image");
String description = request.getParameter("description");
Collection<EmbeddedEntity> tags = getParameters(request, TAG, searchStrings);
Collection<EmbeddedEntity> ingredients = getParameters(request, INGREDIENT, searchStrings);
Collection<EmbeddedEntity> ingredients = getIngredients(request, searchStrings);
Collection<EmbeddedEntity> equipment = getParameters(request, EQUIPMENT, null);
Collection<EmbeddedEntity> steps = getParameters(request, STEP, null);
int likes;
try {
likes = Integer.parseInt(request.getParameter("likes"));
} catch (NumberFormatException e) {
likes = 0;
}
int likes = request.getParameter("likes") == null ? 0 : Integer.parseInt(request.getParameter("likes"));
long timestamp = System.currentTimeMillis();

Entity recipe = new Entity("Recipe");
recipe.setProperty("name", name);
recipe.setProperty("time", time);
recipe.setProperty("servings", servings);
recipe.setProperty("imageKey", imageKey);
recipe.setProperty("description", description);
recipe.setProperty("tags", tags);
recipe.setProperty("ingredients", ingredients);
recipe.setProperty("equipment", equipment);
recipe.setProperty("steps", steps);
recipe.setProperty("search-strings", new ArrayList<String>(searchStrings));
recipe.setProperty("timestamp", timestamp);
recipe.setProperty("likes", likes);
datastore.put(recipe);

response.sendRedirect("/recipe.html");
response.sendRedirect("/recipe.html?key=" + KeyFactory.keyToString(recipe.getKey()));
}

/**
Expand Down Expand Up @@ -122,16 +126,33 @@ private Collection<EmbeddedEntity> getParameters(HttpServletRequest request, Str
return parameters;
}

private Collection<EmbeddedEntity> getIngredients(HttpServletRequest request, Collection<String> searchStrings) {
Collection<EmbeddedEntity> parameters = new LinkedList<>();
int parameterNum = 0;
String parameterName = "ingredient" + parameterNum;
String[] parameterValues = request.getParameterValues(parameterName);

// In the HTML form, parameters are named as [field name][index], ie step0.
// This loop increments the index of the parameter's name, exiting once it reaches an index for which there is no parameter.
while (parameterValues != null) {
addToSearchStrings(searchStrings, parameterValues[2]);
EmbeddedEntity parameterEntity = new EmbeddedEntity();
parameterEntity.setProperty("amount", parameterValues[0].equals("") ? 0 : Double.parseDouble(parameterValues[0]));
parameterEntity.setProperty("unit", parameterValues[1]);
parameterEntity.setProperty("name", parameterValues[2]);
parameters.add(parameterEntity);

parameterName = "ingredient" + (++parameterNum);
parameterValues = request.getParameterValues(parameterName);
}
return parameters;
}

/** Adds a formatted search string to the set of search strings. */
private void addToSearchStrings(Collection<String> searchStrings, String stringToAdd) {
if (searchStrings == null) {
return;
}
searchStrings.add(stringToAdd.toUpperCase());
}

private String convertToJsonUsingGson(Recipe recipe) {
Gson gson = new Gson();
return gson.toJson(recipe);
}
}
Loading