diff --git a/backend/internal/service/server.go b/backend/internal/service/server.go index 4e0fa789..55786552 100644 --- a/backend/internal/service/server.go +++ b/backend/internal/service/server.go @@ -99,6 +99,8 @@ func setupRoutes(app *fiber.App, repo *storage.Repository, config config.Config) // Get Reviews by ID which can be used to populate a preview r.Get("/:id", reviewHandler.GetReviewByID) r.Get("/media/:mediaId/:userID", reviewHandler.GetUserReviewsOfMedia) + r.Get("/media/:mediaId/:userID/following", reviewHandler.GetUserFollowingReviewsOfMedia) + r.Get("/user/:id", reviewHandler.GetReviewsByUserID) r.Post("/vote", func(c *fiber.Ctx) error { return reviewHandler.UserVote(c, "review") diff --git a/backend/internal/storage/postgres/schema/review.go b/backend/internal/storage/postgres/schema/review.go index 37550300..f8e09082 100644 --- a/backend/internal/storage/postgres/schema/review.go +++ b/backend/internal/storage/postgres/schema/review.go @@ -406,6 +406,135 @@ func (r *ReviewRepository) GetUserReviewsOfMedia(ctx context.Context, media_type } +func (r *ReviewRepository) GetUserFollowingReviewsOfMedia(ctx context.Context, media_type string, mediaID string, userID string) ([]*models.Preview, error) { + query := ` + SELECT + r.id, + r.user_id, + u.username, + u.display_name, + u.profile_picture, + r.media_type, + r.media_id, + r.rating, + r.title, + r.comment, + r.created_at, + r.updated_at, + COALESCE(a.cover, t.cover) AS media_cover, + COALESCE(a.title, t.title) AS media_title, + COALESCE(a.artists, t.artists) AS media_artist, + ARRAY_AGG(tag.name) FILTER (WHERE tag.name IS NOT NULL) AS tags + FROM review r + INNER JOIN "user" u ON u.id = r.user_id + LEFT JOIN ( + SELECT t.title, t.id, STRING_AGG(ar.name, ', ') AS artists, cover + FROM track t + JOIN track_artist ta on t.id = ta.track_id + JOIN artist ar ON ta.artist_id = ar.id + JOIN album a on t.album_id = a.id + GROUP BY t.id, cover, t.title + ) t ON r.media_type = 'track' AND r.media_id = t.id + LEFT JOIN ( + SELECT a.id, a.title, STRING_AGG(ar.name, ', ') AS artists, cover + FROM album a + JOIN album_artist aa on a.id = aa.album_id + JOIN artist ar ON aa.artist_id = ar.id + GROUP BY a.id, cover, a.title + ) a ON r.media_type = 'album' AND r.media_id = a.id + LEFT JOIN review_tag rt ON r.id = rt.review_id + LEFT JOIN tag tag ON rt.tag_id = tag.id + LEFT JOIN ( + SELECT post_id as review_id, COUNT(*) AS vote_count + FROM user_vote + WHERE post_type = 'review' + GROUP BY post_id + ) v ON r.id = v.review_id + WHERE r.media_id = $2 AND r.media_type = $3 AND r.user_id IN ( + SELECT followee_id FROM follower WHERE follower_id = $1 + ) + GROUP BY r.id, r.user_id, u.username, u.display_name, u.profile_picture, r.media_type, r.media_id, r.rating, r.comment, r.created_at, r.updated_at, media_cover, media_title, media_artist, v.vote_count + ` + + rows, err := r.Query(ctx, query, userID, mediaID, media_type) + + if err != nil { + fmt.Println(err) + return nil, err + } + defer rows.Close() + + var previews []*models.Preview + + // Scan results into the feedPosts slice + for rows.Next() { + var preview models.Preview + var title, comment sql.NullString // Use sql.NullString for nullable strings + err := rows.Scan( + &preview.ReviewID, + &preview.UserID, + &preview.Username, + &preview.DisplayName, + &preview.ProfilePicture, + &preview.MediaType, + &preview.MediaID, + &preview.Rating, + &title, + &comment, + &preview.CreatedAt, + &preview.UpdatedAt, + &preview.MediaCover, + &preview.MediaTitle, + &preview.MediaArtist, + &preview.Tags, + ) + if err != nil { + fmt.Println(err) + return nil, err + } + + // Assign comment to feedPost.Comment, handling null case + if comment.Valid { + preview.Comment = &comment.String // Point to the string if valid + } else { + preview.Comment = nil // Set to nil if null + } + + if title.Valid { + preview.Title = &title.String // Point to the string if valid + } else { + preview.Title = nil // Set to nil if null + } + + // Ensure tags is an empty array if null + if preview.Tags == nil { + preview.Tags = []string{} + } + + // Fetch review statistics for the current review + reviewStat, err := r.GetReviewStats(ctx, strconv.Itoa(preview.ReviewID)) + if err != nil { + return nil, err + } + + // If reviewStat is not nil, populate the corresponding fields in FeedPost + if reviewStat != nil { + preview.ReviewStat = *reviewStat + } + + // Append the populated FeedPost to the feedPosts slice + previews = append(previews, &preview) + } + + // Check for errors after looping through rows + if err := rows.Err(); err != nil { + return nil, err + } + + return previews, nil + +} + func (r *ReviewRepository) GetReviewsByUserID(ctx context.Context, id string) ([]*models.Review, error) { rows, err := r.Query(ctx, "SELECT id, user_id, media_id, media_type, rating, title, comment, created_at, updated_at, draft FROM review WHERE user_id = $1 ORDER BY updated_at DESC", id) diff --git a/backend/internal/storage/storage.go b/backend/internal/storage/storage.go index ab26a724..584ffe3a 100644 --- a/backend/internal/storage/storage.go +++ b/backend/internal/storage/storage.go @@ -40,6 +40,7 @@ type UserRepository interface { type ReviewRepository interface { GetUserReviewsOfMedia(ctx context.Context, media_type string, mediaID string, userID string) ([]*models.Preview, error) + GetUserFollowingReviewsOfMedia(ctx context.Context, media_type string, mediaID string, userID string) ([]*models.Preview, error) GetReviewsByUserID(ctx context.Context, id string) ([]*models.Review, error) CreateReview(ctx context.Context, review *models.Review) (*models.Review, error) ReviewExists(ctx context.Context, id string) (bool, error) diff --git a/frontend/app/MediaReviewsPage.tsx b/frontend/app/MediaReviewsPage.tsx index 961306ac..2decb717 100644 --- a/frontend/app/MediaReviewsPage.tsx +++ b/frontend/app/MediaReviewsPage.tsx @@ -46,7 +46,7 @@ const MediaReviewsPage = () => { const fetchAll = async () => { try { const response = await axios.get( - `${BASE_URL}/reviews/${media_type}/${media_id}`, + `${BASE_URL}/reviews/${media_type}/${media_id}` ); setAllReviews(response.data.reviews); @@ -78,7 +78,7 @@ const MediaReviewsPage = () => { params: { media_type: media_type, }, - }, + } ); const reviews = response.data; @@ -87,7 +87,7 @@ const MediaReviewsPage = () => { // Calculate the average score const totalScore = response.data.reduce( (sum: any, review: { rating: any }) => sum + review.rating, - 0, + 0 ); // Sum of all ratings const averageScore = reviews.length > 0 ? totalScore / reviews.length : 0; // Avoid division by 0 @@ -110,25 +110,27 @@ const MediaReviewsPage = () => { params: { media_type: media_type, }, - }, + } ); const reviews = response.data; - setFriendsReviews(reviews); + if (reviews) { + setFriendsReviews(reviews); - // Calculate the average score - const totalScore = reviews.reduce( - (sum: any, review: { rating: any }) => sum + review.rating, - 0, - ); // Sum of all ratings - const averageScore = - reviews.length > 0 ? totalScore / reviews.length : 0; // Avoid division by 0 + // Calculate the average score + const totalScore = reviews.reduce( + (sum: any, review: { rating: any }) => sum + review.rating, + 0 + ); // Sum of all ratings + const averageScore = + reviews.length > 0 ? totalScore / reviews.length : 0; // Avoid division by 0 - // Update userScore in mediaStats - setMediaStats((prevStats) => ({ - ...prevStats, - userScore: averageScore, - })); + // Update userScore in mediaStats + setMediaStats((prevStats) => ({ + ...prevStats, + friendScore: averageScore, + })); + } } catch (error) { console.error(error); } diff --git a/frontend/components/media/FriendRatings.tsx b/frontend/components/media/FriendRatings.tsx index 740fb8a3..3a24ae9c 100644 --- a/frontend/components/media/FriendRatings.tsx +++ b/frontend/components/media/FriendRatings.tsx @@ -18,9 +18,9 @@ const FriendRatings = ({ media_id, media_type }: FriendRatingsProps) => { useEffect(() => { axios - .get(`${BASE_URL}/reviews/social/${media_type}/${media_id}`, { - data: { - userid: userId, + .get(`${BASE_URL}/reviews/media/${media_id}/${userId}`, { + params: { + media_type: media_type, }, }) .then((response) => setFriendsReviews(response.data))