Skip to content
This repository was archived by the owner on Jun 3, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 23 additions & 0 deletions src/main/java/com/google/sps/data/DBInterface.java
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,29 @@ public List<RecipeMetadata> getRecipesMatchingCreator(
public List<RecipeMetadata> 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<RecipeMetadata> 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<RecipeMetadata> getRecipesMatchingFollowedTags(
String userId, SortingMethod sortingMethod);

/**
* Returns a paginated list of recipe metadata associated with the IDs of recipes saved by a user.
*
Expand Down
85 changes: 85 additions & 0 deletions src/main/java/com/google/sps/data/FirestoreDB.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -251,6 +255,61 @@ public List<RecipeMetadata> getRecipesMatchingCreator(
return getRecipeMetadataQuery(recipesQuery, sortingMethod);
}

// TODO: Currently, sorting method is unused. Used TOP in recipesMatchingAnyTags() for consistent
// order.
public List<RecipeMetadata> getRecipesMatchingFollowedTags(
String userId, SortingMethod sortingMethod, int page) {
List<RecipeMetadata> 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) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't you get this exception when you try to get the last page?

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<RecipeMetadata> getRecipesMatchingFollowedTags(
String userId, SortingMethod sortingMethod) {
List<RecipeMetadata> 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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to do sorting, you could add some code like this (does sort on our end without firestore's help):

switch (sortingMethod) {
      case TOP:
        System.out.println("Sorting by: TOP");
        Collections.sort(
            results, Collections.reverseOrder(Comparator.comparingLong(RecipeMetadata::getVotes)));
        break;
      case NEW:
        System.out.println("Sorting by: NEW");
        Collections.sort(results,
            Collections.reverseOrder(Comparator.comparingLong(RecipeMetadata::getTimestamp)));
        break;
    }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, getVotes and getTimestamp are simple getters I added on my branch


public List<RecipeMetadata> getRecipesSavedBy(
String userId, SortingMethod sortingMethod, int page) {
List<String> saved_Ids = savedRecipeIds(userId);
Expand Down Expand Up @@ -289,6 +348,8 @@ private List<RecipeMetadata> getRecipeMetadataQuery(
case NEW:
recipesQuery = recipesQuery.orderBy(Recipe.TIMESTAMP_KEY, Query.Direction.DESCENDING);
break;
case NONE:
break;
}

recipesQuery = recipesQuery.limit(MAX_RECIPES_PER_REQUEST);
Expand All @@ -313,6 +374,8 @@ private List<RecipeMetadata> 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);
Expand All @@ -334,6 +397,28 @@ public Query recipesMatchingTags(Iterable<String> tagIds, Iterator<String> iter)
return DBUtils.recipeMetadata();
}

// todo: add to interface (returns list of recipeIDs matching any of the tags)
public List<RecipeMetadata> recipesMatchingAnyTags(List<String> tagIds) {
CollectionReference recipes = DBUtils.recipeMetadata();
Set<RecipeMetadata> metadata = new HashSet<RecipeMetadata>();
// Testing out this line
List<RecipeMetadata> 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<RecipeMetadata> taggedRecipes = new ArrayList<RecipeMetadata>(metadata);
//taggedRecipes.addAll(metadata);
return taggedRecipes;
}

public List<String> savedRecipeIds(String userId) {
DocumentReference userRef = DBUtils.user(userId);
DocumentSnapshot user = DBUtils.blockOnFuture(userRef.get());
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/com/google/sps/data/RecipeMetadata.java
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -10,14 +11,16 @@ 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;
public String creatorLdap;
public String imageUrl;
public long timestamp;
public Map<String, Boolean> tagIds;
public long votes;
public List<String> tagIdsArray;
public long votes;
public GeoPoint location;

public RecipeMetadata() {
Expand Down
18 changes: 16 additions & 2 deletions src/main/java/com/google/sps/servlets/RecipeServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr
if (uid == null) {
return;
}


newRecipe.metadata.tagIdsArray = new ArrayList<String>(newRecipe.metadata.tagIds.keySet());
newRecipe.metadata.creatorId = uid;
newRecipe.metadata.votes = 0;
newRecipe.metadata.timestamp = System.currentTimeMillis();
Expand Down Expand Up @@ -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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the user wants to sort by new or top for their custom feed? I feel like we should have some other flag to determine whether it's a followedTagsQuery

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);
Expand Down
33 changes: 33 additions & 0 deletions src/main/react/src/components/SortTypeSelect.js
Original file line number Diff line number Diff line change
@@ -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 (
<Select
id="sort-select"
name="sort-select"
isMulti
options={options}
onChange={selected => {
if (selected === null) {
props.setSortType("NONE");
return;
}
props.setSortType(selected.value);
}}
/>
);
};

SortTypeSelect.propTypes = {
sortType: PropTypes.string,
setSortType: PropTypes.func,
};

export default SortTypeSelect;
6 changes: 6 additions & 0 deletions src/main/react/src/containers/_nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ export default [
to: "/new",
icon: "cil-clock",
},
{
_tag: "CSidebarNavItem",
name: "Followed",
to: "/followed",
icon: "cil-tags",
},
{
_tag: "CSidebarNavItem",
name: "Map",
Expand Down
9 changes: 6 additions & 3 deletions src/main/react/src/requests.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,23 @@ import 'firebase/auth';
const requestRoute = "http://localhost:8080/";
const mapsApiKey = "AIzaSyAe5HlFZFuhzMimXrKW1z3kglajbdHf_Rc";

const getRecipes = async (feedType, page, tags) => {
const getRecipes = async (feedType, page, tags, sortFollowed) => {
let qs = requestRoute + "api/post?";
qs += (page ? "page=" + page : "");
qs += ((page || page === 0) ? "page=" + page : "");
if (feedType === "saved") {
let token = await app.auth().currentUser.getIdToken();
qs += "&saved=true&sort=NEW&token=" + token;
} else if (feedType === "created") {
let token = await app.auth().currentUser.getIdToken();
qs += "&sort=NEW&token=" + token;
} else if (feedType === "followed-tags") {
let token = await app.auth().currentUser.getIdToken();
qs += "&followed-tags=true&token=" + token + "&sort=" + sortFollowed;
} else if (feedType === "popular") {
qs += "&sort=TOP";
} else if (feedType === "new") {
qs += "&sort=NEW";
}
}
if (tags) {
let tagsQuery = Object.keys(tags).map(id => "tagIDs=" + id);
qs += "&" + tagsQuery.join("&");
Expand Down
1 change: 1 addition & 0 deletions src/main/react/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const Profile = React.lazy(() => import('./views/Profile'));
const routes = [
{ path: '/', exact: true, name: 'Home' },
{ path: '/popular', name: 'Popular', component: FeedWithSearch, props: { feedType: 'popular' } },
{ path: '/followed', name: 'Followed', component: FeedWithSort, props: { feedType: 'followed-tags'} },
{ path: '/new', name: 'New', component: FeedWithSearch, props: { feedType: 'new' } },
{ path: '/recipe', name: 'Recipe', component: Recipe },
{ path: '/addrecipe', name: 'Add Recipe', component: AddRecipe },
Expand Down
11 changes: 9 additions & 2 deletions src/main/react/src/views/Feed.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ const Feed = props => {

const loadRecipes = useCallback(
async (user, recipePage) => {
let recipeData = await getRecipes(props.feedType, recipePage, props.selectedTags);
let recipeData;
if (props.sortType) {
// sortType only exists for followed tags page
recipeData = await getRecipes(props.feedType, recipePage, {}, props.sortType);
} else {
recipeData = await getRecipes(props.feedType, recipePage, props.selectedTags, props.sortType);
}
if (user) {
let [voteData, savedData] = await Promise.all([getVoteData(recipeData), getSavedData(recipeData)]);
recipeData.forEach((recipe, i) => {
Expand All @@ -40,7 +46,7 @@ const Feed = props => {
}
return recipeData;
},
[props.feedType, props.selectedTags]
[props.feedType, props.selectedTags, props.sortType]
);

const [signedIn, setSignedIn] = useState(false);
Expand Down Expand Up @@ -149,6 +155,7 @@ const Feed = props => {
Feed.propTypes = {
feedType: PropTypes.string,
selectedTags: PropTypes.object,
sortType: PropTypes.string,
};

export default Feed;
28 changes: 28 additions & 0 deletions src/main/react/src/views/FeedWithSort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import { CCol, CRow } from "@coreui/react";
import SortTypeSelect from "../components/SortTypeSelect";
import Feed from "./Feed";

const FeedWithSort = props => {
const [sort, setSort] = useState(null);

return (
<>
<CRow>
<CCol>
<SortTypeSelect sortType={sort} setSortType={setSort} />
</CCol>
</CRow>
<br></br>
<br></br>
<Feed feedType={props.feedType} sortType={sort} />
</>
);
};

FeedWithSort.propTypes = {
feedType: PropTypes.string,
};

export default FeedWithSort;