Skip to content

Commit 3abbf57

Browse files
committed
feat: forum
1 parent 08fdada commit 3abbf57

File tree

18 files changed

+957
-0
lines changed

18 files changed

+957
-0
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package org.bounswe.jobboardbackend.forum.controller;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.bounswe.jobboardbackend.auth.model.User;
5+
import org.bounswe.jobboardbackend.auth.repository.UserRepository;
6+
import org.bounswe.jobboardbackend.forum.dto.*;
7+
import org.bounswe.jobboardbackend.forum.service.ForumService;
8+
import org.springframework.http.HttpStatus;
9+
import org.springframework.http.ResponseEntity;
10+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
11+
import org.springframework.security.core.userdetails.UserDetails;
12+
import org.springframework.web.bind.annotation.*;
13+
14+
import java.util.List;
15+
16+
@RestController
17+
@RequestMapping("/api/forum")
18+
@RequiredArgsConstructor
19+
public class ForumController {
20+
21+
private final ForumService forumService;
22+
private final UserRepository userRepository;
23+
24+
private User getUser(UserDetails userDetails) {
25+
String username = userDetails.getUsername();
26+
return userRepository.findByUsername(username)
27+
.orElseThrow(() -> new RuntimeException("User not found: " + username));
28+
}
29+
30+
@PostMapping("/posts")
31+
public ResponseEntity<PostResponse> createPost(@AuthenticationPrincipal UserDetails userDetails,
32+
@RequestBody CreatePostRequest request) {
33+
User user = getUser(userDetails);
34+
return ResponseEntity.status(HttpStatus.CREATED).body(forumService.createPost(user, request));
35+
}
36+
37+
@GetMapping("/posts")
38+
public ResponseEntity<List<PostResponse>> findAllPosts() {
39+
return ResponseEntity.ok(forumService.findAllPosts());
40+
}
41+
42+
@GetMapping("/posts/{id}")
43+
public ResponseEntity<PostResponse> findPostById(@PathVariable Long id) {
44+
return ResponseEntity.ok(forumService.findPostById(id));
45+
}
46+
47+
@PutMapping("/posts/{id}")
48+
public ResponseEntity<PostResponse> updatePost(@PathVariable Long id,
49+
@AuthenticationPrincipal UserDetails userDetails,
50+
@RequestBody UpdatePostRequest request) {
51+
User user = getUser(userDetails);
52+
return ResponseEntity.ok(forumService.updatePost(id, user, request));
53+
}
54+
55+
@DeleteMapping("/posts/{id}")
56+
public ResponseEntity<Void> deletePost(@PathVariable Long id, @AuthenticationPrincipal UserDetails userDetails) {
57+
User user = getUser(userDetails);
58+
forumService.deletePost(id, user);
59+
return ResponseEntity.noContent().build();
60+
}
61+
62+
@PostMapping("/posts/{id}/comments")
63+
public ResponseEntity<CommentResponse> createComment(@PathVariable Long id,
64+
@AuthenticationPrincipal UserDetails userDetails,
65+
@RequestBody CreateCommentRequest request) {
66+
User user = getUser(userDetails);
67+
return ResponseEntity.status(HttpStatus.CREATED).body(forumService.createComment(id, user, request));
68+
}
69+
70+
@PutMapping("/comments/{commentId}")
71+
public ResponseEntity<CommentResponse> updateComment(@PathVariable Long commentId,
72+
@AuthenticationPrincipal UserDetails userDetails, @RequestBody UpdateCommentRequest request) {
73+
User user = getUser(userDetails);
74+
return ResponseEntity.ok(forumService.updateComment(commentId, user, request));
75+
}
76+
77+
@DeleteMapping("/comments/{commentId}")
78+
public ResponseEntity<Void> deleteComment(@PathVariable Long commentId,
79+
@AuthenticationPrincipal UserDetails userDetails) {
80+
User user = getUser(userDetails);
81+
forumService.deleteComment(commentId, user);
82+
return ResponseEntity.noContent().build();
83+
}
84+
85+
@PostMapping("/comments/{commentId}/upvote")
86+
public ResponseEntity<Void> upvoteComment(@PathVariable Long commentId,
87+
@AuthenticationPrincipal UserDetails userDetails) {
88+
User user = getUser(userDetails);
89+
forumService.upvoteComment(commentId, user);
90+
return ResponseEntity.ok().build();
91+
}
92+
93+
@DeleteMapping("/comments/{commentId}/upvote")
94+
public ResponseEntity<Void> removeUpvote(@PathVariable Long commentId,
95+
@AuthenticationPrincipal UserDetails userDetails) {
96+
User user = getUser(userDetails);
97+
forumService.removeUpvote(commentId, user);
98+
return ResponseEntity.ok().build();
99+
}
100+
101+
@PostMapping("/comments/{commentId}/downvote")
102+
public ResponseEntity<Void> downvoteComment(@PathVariable Long commentId,
103+
@AuthenticationPrincipal UserDetails userDetails) {
104+
User user = getUser(userDetails);
105+
forumService.downvoteComment(commentId, user);
106+
return ResponseEntity.ok().build();
107+
}
108+
109+
@DeleteMapping("/comments/{commentId}/downvote")
110+
public ResponseEntity<Void> removeDownvote(@PathVariable Long commentId,
111+
@AuthenticationPrincipal UserDetails userDetails) {
112+
User user = getUser(userDetails);
113+
forumService.removeDownvote(commentId, user);
114+
return ResponseEntity.ok().build();
115+
}
116+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.bounswe.jobboardbackend.forum.dto;
2+
3+
import lombok.Builder;
4+
import lombok.Data;
5+
import org.bounswe.jobboardbackend.forum.model.ForumComment;
6+
7+
import java.time.Instant;
8+
9+
@Data
10+
@Builder
11+
public class CommentResponse {
12+
private Long id;
13+
private String content;
14+
private Long authorId;
15+
private String authorUsername;
16+
private Long postId;
17+
private Long parentCommentId;
18+
private Instant createdAt;
19+
private Instant updatedAt;
20+
private long upvoteCount;
21+
private long downvoteCount;
22+
23+
public static CommentResponse from(ForumComment comment, long upvoteCount, long downvoteCount) {
24+
return CommentResponse.builder()
25+
.id(comment.getId())
26+
.content(comment.getContent())
27+
.authorId(comment.getAuthor().getId())
28+
.authorUsername(comment.getAuthor().getUsername())
29+
.postId(comment.getPost().getId())
30+
.parentCommentId(comment.getParentComment() != null ? comment.getParentComment().getId() : null)
31+
.createdAt(comment.getCreatedAt())
32+
.updatedAt(comment.getUpdatedAt())
33+
.upvoteCount(upvoteCount)
34+
.downvoteCount(downvoteCount)
35+
.build();
36+
}
37+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.bounswe.jobboardbackend.forum.dto;
2+
3+
import jakarta.validation.constraints.NotBlank;
4+
import lombok.Data;
5+
6+
@Data
7+
public class CreateCommentRequest {
8+
@NotBlank(message = "Content is required")
9+
private String content;
10+
private Long parentCommentId;
11+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.bounswe.jobboardbackend.forum.dto;
2+
3+
import jakarta.validation.constraints.NotBlank;
4+
import lombok.Data;
5+
6+
import java.util.List;
7+
8+
@Data
9+
public class CreatePostRequest {
10+
@NotBlank(message = "Title is required")
11+
private String title;
12+
13+
@NotBlank(message = "Content is required")
14+
private String content;
15+
16+
private List<String> tags;
17+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.bounswe.jobboardbackend.forum.dto;
2+
3+
import lombok.Builder;
4+
import lombok.Data;
5+
import org.bounswe.jobboardbackend.forum.model.ForumPost;
6+
7+
import java.time.Instant;
8+
import java.util.List;
9+
10+
@Data
11+
@Builder
12+
public class PostResponse {
13+
private Long id;
14+
private String title;
15+
private String content;
16+
private Long authorId;
17+
private String authorUsername;
18+
private List<String> tags;
19+
private Instant createdAt;
20+
private Instant updatedAt;
21+
private int commentCount;
22+
23+
public static PostResponse from(ForumPost post) {
24+
return PostResponse.builder()
25+
.id(post.getId())
26+
.title(post.getTitle())
27+
.content(post.getContent())
28+
.authorId(post.getAuthor().getId())
29+
.authorUsername(post.getAuthor().getUsername())
30+
.tags(post.getTags())
31+
.createdAt(post.getCreatedAt())
32+
.updatedAt(post.getUpdatedAt())
33+
.commentCount(post.getComments() != null ? post.getComments().size() : 0)
34+
.build();
35+
}
36+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.bounswe.jobboardbackend.forum.dto;
2+
3+
import jakarta.validation.constraints.NotBlank;
4+
import lombok.Data;
5+
6+
@Data
7+
public class UpdateCommentRequest {
8+
@NotBlank(message = "Content is required")
9+
private String content;
10+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.bounswe.jobboardbackend.forum.dto;
2+
3+
import lombok.Data;
4+
5+
import java.util.List;
6+
7+
@Data
8+
public class UpdatePostRequest {
9+
private String title;
10+
private String content;
11+
private List<String> tags;
12+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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+
import org.hibernate.annotations.UpdateTimestamp;
8+
9+
import java.time.Instant;
10+
import java.util.ArrayList;
11+
import java.util.List;
12+
13+
@Entity
14+
@Table(name = "forum_comments")
15+
@Getter
16+
@Setter
17+
@NoArgsConstructor
18+
@AllArgsConstructor
19+
@Builder
20+
public class ForumComment {
21+
22+
@Id
23+
@GeneratedValue(strategy = GenerationType.IDENTITY)
24+
private Long id;
25+
26+
@Column(nullable = false, columnDefinition = "TEXT")
27+
private String content;
28+
29+
@ManyToOne(fetch = FetchType.LAZY, optional = false)
30+
@JoinColumn(name = "author_id", nullable = false)
31+
private User author;
32+
33+
@ManyToOne(fetch = FetchType.LAZY, optional = false)
34+
@JoinColumn(name = "post_id", nullable = false)
35+
private ForumPost post;
36+
37+
@ManyToOne(fetch = FetchType.LAZY)
38+
@JoinColumn(name = "parent_comment_id")
39+
private ForumComment parentComment;
40+
41+
@OneToMany(mappedBy = "comment", cascade = CascadeType.ALL, orphanRemoval = true)
42+
@Builder.Default
43+
private List<ForumCommentUpvote> upvotes = new ArrayList<>();
44+
45+
@OneToMany(mappedBy = "comment", cascade = CascadeType.ALL, orphanRemoval = true)
46+
@Builder.Default
47+
private List<ForumCommentDownvote> downvotes = new ArrayList<>();
48+
49+
@CreationTimestamp
50+
@Column(nullable = false, updatable = false)
51+
private Instant createdAt;
52+
53+
@UpdateTimestamp
54+
@Column(nullable = false)
55+
private Instant updatedAt;
56+
}
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_comment_downvotes", uniqueConstraints = {
12+
@UniqueConstraint(columnNames = { "user_id", "comment_id" })
13+
})
14+
@Getter
15+
@Setter
16+
@NoArgsConstructor
17+
@AllArgsConstructor
18+
@Builder
19+
public class ForumCommentDownvote {
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 = "comment_id", nullable = false)
31+
private ForumComment comment;
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_comment_upvotes", uniqueConstraints = {
12+
@UniqueConstraint(columnNames = { "user_id", "comment_id" })
13+
})
14+
@Getter
15+
@Setter
16+
@NoArgsConstructor
17+
@AllArgsConstructor
18+
@Builder
19+
public class ForumCommentUpvote {
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 = "comment_id", nullable = false)
31+
private ForumComment comment;
32+
33+
@CreationTimestamp
34+
@Column(nullable = false, updatable = false)
35+
private Instant createdAt;
36+
}

0 commit comments

Comments
 (0)