Skip to content

Commit a7d02cf

Browse files
authored
Merge pull request #498 from bounswe/fix/479-forum-missing-endpoints
fix: forum
2 parents 1f0e932 + f468c3d commit a7d02cf

File tree

9 files changed

+219
-5
lines changed

9 files changed

+219
-5
lines changed

apps/jobboard-backend/src/main/java/org/bounswe/jobboardbackend/forum/controller/ForumController.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,36 @@ public ResponseEntity<Void> deletePost(@PathVariable Long id, @AuthenticationPri
5959
return ResponseEntity.noContent().build();
6060
}
6161

62+
@PostMapping("/posts/{id}/upvote")
63+
public ResponseEntity<Void> upvotePost(@PathVariable Long id, @AuthenticationPrincipal UserDetails userDetails) {
64+
User user = getUser(userDetails);
65+
forumService.upvotePost(id, user);
66+
return ResponseEntity.ok().build();
67+
}
68+
69+
@DeleteMapping("/posts/{id}/upvote")
70+
public ResponseEntity<Void> removePostUpvote(@PathVariable Long id,
71+
@AuthenticationPrincipal UserDetails userDetails) {
72+
User user = getUser(userDetails);
73+
forumService.removePostUpvote(id, user);
74+
return ResponseEntity.ok().build();
75+
}
76+
77+
@PostMapping("/posts/{id}/downvote")
78+
public ResponseEntity<Void> downvotePost(@PathVariable Long id, @AuthenticationPrincipal UserDetails userDetails) {
79+
User user = getUser(userDetails);
80+
forumService.downvotePost(id, user);
81+
return ResponseEntity.ok().build();
82+
}
83+
84+
@DeleteMapping("/posts/{id}/downvote")
85+
public ResponseEntity<Void> removePostDownvote(@PathVariable Long id,
86+
@AuthenticationPrincipal UserDetails userDetails) {
87+
User user = getUser(userDetails);
88+
forumService.removePostDownvote(id, user);
89+
return ResponseEntity.ok().build();
90+
}
91+
6292
@PostMapping("/posts/{id}/comments")
6393
public ResponseEntity<CommentResponse> createComment(@PathVariable Long id,
6494
@AuthenticationPrincipal UserDetails userDetails,

apps/jobboard-backend/src/main/java/org/bounswe/jobboardbackend/forum/dto/PostResponse.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@ public class PostResponse {
1919
private Instant createdAt;
2020
private Instant updatedAt;
2121
private int commentCount;
22+
private long upvoteCount;
23+
private long downvoteCount;
24+
private List<CommentResponse> comments;
2225

23-
public static PostResponse from(ForumPost post) {
26+
public static PostResponse from(ForumPost post, long upvoteCount, long downvoteCount,
27+
List<CommentResponse> comments) {
2428
return PostResponse.builder()
2529
.id(post.getId())
2630
.title(post.getTitle())
@@ -31,6 +35,9 @@ public static PostResponse from(ForumPost post) {
3135
.createdAt(post.getCreatedAt())
3236
.updatedAt(post.getUpdatedAt())
3337
.commentCount(post.getComments() != null ? post.getComments().size() : 0)
38+
.upvoteCount(upvoteCount)
39+
.downvoteCount(downvoteCount)
40+
.comments(comments)
3441
.build();
3542
}
3643
}

apps/jobboard-backend/src/main/java/org/bounswe/jobboardbackend/forum/model/ForumPost.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ public class ForumPost {
4343
@Builder.Default
4444
private List<ForumComment> comments = new ArrayList<>();
4545

46+
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
47+
@Builder.Default
48+
private List<ForumPostUpvote> upvotes = new ArrayList<>();
49+
50+
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
51+
@Builder.Default
52+
private List<ForumPostDownvote> downvotes = new ArrayList<>();
53+
4654
@CreationTimestamp
4755
@Column(nullable = false, updatable = false)
4856
private Instant createdAt;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.bounswe.jobboardbackend.forum.model;
2+
3+
import jakarta.persistence.*;
4+
import lombok.*;
5+
import org.bounswe.jobboardbackend.auth.model.User;
6+
import org.hibernate.annotations.CreationTimestamp;
7+
8+
import java.time.Instant;
9+
10+
@Entity
11+
@Table(name = "forum_post_downvotes", uniqueConstraints = {
12+
@UniqueConstraint(columnNames = { "user_id", "post_id" })
13+
})
14+
@Getter
15+
@Setter
16+
@NoArgsConstructor
17+
@AllArgsConstructor
18+
@Builder
19+
public class ForumPostDownvote {
20+
21+
@Id
22+
@GeneratedValue(strategy = GenerationType.IDENTITY)
23+
private Long id;
24+
25+
@ManyToOne(fetch = FetchType.LAZY, optional = false)
26+
@JoinColumn(name = "user_id", nullable = false)
27+
private User user;
28+
29+
@ManyToOne(fetch = FetchType.LAZY, optional = false)
30+
@JoinColumn(name = "post_id", nullable = false)
31+
private ForumPost post;
32+
33+
@CreationTimestamp
34+
@Column(nullable = false, updatable = false)
35+
private Instant createdAt;
36+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.bounswe.jobboardbackend.forum.model;
2+
3+
import jakarta.persistence.*;
4+
import lombok.*;
5+
import org.bounswe.jobboardbackend.auth.model.User;
6+
import org.hibernate.annotations.CreationTimestamp;
7+
8+
import java.time.Instant;
9+
10+
@Entity
11+
@Table(name = "forum_post_upvotes", uniqueConstraints = {
12+
@UniqueConstraint(columnNames = { "user_id", "post_id" })
13+
})
14+
@Getter
15+
@Setter
16+
@NoArgsConstructor
17+
@AllArgsConstructor
18+
@Builder
19+
public class ForumPostUpvote {
20+
21+
@Id
22+
@GeneratedValue(strategy = GenerationType.IDENTITY)
23+
private Long id;
24+
25+
@ManyToOne(fetch = FetchType.LAZY, optional = false)
26+
@JoinColumn(name = "user_id", nullable = false)
27+
private User user;
28+
29+
@ManyToOne(fetch = FetchType.LAZY, optional = false)
30+
@JoinColumn(name = "post_id", nullable = false)
31+
private ForumPost post;
32+
33+
@CreationTimestamp
34+
@Column(nullable = false, updatable = false)
35+
private Instant createdAt;
36+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.bounswe.jobboardbackend.forum.repository;
2+
3+
import org.bounswe.jobboardbackend.forum.model.ForumPostDownvote;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
6+
import java.util.Optional;
7+
8+
public interface ForumPostDownvoteRepository extends JpaRepository<ForumPostDownvote, Long> {
9+
Optional<ForumPostDownvote> findByUserIdAndPostId(Long userId, Long postId);
10+
11+
long countByPostId(Long postId);
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.bounswe.jobboardbackend.forum.repository;
2+
3+
import org.bounswe.jobboardbackend.forum.model.ForumPostUpvote;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
6+
import java.util.Optional;
7+
8+
public interface ForumPostUpvoteRepository extends JpaRepository<ForumPostUpvote, Long> {
9+
Optional<ForumPostUpvote> findByUserIdAndPostId(Long userId, Long postId);
10+
11+
long countByPostId(Long postId);
12+
}

apps/jobboard-backend/src/main/java/org/bounswe/jobboardbackend/forum/service/ForumService.java

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@
1010
import org.bounswe.jobboardbackend.forum.model.ForumCommentDownvote;
1111
import org.bounswe.jobboardbackend.forum.model.ForumCommentUpvote;
1212
import org.bounswe.jobboardbackend.forum.model.ForumPost;
13+
import org.bounswe.jobboardbackend.forum.model.ForumPostDownvote;
14+
import org.bounswe.jobboardbackend.forum.model.ForumPostUpvote;
1315
import org.bounswe.jobboardbackend.forum.repository.ForumCommentDownvoteRepository;
1416
import org.bounswe.jobboardbackend.forum.repository.ForumCommentRepository;
1517
import org.bounswe.jobboardbackend.forum.repository.ForumCommentUpvoteRepository;
18+
import org.bounswe.jobboardbackend.forum.repository.ForumPostDownvoteRepository;
1619
import org.bounswe.jobboardbackend.forum.repository.ForumPostRepository;
20+
import org.bounswe.jobboardbackend.forum.repository.ForumPostUpvoteRepository;
1721
import org.springframework.stereotype.Service;
1822
import org.springframework.transaction.annotation.Transactional;
1923

@@ -29,6 +33,8 @@ public class ForumService {
2933
private final ForumCommentRepository commentRepository;
3034
private final ForumCommentUpvoteRepository upvoteRepository;
3135
private final ForumCommentDownvoteRepository downvoteRepository;
36+
private final ForumPostUpvoteRepository postUpvoteRepository;
37+
private final ForumPostDownvoteRepository postDownvoteRepository;
3238

3339
@Transactional
3440
public PostResponse createPost(User author, CreatePostRequest request) {
@@ -40,21 +46,21 @@ public PostResponse createPost(User author, CreatePostRequest request) {
4046
.build();
4147

4248
ForumPost savedPost = postRepository.save(post);
43-
return PostResponse.from(savedPost);
49+
return toPostResponse(savedPost);
4450
}
4551

4652
@Transactional(readOnly = true)
4753
public List<PostResponse> findAllPosts() {
4854
return postRepository.findAll().stream()
49-
.map(PostResponse::from)
55+
.map(this::toPostResponse)
5056
.collect(Collectors.toList());
5157
}
5258

5359
@Transactional(readOnly = true)
5460
public PostResponse findPostById(Long id) {
5561
ForumPost post = postRepository.findById(id)
5662
.orElseThrow(() -> new HandleException(ErrorCode.NOT_FOUND, "Post not found"));
57-
return PostResponse.from(post);
63+
return toPostResponse(post);
5864
}
5965

6066
@Transactional
@@ -77,7 +83,7 @@ public PostResponse updatePost(Long id, User user, UpdatePostRequest request) {
7783
}
7884

7985
ForumPost updatedPost = postRepository.save(post);
80-
return PostResponse.from(updatedPost);
86+
return toPostResponse(updatedPost);
8187
}
8288

8389
@Transactional
@@ -211,4 +217,63 @@ private CommentResponse toCommentResponse(ForumComment comment) {
211217
long downvotes = downvoteRepository.countByCommentId(comment.getId());
212218
return CommentResponse.from(comment, upvotes, downvotes);
213219
}
220+
221+
private PostResponse toPostResponse(ForumPost post) {
222+
long upvotes = postUpvoteRepository.countByPostId(post.getId());
223+
long downvotes = postDownvoteRepository.countByPostId(post.getId());
224+
List<CommentResponse> comments = post.getComments().stream()
225+
.map(this::toCommentResponse)
226+
.collect(Collectors.toList());
227+
return PostResponse.from(post, upvotes, downvotes, comments);
228+
}
229+
230+
@Transactional
231+
public void upvotePost(Long postId, User user) {
232+
ForumPost post = postRepository.findById(postId)
233+
.orElseThrow(() -> new HandleException(ErrorCode.NOT_FOUND, "Post not found"));
234+
235+
if (postUpvoteRepository.findByUserIdAndPostId(user.getId(), postId).isPresent()) {
236+
return;
237+
}
238+
239+
postDownvoteRepository.findByUserIdAndPostId(user.getId(), postId)
240+
.ifPresent(postDownvoteRepository::delete);
241+
242+
ForumPostUpvote upvote = ForumPostUpvote.builder()
243+
.user(user)
244+
.post(post)
245+
.build();
246+
postUpvoteRepository.save(upvote);
247+
}
248+
249+
@Transactional
250+
public void removePostUpvote(Long postId, User user) {
251+
postUpvoteRepository.findByUserIdAndPostId(user.getId(), postId)
252+
.ifPresent(postUpvoteRepository::delete);
253+
}
254+
255+
@Transactional
256+
public void downvotePost(Long postId, User user) {
257+
ForumPost post = postRepository.findById(postId)
258+
.orElseThrow(() -> new HandleException(ErrorCode.NOT_FOUND, "Post not found"));
259+
260+
if (postDownvoteRepository.findByUserIdAndPostId(user.getId(), postId).isPresent()) {
261+
return;
262+
}
263+
264+
postUpvoteRepository.findByUserIdAndPostId(user.getId(), postId)
265+
.ifPresent(postUpvoteRepository::delete);
266+
267+
ForumPostDownvote downvote = ForumPostDownvote.builder()
268+
.user(user)
269+
.post(post)
270+
.build();
271+
postDownvoteRepository.save(downvote);
272+
}
273+
274+
@Transactional
275+
public void removePostDownvote(Long postId, User user) {
276+
postDownvoteRepository.findByUserIdAndPostId(user.getId(), postId)
277+
.ifPresent(postDownvoteRepository::delete);
278+
}
214279
}

apps/jobboard-backend/src/test/java/org/bounswe/jobboardbackend/forum/service/ForumServiceTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
import org.bounswe.jobboardbackend.forum.repository.ForumCommentDownvoteRepository;
1313
import org.bounswe.jobboardbackend.forum.repository.ForumCommentRepository;
1414
import org.bounswe.jobboardbackend.forum.repository.ForumCommentUpvoteRepository;
15+
import org.bounswe.jobboardbackend.forum.repository.ForumPostDownvoteRepository;
1516
import org.bounswe.jobboardbackend.forum.repository.ForumPostRepository;
17+
import org.bounswe.jobboardbackend.forum.repository.ForumPostUpvoteRepository;
1618
import org.junit.jupiter.api.BeforeEach;
1719
import org.junit.jupiter.api.Test;
1820
import org.junit.jupiter.api.extension.ExtendWith;
@@ -43,6 +45,12 @@ class ForumServiceTest {
4345
@Mock
4446
private ForumCommentDownvoteRepository downvoteRepository;
4547

48+
@Mock
49+
private ForumPostUpvoteRepository postUpvoteRepository;
50+
51+
@Mock
52+
private ForumPostDownvoteRepository postDownvoteRepository;
53+
4654
@InjectMocks
4755
private ForumService forumService;
4856

0 commit comments

Comments
 (0)