From 55bff4055644a53d989a23eca0243d86d3cb1792 Mon Sep 17 00:00:00 2001 From: Josip Date: Sat, 28 Jun 2025 04:43:28 +0200 Subject: [PATCH] Implement posts filtering based on friendship --- .../controller/PostController.java | 9 ++ .../repository/PostRepository.java | 9 ++ .../socialnetwork/service/PostService.java | 19 +++ frontend/src/App.jsx | 8 + frontend/src/components/layout/Navbar.jsx | 1 + frontend/src/pages/FriendsPage.jsx | 139 ++++++++++++++++++ frontend/src/pages/HomePage.jsx | 70 ++++++--- frontend/src/services/postsService.js | 11 ++ 8 files changed, 246 insertions(+), 20 deletions(-) create mode 100644 frontend/src/pages/FriendsPage.jsx diff --git a/backend/src/main/java/hr/algebra/socialnetwork/controller/PostController.java b/backend/src/main/java/hr/algebra/socialnetwork/controller/PostController.java index ab1b3b2..87bb6df 100644 --- a/backend/src/main/java/hr/algebra/socialnetwork/controller/PostController.java +++ b/backend/src/main/java/hr/algebra/socialnetwork/controller/PostController.java @@ -32,6 +32,15 @@ public ResponseEntity> getAllPosts( return ResponseEntity.ok(postService.getAllPosts(pageable)); } + @GetMapping("/friends") + public ResponseEntity> getFriendsPosts( + Principal principal, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending()); + return ResponseEntity.ok(postService.getFriendsPosts(principal.getName(), pageable)); + } + @GetMapping("/{id}") public ResponseEntity getPostById(@PathVariable Long id) { return ResponseEntity.ok(postService.getPostById(id)); diff --git a/backend/src/main/java/hr/algebra/socialnetwork/repository/PostRepository.java b/backend/src/main/java/hr/algebra/socialnetwork/repository/PostRepository.java index 01d6240..c70f87d 100644 --- a/backend/src/main/java/hr/algebra/socialnetwork/repository/PostRepository.java +++ b/backend/src/main/java/hr/algebra/socialnetwork/repository/PostRepository.java @@ -1,8 +1,11 @@ package hr.algebra.socialnetwork.repository; import hr.algebra.socialnetwork.model.Post; +import hr.algebra.socialnetwork.model.User; import java.util.List; import java.util.Optional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -32,4 +35,10 @@ SELECT COUNT(p) > 0 FROM Post p SELECT p FROM Post p """) List findAll(); + + @Query(""" + SELECT p FROM Post p + WHERE p.user IN :friends + """) + Page findAllByUserIn(@Param("friends") List friends, Pageable pageable); } diff --git a/backend/src/main/java/hr/algebra/socialnetwork/service/PostService.java b/backend/src/main/java/hr/algebra/socialnetwork/service/PostService.java index 81ccdbe..1e0e9a4 100644 --- a/backend/src/main/java/hr/algebra/socialnetwork/service/PostService.java +++ b/backend/src/main/java/hr/algebra/socialnetwork/service/PostService.java @@ -18,7 +18,9 @@ import hr.algebra.socialnetwork.s3.S3Service; import java.io.IOException; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -211,4 +213,21 @@ public byte[] getPostImage(Long postId) { String key = "post-images/%s/%s.jpg".formatted(postId, post.getImageId()); return s3Service.getObject(key); } + + public Page getFriendsPosts(String requesterEmail, Pageable pageable) { + User requester = + userRepository + .findByEmail(requesterEmail) + .orElseThrow(() -> new ResourceNotFoundException("User not found: " + requesterEmail)); + + Set friendSet = requester.getFriends(); + if (friendSet.isEmpty()) { + return Page.empty(pageable); + } + + List friends = new ArrayList<>(friendSet); + + Page posts = postRepository.findAllByUserIn(friends, pageable); + return posts.map(postDTOMapper); + } } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index c0b6ee3..88ef1d3 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -41,6 +41,14 @@ function App() { } /> + + + + } + /> { + const [requests, setRequests] = useState([]); + const [loading, setLoading] = useState(true); + + const fetchRequests = async () => { + try { + const res = await getPendingFriendRequests(); + setRequests(res.data || []); + } catch (err) { + alert("Error fetching requests."); + } finally { + setLoading(false); + } + }; + + const handleApprove = async (id) => { + try { + await approveFriendRequest(id); + alert("Friend request approved."); + fetchRequests(); + } catch (err) { + alert("Error approving request."); + } + }; + + const handleDecline = async (id) => { + try { + await declineFriendRequest(id); + alert("Friend request declined."); + fetchRequests(); + } catch (err) { + alert("Error declining request."); + } + }; + + useEffect(() => { + fetchRequests(); + }, []); + + if (loading) return ; + + return ( + <> + + + + + + + + + + Pending Friend Requests + + + {requests.length === 0 ? ( + No pending friend requests + ) : ( + requests.map((req) => ( + + + + {req.senderFullName || "Unknown User"} + + + + + + + + )) + )} + + + + + + ); +}; + +export default RequestsPage; diff --git a/frontend/src/pages/HomePage.jsx b/frontend/src/pages/HomePage.jsx index 4bb9aea..edd15ee 100644 --- a/frontend/src/pages/HomePage.jsx +++ b/frontend/src/pages/HomePage.jsx @@ -1,17 +1,20 @@ import React, { useEffect, useState } from "react"; -import { Flex, Box } from "@chakra-ui/react"; +import { Flex, Box, Button, HStack } from "@chakra-ui/react"; import Navbar from "../components/layout/Navbar.jsx"; import Sidebar from "../components/layout/Sidebar.jsx"; import PostFeed from "../components/posts/PostFeed.jsx"; import PostItem from "../components/posts/PostItem.jsx"; -import { getAllPosts } from "../services/postsService.js"; +import { getAllPosts, getFriendsPosts } from "../services/postsService.js"; function HomePage() { const [posts, setPosts] = useState([]); + const [showFriendsPosts, setShowFriendsPosts] = useState(false); const fetchPosts = async () => { try { - const response = await getAllPosts(); + const response = showFriendsPosts + ? await getFriendsPosts() + : await getAllPosts(); setPosts(response?.data?.content || []); } catch (err) { console.error("Failed to load posts:", err); @@ -20,7 +23,14 @@ function HomePage() { useEffect(() => { fetchPosts(); - }, []); + }, [showFriendsPosts]); + + const handlePostCreated = () => { + fetchPosts(); + }; + + const activeButtonBg = "#3182CE"; + const inactiveButtonBg = "#EDF2F7"; return ( <> @@ -46,24 +56,44 @@ function HomePage() { - - + + {/* Post Feed (Create new post) */} + + + + + {/* Toggle Buttons */} + + + + + + {/* Posts Feed */} {posts.map((post) => ( diff --git a/frontend/src/services/postsService.js b/frontend/src/services/postsService.js index 30581db..7fffcb1 100644 --- a/frontend/src/services/postsService.js +++ b/frontend/src/services/postsService.js @@ -19,6 +19,17 @@ export const getAllPosts = async (page = 0, size = 10) => { } }; +export const getFriendsPosts = async (page = 0, size = 10) => { + try { + return await axios.get( + `${API_BASE}/api/v1/posts/friends?page=${page}&size=${size}`, + getAuthConfig(), + ); + } catch (e) { + console.error(`Error: ${e}`); + } +}; + export const getPostById = async (id) => { try { return await axios.get(`${API_BASE}/api/v1/posts/${id}`, getAuthConfig());