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 87bb6df..3966010 100644 --- a/backend/src/main/java/hr/algebra/socialnetwork/controller/PostController.java +++ b/backend/src/main/java/hr/algebra/socialnetwork/controller/PostController.java @@ -71,10 +71,9 @@ public ResponseEntity> getPostsByUserId(@PathVariable Long userId) } @PostMapping("/{id}/rate") - public ResponseEntity ratePost( + public ResponseEntity ratePost( @PathVariable Long id, Principal principal, @RequestParam int stars) { - postService.ratePost(id, principal.getName(), stars); - return ResponseEntity.ok().build(); + return ResponseEntity.ok(postService.ratePost(id, principal.getName(), stars)); } @PostMapping("/{id}/comments") diff --git a/backend/src/main/java/hr/algebra/socialnetwork/repository/RatingRepository.java b/backend/src/main/java/hr/algebra/socialnetwork/repository/RatingRepository.java index d28456e..bf98a45 100644 --- a/backend/src/main/java/hr/algebra/socialnetwork/repository/RatingRepository.java +++ b/backend/src/main/java/hr/algebra/socialnetwork/repository/RatingRepository.java @@ -1,8 +1,18 @@ package hr.algebra.socialnetwork.repository; +import hr.algebra.socialnetwork.model.Post; import hr.algebra.socialnetwork.model.Rating; +import hr.algebra.socialnetwork.model.User; +import java.util.Optional; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository -public interface RatingRepository extends CrudRepository {} +public interface RatingRepository extends CrudRepository { + Optional findByPostAndUser(Post post, User user); + + @Query("SELECT AVG(r.stars) FROM Rating r WHERE r.post.id = :postId") + Double calculateAverageStarsForPost(@Param("postId") Long postId); +} 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 1e0e9a4..8025281 100644 --- a/backend/src/main/java/hr/algebra/socialnetwork/service/PostService.java +++ b/backend/src/main/java/hr/algebra/socialnetwork/service/PostService.java @@ -62,14 +62,26 @@ public List getPostsByUserId(Long userId) { return postRepository.findAllByUserId(userId).stream().map(postDTOMapper).toList(); } - public void ratePost(Long postId, String email, int stars) { + public double ratePost(Long postId, String email, int stars) { validateStars(stars); Post post = findPostById(postId); User user = findUserByEmail(email); - Rating rating = buildRating(post, user, stars); + + Rating rating = + ratingRepository + .findByPostAndUser(post, user) + .map( + existing -> { + existing.setStars(stars); + existing.setUpdatedAt(LocalDateTime.now()); + return existing; + }) + .orElseGet(() -> buildRating(post, user, stars)); ratingRepository.save(rating); + + return post.getAverageRating(); } public void commentOnPost(Long postId, String email, String content) { diff --git a/frontend/src/components/posts/PostItem.jsx b/frontend/src/components/posts/PostItem.jsx index 52fba7f..af0defe 100644 --- a/frontend/src/components/posts/PostItem.jsx +++ b/frontend/src/components/posts/PostItem.jsx @@ -19,19 +19,24 @@ import { import CommentItem from "./CommentItem"; const PostItem = ({ post }) => { - const [rating, setRating] = useState(0); + const [averageRating, setAverageRating] = useState(post.averageRating || 0); const [comment, setComment] = useState(""); const [comments, setComments] = useState([]); const UID = getUID(); - const handleRating = (value) => { - ratePost(post.id, value); - setRating(value); + const handleRating = async (value) => { + try { + const response = await ratePost(post.id, value); + if (typeof response.data === "number") { + setAverageRating(response.data); + } + } catch (e) { + console.error("Rating error:", e); + } }; const handleAddComment = async () => { if (!comment.trim()) return; - try { await commentOnPost(post.id, comment); setComment(""); @@ -42,33 +47,29 @@ const PostItem = ({ post }) => { setComments(updatedComments); } } catch (e) { - console.log("Comment error:", e); + console.error("Comment error:", e); } }; const handleDeletePost = async () => { - console.log("Post: " + post.id); try { await deletePostById(post.id); } catch (e) { - console.log("Failed to delete post. " + e); + console.error("Failed to delete post:", e); } }; useEffect(() => { - setRating(post.averageRating); - const fetchComments = async () => { try { const response = await getCommentsForPost(post.id); - const comments = response.data; + const fetchedComments = response.data; if ( - Array.isArray(comments) && - comments.length > 0 && - comments.some((c) => Object.keys(c).length > 0) + Array.isArray(fetchedComments) && + fetchedComments.some((c) => Object.keys(c).length > 0) ) { - setComments(comments); + setComments(fetchedComments); } } catch (e) { console.error("Error fetching comments:", e); @@ -92,7 +93,7 @@ const PostItem = ({ post }) => { { {post?.title || "Greska u post titleu"} + + + Average Rating: {averageRating?.toFixed(1)} / 5 + + {post?.content || "Greska u post contentu"} @@ -149,7 +155,7 @@ const PostItem = ({ post }) => { {[1, 2, 3, 4, 5].map((val) => - val <= rating ? ( + val <= Math.round(averageRating) ? ( { try { return await axios.post( `${API_BASE}/api/v1/posts/${id}/rate?stars=${stars}`, - {}, // empty body, TODO check if recalculated average should be send + {}, getAuthConfig(), ); } catch (e) {