Skip to content
3 changes: 3 additions & 0 deletions FEATURETHON.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Featurethon

<!-- List your progress below as you go! -->
7 changes: 7 additions & 0 deletions backend/internal/handlers/users/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,18 @@ func Routes(app *fiber.App, collections map[string]*mongo.Collection) {
user.Get("/:id", handler.GetUserById)

user.Get("/followers", handler.GetFollowers)
user.Get("/:id/following", handler.GetFollowing)
user.Post("/follow", handler.FollowUser)
user.Delete("/follow", handler.UnfollowUser)

// Item review endpoints
item := apiV1.Group("/item")
item.Get("/:id/followReviews", handler.GetFollowingReviewsForItem)
item.Get("/:id/friendReviews", handler.GetFriendReviewsForItem)

// User settings
settings := apiV1.Group("/settings")
settings.Get("/:id/dietaryPreferences", handler.GetDietaryPreferences)
settings.Post("/:id/dietaryPreferences", handler.PostDietaryPreferences)
settings.Delete("/:id/dietaryPreferences", handler.DeleteDietaryPreferences)
}
143 changes: 143 additions & 0 deletions backend/internal/handlers/users/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package users
import (
"context"
"errors"
"fmt"

"github.com/GenerateNU/platemate/internal/handlers/menu_items"
"github.com/GenerateNU/platemate/internal/handlers/review"
Expand Down Expand Up @@ -134,6 +135,86 @@ func (s *Service) GetUserFollowers(userId string, page, limit int) ([]UserRespon
FollowersCount: follower.FollowersCount,
FollowingCount: follower.FollowingCount,
Reviews: reviews,
Preferences: follower.Preferences,
}
}

return response, nil
}

