diff --git a/src/main/java/com/google/sps/data/DBInterface.java b/src/main/java/com/google/sps/data/DBInterface.java index 1ace5a3..53ef274 100644 --- a/src/main/java/com/google/sps/data/DBInterface.java +++ b/src/main/java/com/google/sps/data/DBInterface.java @@ -275,6 +275,29 @@ public List getRecipesMatchingCreator( public List getRecipesMatchingCreator( String creatorId, SortingMethod sortingMethod); + /** + * Returns a paginated list of recipe metadata associated with tags followd by + * a certain user. + * + * @param userId user's Firestore ID + * @param sortingMethod such as TOP or NEW + * @param page pagination for recipe query + * @return user's follow-associated recipe metadata, sorted + */ + public List getRecipesMatchingFollowedTags( + String userId, SortingMethod sortingMethod, int page); + + /** + * Returns a non-paginated list of recipe metadata associated with tags followd by + * a certain user. + * + * @param userId user's Firestore ID + * @param sortingMethod such as TOP or NEW + * @return user's follow-associated recipe metadata, sorted + */ + public List getRecipesMatchingFollowedTags( + String userId, SortingMethod sortingMethod); + /** * Returns a paginated list of recipe metadata associated with the IDs of recipes saved by a user. * diff --git a/src/main/java/com/google/sps/data/FirestoreDB.java b/src/main/java/com/google/sps/data/FirestoreDB.java index a5b6fe2..0e36b62 100644 --- a/src/main/java/com/google/sps/data/FirestoreDB.java +++ b/src/main/java/com/google/sps/data/FirestoreDB.java @@ -1,6 +1,7 @@ package com.google.sps.meltingpot.data; import com.google.api.core.ApiFuture; +import com.google.cloud.firestore.CollectionReference; import com.google.cloud.firestore.DocumentReference; import com.google.cloud.firestore.DocumentSnapshot; import com.google.cloud.firestore.FieldValue; @@ -9,11 +10,14 @@ import com.google.cloud.firestore.Transaction; import com.google.cloud.firestore.WriteBatch; import java.util.ArrayList; +import java.util.Calendar; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; public class FirestoreDB implements DBInterface { private static final int RECIPES_PER_PAGE = 12; @@ -251,6 +255,61 @@ public List getRecipesMatchingCreator( return getRecipeMetadataQuery(recipesQuery, sortingMethod); } + // TODO: Currently, sorting method is unused. Used TOP in recipesMatchingAnyTags() for consistent + // order. + public List getRecipesMatchingFollowedTags( + String userId, SortingMethod sortingMethod, int page) { + List followedTagsRecipes = recipesMatchingAnyTags(followedTagIds(userId)); + + // Sort the results. + switch (sortingMethod) { + case TOP: + Collections.sort(followedTagsRecipes, + Collections.reverseOrder(Comparator.comparingLong(RecipeMetadata::getVotes))); + break; + case NEW: + Collections.sort(followedTagsRecipes, + Collections.reverseOrder(Comparator.comparingLong(RecipeMetadata::getTimestamp))); + break; + } + + // Return the appropriate page of recipes manually. + try { + return followedTagsRecipes.subList( + (page * RECIPES_PER_PAGE), ((page + 1) * RECIPES_PER_PAGE)); + } catch (IndexOutOfBoundsException e) { + if (page == 0) { + return followedTagsRecipes; + } else { + if (followedTagsRecipes.size() <= (page * RECIPES_PER_PAGE)) { + return null; + } else { + return ( + followedTagsRecipes.subList((page * RECIPES_PER_PAGE), followedTagsRecipes.size())); + } + } + } + } + + public List getRecipesMatchingFollowedTags( + String userId, SortingMethod sortingMethod) { + List followedTagsRecipes = recipesMatchingAnyTags(followedTagIds(userId)); + + // Sort the results. + switch (sortingMethod) { + case TOP: + Collections.sort(followedTagsRecipes, + Collections.reverseOrder(Comparator.comparingLong(RecipeMetadata::getVotes))); + break; + case NEW: + Collections.sort(followedTagsRecipes, + Collections.reverseOrder(Comparator.comparingLong(RecipeMetadata::getTimestamp))); + break; + } + + return followedTagsRecipes; + } + public List getRecipesSavedBy( String userId, SortingMethod sortingMethod, int page) { List saved_Ids = savedRecipeIds(userId); @@ -289,6 +348,8 @@ private List getRecipeMetadataQuery( case NEW: recipesQuery = recipesQuery.orderBy(Recipe.TIMESTAMP_KEY, Query.Direction.DESCENDING); break; + case NONE: + break; } recipesQuery = recipesQuery.limit(MAX_RECIPES_PER_REQUEST); @@ -313,6 +374,8 @@ private List getRecipeMetadataQuery( case NEW: recipesQuery = recipesQuery.orderBy(Recipe.TIMESTAMP_KEY, Query.Direction.DESCENDING); break; + case NONE: + break; } recipesQuery = recipesQuery.offset(page * RECIPES_PER_PAGE).limit(RECIPES_PER_PAGE); @@ -334,6 +397,28 @@ public Query recipesMatchingTags(Iterable tagIds, Iterator iter) return DBUtils.recipeMetadata(); } + // todo: add to interface (returns list of recipeIDs matching any of the tags) + public List recipesMatchingAnyTags(List tagIds) { + CollectionReference recipes = DBUtils.recipeMetadata(); + Set metadata = new HashSet(); + // Testing out this line + List taggedRecipes = getRecipeMetadataQuery(recipes.whereArrayContainsAny("tagIdsArray", tagIds), SortingMethod.NONE); + //for (String tagId : tagIds) { + // Get only relevant recipes from the last week. + // Calendar calendar = Calendar.getInstance(); + // calendar.add(Calendar.WEEK_OF_YEAR, -1); + //long oneWeekAgo = + // calendar.getTime().getTime(); // in millis. Calendar.getTime() returns a Date. + // metadata.addAll( + // getRecipeMetadataQuery(recipes.whereEqualTo("tagIds." + tagId, true) + // .whereGreaterThanOrEqualTo("timestamp", oneWeekAgo), + // SortingMethod.NONE)); + // } + //List taggedRecipes = new ArrayList(metadata); + //taggedRecipes.addAll(metadata); + return taggedRecipes; + } + public List savedRecipeIds(String userId) { DocumentReference userRef = DBUtils.user(userId); DocumentSnapshot user = DBUtils.blockOnFuture(userRef.get()); diff --git a/src/main/java/com/google/sps/data/RecipeMetadata.java b/src/main/java/com/google/sps/data/RecipeMetadata.java index a1cbfbe..b2e3eb8 100644 --- a/src/main/java/com/google/sps/data/RecipeMetadata.java +++ b/src/main/java/com/google/sps/data/RecipeMetadata.java @@ -1,6 +1,7 @@ package com.google.sps.meltingpot.data; import com.google.cloud.firestore.GeoPoint; +import java.util.List; import java.util.Map; public class RecipeMetadata extends DBObject { @@ -10,6 +11,7 @@ public class RecipeMetadata extends DBObject { public static final String VOTES_KEY = "votes"; public static final String TIMESTAMP_KEY = "timestamp"; public static final String TAG_IDS_KEY = "tagIds"; + public static final String TAG_IDS_ARRAY = "tagIdsArray"; public String title; public String creatorId; @@ -17,7 +19,8 @@ public class RecipeMetadata extends DBObject { public String imageUrl; public long timestamp; public Map tagIds; - public long votes; + public List tagIdsArray; + public long votes; public GeoPoint location; public RecipeMetadata() { diff --git a/src/main/java/com/google/sps/servlets/RecipeServlet.java b/src/main/java/com/google/sps/servlets/RecipeServlet.java index 792ec65..6489557 100644 --- a/src/main/java/com/google/sps/servlets/RecipeServlet.java +++ b/src/main/java/com/google/sps/servlets/RecipeServlet.java @@ -90,7 +90,8 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr if (uid == null) { return; } - + + newRecipe.metadata.tagIdsArray = new ArrayList(newRecipe.metadata.tagIds.keySet()); newRecipe.metadata.creatorId = uid; newRecipe.metadata.votes = 0; newRecipe.metadata.timestamp = System.currentTimeMillis(); @@ -162,12 +163,25 @@ protected String getRecipeList(HttpServletRequest request, HttpServletResponse r } String tagIDs[] = request.getParameterValues("tagIDs"); + // TODO: change sorting method tags to parameter tags + boolean isFollowedTagsRequest = Boolean.parseBoolean(request.getParameter("followed-tags")); boolean isSavedRequest = Boolean.parseBoolean(request.getParameter("saved")); boolean isTagQuery = (tagIDs != null && tagIDs.length > 0 && !tagIDs[0].equals("None")); boolean isCreatorQuery = (creatorToken != null && !creatorToken.equals("None")); + boolean isFollowedTagsQuery = (isCreatorQuery && isFollowedTagsRequest); + + if (isFollowedTagsQuery) { + // If the front end is requesting recipes tagged with the tags that a certain user follows, + // then perform that query + String uid = Auth.getUid(creatorToken, response); + if (uid == null) { + return null; + } - if (isSavedRequest || (isCreatorQuery && !isTagQuery)) { + return gson.toJson(page != null ? db.getRecipesMatchingFollowedTags(uid, sortingMethod, page) + : db.getRecipesMatchingFollowedTags(uid, sortingMethod)); + } else if (isSavedRequest || (isCreatorQuery && !isTagQuery)) { // If frontend is requesting saved recipes or created recipes of a given user, // make sure they are authenticated String uid = Auth.getUid(creatorToken, response); diff --git a/src/main/react/src/components/SortTypeSelect.js b/src/main/react/src/components/SortTypeSelect.js new file mode 100644 index 0000000..44fdbc5 --- /dev/null +++ b/src/main/react/src/components/SortTypeSelect.js @@ -0,0 +1,33 @@ +import React from "react"; +import PropTypes from "prop-types"; +import Select from "react-select"; + +const SortTypeSelect = props => { + const options = [ + { value: "NEW", label: "New" }, + { value: "TOP", label: "Top of last week" }, + ]; + + return ( +