Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,9 @@ public ResponseEntity<List<PostDTO>> getPostsByUserId(@PathVariable Long userId)
}

@PostMapping("/{id}/rate")
public ResponseEntity<Void> ratePost(
public ResponseEntity<Double> 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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Rating, Long> {}
public interface RatingRepository extends CrudRepository<Rating, Long> {
Optional<Rating> findByPostAndUser(Post post, User user);

@Query("SELECT AVG(r.stars) FROM Rating r WHERE r.post.id = :postId")
Double calculateAverageStarsForPost(@Param("postId") Long postId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,26 @@ public List<PostDTO> 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) {
Expand Down
40 changes: 23 additions & 17 deletions frontend/src/components/posts/PostItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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("");
Expand All @@ -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);
Expand All @@ -92,7 +93,7 @@ const PostItem = ({ post }) => {
<Avatar.Root
size={"lg"}
mb={4}
pos={"relative"}
pos="relative"
_after={{
content: '""',
w: 5,
Expand Down Expand Up @@ -122,6 +123,11 @@ const PostItem = ({ post }) => {
<Text fontSize="lg" fontWeight="semibold" mb={1} color="black">
{post?.title || "Greska u post titleu"}
</Text>

<Text fontSize="sm" color="gray.600" mb={2}>
Average Rating: {averageRating?.toFixed(1)} / 5
</Text>

<Text fontSize="md" color="gray.700" mb={4}>
{post?.content || "Greska u post contentu"}
</Text>
Expand Down Expand Up @@ -149,7 +155,7 @@ const PostItem = ({ post }) => {
</Text>
<HStack spacing={1}>
{[1, 2, 3, 4, 5].map((val) =>
val <= rating ? (
val <= Math.round(averageRating) ? (
<FaStar
key={val}
color="gold"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/services/postsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export const ratePost = async (id, stars) => {
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) {
Expand Down