func (s *Service) GetUserFollowing(userId string, page, limit int) ([]UserResponse, error) {
ctx := context.Background()
fmt.Println((userId))
userObjID, err := primitive.ObjectIDFromHex(userId)
if err != nil {
badReq := xerr.BadRequest(err)
return nil, &badReq
}

// Find the user and get their followers
var user User
err = s.users.FindOne(ctx, bson.M{"_id": userObjID}).Decode(&user)
if err != nil {
return nil, err
}

// Calculate total pages and adjust page number if out of bounds
totalFollowing := len(user.Following)
totalPages := (totalFollowing + limit - 1) / limit // Ceiling division

if totalPages == 0 {
return []UserResponse{}, nil
}

// Adjust page to be within bounds
if page > totalPages {
page = totalPages
}
if page < 1 {
page = 1
}

// Calculate pagination bounds
skip := (page - 1) * limit
end := skip + limit
if end > totalFollowing {
end = totalFollowing
}

// Get the slice of follower IDs for this page, could be an issue if the value is 0
pageFollowers := user.Following[skip:end]

// Fetch the actual user documents for these followers
cursor, err := s.users.Find(ctx, bson.M{
"_id": bson.M{"$in": pageFollowers},
})
if err != nil {
return nil, err
}
defer cursor.Close(ctx)

var following []User
if err = cursor.All(ctx, &following); err != nil {
return nil, err
}

// Convert to response format
response := make([]UserResponse, len(following))
for i, followingUser := range following {
reviews := make([]string, len(followingUser.Reviews))
for j, reviewID := range followingUser.Reviews {
reviews[j] = reviewID.Hex()
}

response[i] = UserResponse{
ID: followingUser.ID.Hex(),
Name: followingUser.Name,
Username: followingUser.Username,
ProfilePicture: followingUser.ProfilePicture,
FollowersCount: followingUser.FollowersCount,
FollowingCount: followingUser.FollowingCount,
Reviews: reviews,
Preferences: followingUser.Preferences,
}
}

Expand Down Expand Up @@ -412,3 +493,65 @@ func (s *Service) GetFriendReviewsForItem(userObjID primitive.ObjectID, menuItem
return reviews, nil

}

func (s *Service) GetDietaryPreferences(userId string) ([]string, error) {
ctx := context.Background()
userObjID, err := primitive.ObjectIDFromHex(userId)
if err != nil {
badReq := xerr.BadRequest(err)
return nil, &badReq
}

// Find the user and get their followers
var user User
err = s.users.FindOne(ctx, bson.M{"_id": userObjID}).Decode(&user)
if err != nil {
return nil, err
}

dietaryRestrictions := user.Preferences

return dietaryRestrictions, nil
}

func (s *Service) PostDietaryPreferences(userId string, preference string) error {
ctx := context.Background()
userObjID, err := primitive.ObjectIDFromHex(userId)
if err != nil {
badReq := xerr.BadRequest(err)
return &badReq
}

update := bson.M{
"$push": bson.M{"preferences": preference},
}

// Update the user's dietary preferences in the database
_, err = s.users.UpdateOne(ctx, bson.M{"_id": userObjID}, update)
if err != nil {
return err
}

return nil
}

func (s *Service) DeleteDietaryPreferences(userId string, preference string) error {
ctx := context.Background()
userObjID, err := primitive.ObjectIDFromHex(userId)
if err != nil {
badReq := xerr.BadRequest(err)
return &badReq
}

delete := bson.M{
"$pull": bson.M{"preferences": preference},
}

// Update the user's dietary preferences in the database
_, err = s.users.UpdateOne(ctx, bson.M{"_id": userObjID}, delete)
if err != nil {
return err
}

return nil
}
11 changes: 11 additions & 0 deletions backend/internal/handlers/users/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type User struct {
FollowersCount int `bson:"followersCount"`
ProfilePicture string `bson:"profile_picture,omitempty"`
Name string `bson:"name,omitempty"`
Preferences []string `bson:"preferences,omitempty"`
}

type UserResponse struct {
Expand All @@ -26,6 +27,7 @@ type UserResponse struct {
FollowingCount int `json:"followingCount"`
Reviews []string `json:"reviews,omitempty"`
Name string `json:"name,omitempty"`
Preferences []string `json:"preferences,omitempty"`
}

type FollowRequest struct {
Expand All @@ -47,7 +49,16 @@ type GetFollowersQuery struct {
UserId string `query:"userId" validate:"required"`
}

type GetFollowingQuery struct {
PaginationQuery
UserId string `query:"userId" validate:"required"`
}

type ReviewQuery struct {
UserId string `query:"userId" validate:"required"`
ItemId string `params:"id" validate:"required"`
}

type PostDietaryPreferencesQuery struct {
Preference string `json:"preference"`
}
69 changes: 69 additions & 0 deletions backend/internal/handlers/users/user_connections.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,30 @@ func (h *Handler) GetFollowers(c *fiber.Ctx) error {
return c.JSON(followers)
}

// GetFollowing returns a paginated list of who the user is following
func (h *Handler) GetFollowing(c *fiber.Ctx) error {
var query GetFollowingQuery
userId := c.Params("id")

// Set defaults if not provided
if query.Page < 1 {
query.Page = 1
}
if query.Limit < 1 {
query.Limit = 20
}

followers, err := h.service.GetUserFollowing(userId, query.Page, query.Limit)
if err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
return c.Status(fiber.StatusNotFound).JSON(xerr.NotFound("User", "id", query.UserId))
}
return err
}

return c.JSON(followers)
}

// GetFollowingReviewsForItem gets reviews for a menu item from users that the current user follows
func (h *Handler) GetFollowingReviewsForItem(c *fiber.Ctx) error {

Expand Down Expand Up @@ -180,3 +204,48 @@ func (h *Handler) UnfollowUser(c *fiber.Ctx) error {

return c.SendStatus(fiber.StatusNoContent)
}

// GetDietaryPreferences retrieves the dietary preferences of a user
func (h *Handler) GetDietaryPreferences(c *fiber.Ctx) error {
userId := c.Params("id")

preferences, err := h.service.GetDietaryPreferences(userId)
if err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
return c.Status(fiber.StatusNotFound).JSON(xerr.NotFound("User", "id", userId))
}
return err
}

return c.JSON(preferences)
}

func (h *Handler) PostDietaryPreferences(c *fiber.Ctx) error {
userId := c.Params("id")
preference := c.Query("preference")

err := h.service.PostDietaryPreferences(userId, preference)
if err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
return c.Status(fiber.StatusNotFound).JSON(xerr.NotFound("User", "id", "specified"))
}
return err
}

return c.SendStatus(fiber.StatusCreated)
}

func (h *Handler) DeleteDietaryPreferences(c *fiber.Ctx) error {
userId := c.Params("id")
preference := c.Query("preference")

err := h.service.DeleteDietaryPreferences(userId, preference)
if err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
return c.Status(fiber.StatusNotFound).JSON(xerr.NotFound("User", "id", "specified"))
}
return err
}

return c.SendStatus(fiber.StatusCreated)
}
Loading