From 69e9ef85326cd6d39911f01078a4a98432ff62ef Mon Sep 17 00:00:00 2001 From: Tim <135014430+nagisa77@users.noreply.github.com> Date: Mon, 30 Jun 2025 19:52:22 +0800 Subject: [PATCH] Add reaction module --- .../controller/ReactionController.java | 61 +++++++++++++++++++ .../java/com/openisle/model/Reaction.java | 40 ++++++++++++ .../java/com/openisle/model/ReactionType.java | 11 ++++ .../repository/ReactionRepository.java | 14 +++++ .../com/openisle/service/ReactionService.java | 49 +++++++++++++++ 5 files changed, 175 insertions(+) create mode 100644 src/main/java/com/openisle/controller/ReactionController.java create mode 100644 src/main/java/com/openisle/model/Reaction.java create mode 100644 src/main/java/com/openisle/model/ReactionType.java create mode 100644 src/main/java/com/openisle/repository/ReactionRepository.java create mode 100644 src/main/java/com/openisle/service/ReactionService.java diff --git a/src/main/java/com/openisle/controller/ReactionController.java b/src/main/java/com/openisle/controller/ReactionController.java new file mode 100644 index 000000000..607b1cfff --- /dev/null +++ b/src/main/java/com/openisle/controller/ReactionController.java @@ -0,0 +1,61 @@ +package com.openisle.controller; + +import com.openisle.model.Reaction; +import com.openisle.model.ReactionType; +import com.openisle.service.ReactionService; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class ReactionController { + private final ReactionService reactionService; + + @PostMapping("/posts/{postId}/reactions") + public ResponseEntity reactToPost(@PathVariable Long postId, + @RequestBody ReactionRequest req, + Authentication auth) { + Reaction reaction = reactionService.reactToPost(auth.getName(), postId, req.getType()); + return ResponseEntity.ok(toDto(reaction)); + } + + @PostMapping("/comments/{commentId}/reactions") + public ResponseEntity reactToComment(@PathVariable Long commentId, + @RequestBody ReactionRequest req, + Authentication auth) { + Reaction reaction = reactionService.reactToComment(auth.getName(), commentId, req.getType()); + return ResponseEntity.ok(toDto(reaction)); + } + + private ReactionDto toDto(Reaction reaction) { + ReactionDto dto = new ReactionDto(); + dto.setId(reaction.getId()); + dto.setType(reaction.getType()); + dto.setUser(reaction.getUser().getUsername()); + if (reaction.getPost() != null) { + dto.setPostId(reaction.getPost().getId()); + } + if (reaction.getComment() != null) { + dto.setCommentId(reaction.getComment().getId()); + } + return dto; + } + + @Data + private static class ReactionRequest { + private ReactionType type; + } + + @Data + private static class ReactionDto { + private Long id; + private ReactionType type; + private String user; + private Long postId; + private Long commentId; + } +} diff --git a/src/main/java/com/openisle/model/Reaction.java b/src/main/java/com/openisle/model/Reaction.java new file mode 100644 index 000000000..380d2ceb9 --- /dev/null +++ b/src/main/java/com/openisle/model/Reaction.java @@ -0,0 +1,40 @@ +package com.openisle.model; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Reaction entity representing a user's reaction to a post or comment. + */ +@Entity +@Getter +@Setter +@NoArgsConstructor +@Table(name = "reactions", + uniqueConstraints = { + @UniqueConstraint(columnNames = {"user_id", "post_id"}), + @UniqueConstraint(columnNames = {"user_id", "comment_id"}) + }) +public class Reaction { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private ReactionType type; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id") + private Post post; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "comment_id") + private Comment comment; +} diff --git a/src/main/java/com/openisle/model/ReactionType.java b/src/main/java/com/openisle/model/ReactionType.java new file mode 100644 index 000000000..442546638 --- /dev/null +++ b/src/main/java/com/openisle/model/ReactionType.java @@ -0,0 +1,11 @@ +package com.openisle.model; + +/** + * Enum of possible reaction types for posts and comments. + */ +public enum ReactionType { + LIKE, + DISLIKE, + RECOMMEND, + ANGRY +} diff --git a/src/main/java/com/openisle/repository/ReactionRepository.java b/src/main/java/com/openisle/repository/ReactionRepository.java new file mode 100644 index 000000000..2d64c9b8f --- /dev/null +++ b/src/main/java/com/openisle/repository/ReactionRepository.java @@ -0,0 +1,14 @@ +package com.openisle.repository; + +import com.openisle.model.Comment; +import com.openisle.model.Post; +import com.openisle.model.Reaction; +import com.openisle.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface ReactionRepository extends JpaRepository { + Optional findByUserAndPost(User user, Post post); + Optional findByUserAndComment(User user, Comment comment); +} diff --git a/src/main/java/com/openisle/service/ReactionService.java b/src/main/java/com/openisle/service/ReactionService.java new file mode 100644 index 000000000..bb7e5a4b9 --- /dev/null +++ b/src/main/java/com/openisle/service/ReactionService.java @@ -0,0 +1,49 @@ +package com.openisle.service; + +import com.openisle.model.Comment; +import com.openisle.model.Post; +import com.openisle.model.Reaction; +import com.openisle.model.ReactionType; +import com.openisle.model.User; +import com.openisle.repository.CommentRepository; +import com.openisle.repository.PostRepository; +import com.openisle.repository.ReactionRepository; +import com.openisle.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ReactionService { + private final ReactionRepository reactionRepository; + private final UserRepository userRepository; + private final PostRepository postRepository; + private final CommentRepository commentRepository; + + public Reaction reactToPost(String username, Long postId, ReactionType type) { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new IllegalArgumentException("User not found")); + Post post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("Post not found")); + Reaction reaction = reactionRepository.findByUserAndPost(user, post) + .orElseGet(Reaction::new); + reaction.setUser(user); + reaction.setPost(post); + reaction.setType(type); + return reactionRepository.save(reaction); + } + + public Reaction reactToComment(String username, Long commentId, ReactionType type) { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new IllegalArgumentException("User not found")); + Comment comment = commentRepository.findById(commentId) + .orElseThrow(() -> new IllegalArgumentException("Comment not found")); + Reaction reaction = reactionRepository.findByUserAndComment(user, comment) + .orElseGet(Reaction::new); + reaction.setUser(user); + reaction.setComment(comment); + reaction.setPost(null); + reaction.setType(type); + return reactionRepository.save(reaction); + } +}