diff --git a/app/backend/src/main/java/com/app/gamereview/controller/CommentController.java b/app/backend/src/main/java/com/app/gamereview/controller/CommentController.java new file mode 100644 index 00000000..a063b196 --- /dev/null +++ b/app/backend/src/main/java/com/app/gamereview/controller/CommentController.java @@ -0,0 +1,61 @@ +package com.app.gamereview.controller; + +import com.app.gamereview.dto.request.comment.CreateCommentRequestDto; +import com.app.gamereview.dto.request.comment.EditCommentRequestDto; +import com.app.gamereview.dto.request.comment.ReplyCommentRequestDto; +import com.app.gamereview.model.Comment; +import com.app.gamereview.model.User; +import com.app.gamereview.service.CommentService; +import com.app.gamereview.util.validation.annotation.AuthorizationRequired; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/comment") +@Validated +public class CommentController { + + private final CommentService commentService; + + @Autowired + public CommentController(CommentService commentService) { + this.commentService = commentService; + } + + + @AuthorizationRequired + @PostMapping("/create") + public ResponseEntity createComment(@Valid @RequestBody CreateCommentRequestDto comment, @RequestHeader String Authorization, HttpServletRequest request) { + User user = (User) request.getAttribute("authenticatedUser"); + Comment commentToCreate = commentService.createComment(comment, user); + return ResponseEntity.ok(commentToCreate); + } + + @AuthorizationRequired + @PostMapping("/reply") + public ResponseEntity createReply(@Valid @RequestBody ReplyCommentRequestDto reply, @RequestHeader String Authorization, HttpServletRequest request) { + User user = (User) request.getAttribute("authenticatedUser"); + Comment commentToCreate = commentService.replyComment(reply, user); + return ResponseEntity.ok(commentToCreate); + } + + @AuthorizationRequired + @PostMapping("/edit") + public ResponseEntity editComment(@RequestParam String id, @Valid @RequestBody EditCommentRequestDto comment, @RequestHeader String Authorization, HttpServletRequest request) { + User user = (User) request.getAttribute("authenticatedUser"); + Comment editedComment = commentService.editComment(id, comment, user); + return ResponseEntity.ok(editedComment); + } + + @AuthorizationRequired + @DeleteMapping("/delete") + public ResponseEntity deleteComment(@RequestParam String id, @RequestHeader String Authorization, HttpServletRequest request) { + User user = (User) request.getAttribute("authenticatedUser"); + Comment deletedComment = commentService.deleteComment(id, user); + return ResponseEntity.ok(deletedComment); + } +} diff --git a/app/backend/src/main/java/com/app/gamereview/controller/PostController.java b/app/backend/src/main/java/com/app/gamereview/controller/PostController.java index 3204a3c6..3bd340ce 100644 --- a/app/backend/src/main/java/com/app/gamereview/controller/PostController.java +++ b/app/backend/src/main/java/com/app/gamereview/controller/PostController.java @@ -3,6 +3,7 @@ import java.util.List; import com.app.gamereview.dto.request.post.EditPostRequestDto; +import com.app.gamereview.dto.response.comment.GetPostCommentsResponseDto; import com.app.gamereview.model.User; import com.app.gamereview.util.validation.annotation.AuthorizationRequired; import jakarta.servlet.http.HttpServletRequest; @@ -25,48 +26,56 @@ @Validated public class PostController { - private final PostService postService; + private final PostService postService; - @Autowired - public PostController(PostService postService) { - this.postService = postService; - } + @Autowired + public PostController(PostService postService) { + this.postService = postService; + } - @GetMapping("/get-post-list") - public ResponseEntity> getPostList(@Valid @ParameterObject GetPostListFilterRequestDto filter) { - List posts = postService.getPostList(filter); - return ResponseEntity.ok(posts); - } + @GetMapping("/get-post-list") + public ResponseEntity> getPostList(@Valid @ParameterObject GetPostListFilterRequestDto filter) { + List posts = postService.getPostList(filter); + return ResponseEntity.ok(posts); + } - @AuthorizationRequired - @GetMapping("/get-post-detail") - public ResponseEntity getPostDetail(@RequestParam String id, @RequestHeader String Authorization, HttpServletRequest request) { - User user = (User) request.getAttribute("authenticatedUser"); - Post post = postService.getPostById(id, user); - return ResponseEntity.ok(post); - } + @AuthorizationRequired + @GetMapping("/get-post-detail") + public ResponseEntity getPostDetail(@RequestParam String id, @RequestHeader String Authorization, HttpServletRequest request) { + User user = (User) request.getAttribute("authenticatedUser"); + Post post = postService.getPostById(id, user); + return ResponseEntity.ok(post); + } - @AuthorizationRequired - @PostMapping("/create") - public ResponseEntity createPost(@Valid @RequestBody CreatePostRequestDto post, @RequestHeader String Authorization, HttpServletRequest request) { - User user = (User) request.getAttribute("authenticatedUser"); - Post postToCreate = postService.createPost(post, user); - return ResponseEntity.ok(postToCreate); - } + @AuthorizationRequired + @GetMapping("/get-post-comments") + public ResponseEntity> getPostComments(@RequestParam String id, @RequestHeader String Authorization, HttpServletRequest request) { + User user = (User) request.getAttribute("authenticatedUser"); + List comments = postService.getCommentList(id, user); + return ResponseEntity.ok(comments); + } - @AuthorizationRequired - @PostMapping("/edit") - public ResponseEntity editPost(@RequestParam String id, @Valid @RequestBody EditPostRequestDto post, @RequestHeader String Authorization, HttpServletRequest request) { - User user = (User) request.getAttribute("authenticatedUser"); - Post editedPost = postService.editPost(id, post, user); - return ResponseEntity.ok(editedPost); - } + @AuthorizationRequired + @PostMapping("/create") + public ResponseEntity createPost(@Valid @RequestBody CreatePostRequestDto post, @RequestHeader String Authorization, HttpServletRequest request) { + User user = (User) request.getAttribute("authenticatedUser"); + Post postToCreate = postService.createPost(post, user); + return ResponseEntity.ok(postToCreate); + } - @AuthorizationRequired - @DeleteMapping("/delete") - public ResponseEntity deletePost(@RequestParam String id, @RequestHeader String Authorization, HttpServletRequest request) { - User user = (User) request.getAttribute("authenticatedUser"); - Post deletedPost = postService.deletePost(id, user); - return ResponseEntity.ok(deletedPost); - } + @AuthorizationRequired + @PostMapping("/edit") + public ResponseEntity editPost(@RequestParam String id, @Valid @RequestBody EditPostRequestDto post, @RequestHeader String Authorization, HttpServletRequest request) { + User user = (User) request.getAttribute("authenticatedUser"); + Post editedPost = postService.editPost(id, post, user); + return ResponseEntity.ok(editedPost); + } + + @AuthorizationRequired + @DeleteMapping("/delete") + public ResponseEntity deletePost(@RequestParam String id, @RequestHeader String Authorization, HttpServletRequest request) { + User user = (User) request.getAttribute("authenticatedUser"); + Post deletedPost = postService.deletePost(id, user); + return ResponseEntity.ok(deletedPost); + } } diff --git a/app/backend/src/main/java/com/app/gamereview/dto/request/comment/CreateCommentRequestDto.java b/app/backend/src/main/java/com/app/gamereview/dto/request/comment/CreateCommentRequestDto.java new file mode 100644 index 00000000..dd7ae92b --- /dev/null +++ b/app/backend/src/main/java/com/app/gamereview/dto/request/comment/CreateCommentRequestDto.java @@ -0,0 +1,25 @@ +package com.app.gamereview.dto.request.comment; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CreateCommentRequestDto { + + private String commentContent; + + @NotNull(message = "Post Id cannot be null or empty.") + @Pattern(regexp = "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$", + message = "Post has invalid Id (UUID) format") + private String post; + + + // TODO annotations + // TODO achievements +} diff --git a/app/backend/src/main/java/com/app/gamereview/dto/request/comment/EditCommentRequestDto.java b/app/backend/src/main/java/com/app/gamereview/dto/request/comment/EditCommentRequestDto.java new file mode 100644 index 00000000..83aa7d04 --- /dev/null +++ b/app/backend/src/main/java/com/app/gamereview/dto/request/comment/EditCommentRequestDto.java @@ -0,0 +1,14 @@ +package com.app.gamereview.dto.request.comment; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EditCommentRequestDto { + + private String commentContent; + +} diff --git a/app/backend/src/main/java/com/app/gamereview/dto/request/comment/ReplyCommentRequestDto.java b/app/backend/src/main/java/com/app/gamereview/dto/request/comment/ReplyCommentRequestDto.java new file mode 100644 index 00000000..24cbdbef --- /dev/null +++ b/app/backend/src/main/java/com/app/gamereview/dto/request/comment/ReplyCommentRequestDto.java @@ -0,0 +1,25 @@ +package com.app.gamereview.dto.request.comment; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ReplyCommentRequestDto { + + private String commentContent; + + + @NotNull(message = "Parent Comment Id cannot be null or empty.") + @Pattern(regexp = "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$", + message = "Comment has invalid Id (UUID) format") + private String parentComment; + + // TODO annotations + // TODO achievements +} diff --git a/app/backend/src/main/java/com/app/gamereview/dto/response/comment/CommentReplyResponseDto.java b/app/backend/src/main/java/com/app/gamereview/dto/response/comment/CommentReplyResponseDto.java new file mode 100644 index 00000000..fcd497e3 --- /dev/null +++ b/app/backend/src/main/java/com/app/gamereview/dto/response/comment/CommentReplyResponseDto.java @@ -0,0 +1,35 @@ +package com.app.gamereview.dto.response.comment; + +import com.app.gamereview.model.User; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CommentReplyResponseDto { + + private String id; + + private String commentContent; + + private User commenter; + + private String post; + + private LocalDateTime lastEditedAt; + + private LocalDateTime createdAt; + + private Boolean isEdited; + + private int overallVote; // overallVote = # of upvote - # of downvote + + private int voteCount; // voteCount = # of upvote + # of downvote + + // TODO reports + // TODO annotations +} diff --git a/app/backend/src/main/java/com/app/gamereview/dto/response/comment/GetPostCommentsResponseDto.java b/app/backend/src/main/java/com/app/gamereview/dto/response/comment/GetPostCommentsResponseDto.java new file mode 100644 index 00000000..90ed24ae --- /dev/null +++ b/app/backend/src/main/java/com/app/gamereview/dto/response/comment/GetPostCommentsResponseDto.java @@ -0,0 +1,38 @@ +package com.app.gamereview.dto.response.comment; + +import com.app.gamereview.model.User; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.ArrayList; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GetPostCommentsResponseDto { + + private String id; + + private String commentContent; + + private User commenter; + + private String post; + + private LocalDateTime lastEditedAt; + + private LocalDateTime createdAt; + + private Boolean isEdited; + + private int overallVote; // overallVote = # of upvote - # of downvote + + private int voteCount; // voteCount = # of upvote + # of downvote + + private ArrayList replies; + + // TODO reports + // TODO annotations +} diff --git a/app/backend/src/main/java/com/app/gamereview/dto/response/post/GetPostListResponseDto.java b/app/backend/src/main/java/com/app/gamereview/dto/response/post/GetPostListResponseDto.java index 3a0d9658..91d2152b 100644 --- a/app/backend/src/main/java/com/app/gamereview/dto/response/post/GetPostListResponseDto.java +++ b/app/backend/src/main/java/com/app/gamereview/dto/response/post/GetPostListResponseDto.java @@ -37,6 +37,8 @@ public class GetPostListResponseDto { private int voteCount; // voteCount = # of upvote + # of downvote + private int commentCount; + // TODO reports // TODO comments // TODO annotations diff --git a/app/backend/src/main/java/com/app/gamereview/model/Comment.java b/app/backend/src/main/java/com/app/gamereview/model/Comment.java new file mode 100644 index 00000000..c3d15fe8 --- /dev/null +++ b/app/backend/src/main/java/com/app/gamereview/model/Comment.java @@ -0,0 +1,58 @@ +package com.app.gamereview.model; + +import com.app.gamereview.enums.VoteChoice; +import com.app.gamereview.model.common.BaseModel; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.time.LocalDateTime; + +@Document(collection = "Comment") +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class Comment extends BaseModel { + + private String commentContent; + + private String commenter; + + @NotNull + private String post; + + private String parentComment; + + private LocalDateTime lastEditedAt; + + private int overallVote; // overallVote = # of upvote - # of downvote + + private int voteCount; // voteCount = # of upvote + # of downvote + + // TODO reports + // TODO annotations + + public void addVote(VoteChoice choice) { + voteCount += 1; + if (choice.name().equals("UPVOTE")) { + overallVote += 1; + } else if (choice.name().equals("DOWNVOTE")) { + overallVote -= 1; + } + } + + public void deleteVote(VoteChoice choice) { + voteCount -= 1; + if (choice.name().equals("UPVOTE")) { + overallVote -= 1; + } else if (choice.name().equals("DOWNVOTE")) { + overallVote += 1; + } + } + + +} diff --git a/app/backend/src/main/java/com/app/gamereview/model/Post.java b/app/backend/src/main/java/com/app/gamereview/model/Post.java index f3bb53aa..d94b04ad 100644 --- a/app/backend/src/main/java/com/app/gamereview/model/Post.java +++ b/app/backend/src/main/java/com/app/gamereview/model/Post.java @@ -47,7 +47,6 @@ public class Post extends BaseModel { private int voteCount; // voteCount = # of upvote + # of downvote // TODO reports - // TODO comments // TODO annotations // TODO achievements diff --git a/app/backend/src/main/java/com/app/gamereview/repository/CommentRepository.java b/app/backend/src/main/java/com/app/gamereview/repository/CommentRepository.java new file mode 100644 index 00000000..2ca20e88 --- /dev/null +++ b/app/backend/src/main/java/com/app/gamereview/repository/CommentRepository.java @@ -0,0 +1,12 @@ +package com.app.gamereview.repository; + +import com.app.gamereview.model.Comment; +import org.springframework.data.mongodb.repository.MongoRepository; + +import java.util.List; + +public interface CommentRepository extends MongoRepository { + List findByPost(String postId); + + int countByPost(String postId); +} diff --git a/app/backend/src/main/java/com/app/gamereview/service/CommentService.java b/app/backend/src/main/java/com/app/gamereview/service/CommentService.java new file mode 100644 index 00000000..737c2898 --- /dev/null +++ b/app/backend/src/main/java/com/app/gamereview/service/CommentService.java @@ -0,0 +1,100 @@ +package com.app.gamereview.service; + +import com.app.gamereview.dto.request.comment.CreateCommentRequestDto; +import com.app.gamereview.dto.request.comment.EditCommentRequestDto; +import com.app.gamereview.dto.request.comment.ReplyCommentRequestDto; +import com.app.gamereview.exception.BadRequestException; +import com.app.gamereview.exception.ResourceNotFoundException; +import com.app.gamereview.model.*; +import com.app.gamereview.repository.CommentRepository; +import com.app.gamereview.repository.PostRepository; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Optional; + +@Service +public class CommentService { + + private final PostRepository postRepository; + private final CommentRepository commentRepository; + private final ModelMapper modelMapper; + + @Autowired + public CommentService(PostRepository postRepository, CommentRepository commentRepository, ModelMapper modelMapper) { + this.postRepository = postRepository; + this.commentRepository = commentRepository; + this.modelMapper = modelMapper; + } + + + public Comment createComment(CreateCommentRequestDto request, User user) { + + Optional post = postRepository.findById(request.getPost()); + + if (post.isEmpty()) { + throw new ResourceNotFoundException("Post is not found."); + } + + Comment commentToCreate = modelMapper.map(request, Comment.class); + commentToCreate.setCommenter(user.getId()); + commentToCreate.setLastEditedAt(commentToCreate.getCreatedAt()); + return commentRepository.save(commentToCreate); + } + + public Comment replyComment(ReplyCommentRequestDto request, User user) { + + Optional parentComment = commentRepository.findById(request.getParentComment()); + + if (parentComment.isEmpty() || parentComment.get().getIsDeleted()) { + throw new ResourceNotFoundException("Parent Comment is not found."); + } + + Comment commentToCreate = modelMapper.map(request, Comment.class); + commentToCreate.setCommenter(user.getId()); + commentToCreate.setLastEditedAt(commentToCreate.getCreatedAt()); + commentToCreate.setPost(parentComment.get().getPost()); + return commentRepository.save(commentToCreate); + } + + public Comment editComment(String id, EditCommentRequestDto request, User user) { + Optional comment = commentRepository.findById(id); + + if (comment.isEmpty() || comment.get().getIsDeleted()) { + throw new ResourceNotFoundException("The comment with the given id is not found."); + } + + if (!comment.get().getCommenter().equals(user.getId())) { + throw new BadRequestException("Only the user that created the comment can edit it."); + } + + Comment editedComment = comment.get(); + + if (request.getCommentContent() != null && !request.getCommentContent().isEmpty()) { + editedComment.setCommentContent(request.getCommentContent()); + editedComment.setLastEditedAt(LocalDateTime.now()); + } + + return commentRepository.save(editedComment); + } + + public Comment deleteComment(String id, User user) { + Optional comment = commentRepository.findById(id); + + if (comment.isEmpty() || comment.get().getIsDeleted()) { + throw new ResourceNotFoundException("The comment with the given id is not found."); + } + + if (!comment.get().getCommenter().equals(user.getId())) { + throw new BadRequestException("Only the user that created the comment can delete it."); + } + + Comment commentToDelete = comment.get(); + + commentToDelete.setIsDeleted(true); + + return commentRepository.save(commentToDelete); + } +} diff --git a/app/backend/src/main/java/com/app/gamereview/service/PostService.java b/app/backend/src/main/java/com/app/gamereview/service/PostService.java index 9c60b77e..28751009 100644 --- a/app/backend/src/main/java/com/app/gamereview/service/PostService.java +++ b/app/backend/src/main/java/com/app/gamereview/service/PostService.java @@ -1,18 +1,17 @@ package com.app.gamereview.service; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; +import com.app.gamereview.dto.response.comment.CommentReplyResponseDto; +import com.app.gamereview.dto.response.comment.GetPostCommentsResponseDto; import com.app.gamereview.enums.SortDirection; import com.app.gamereview.enums.SortType; import com.app.gamereview.enums.UserRole; import com.app.gamereview.exception.BadRequestException; -import com.app.gamereview.model.Forum; -import com.app.gamereview.model.Tag; -import com.app.gamereview.model.User; +import com.app.gamereview.model.*; +import com.app.gamereview.repository.CommentRepository; import com.app.gamereview.repository.ForumRepository; import com.app.gamereview.repository.TagRepository; import com.app.gamereview.repository.UserRepository; @@ -29,168 +28,234 @@ import com.app.gamereview.dto.request.post.GetPostListFilterRequestDto; import com.app.gamereview.dto.response.post.GetPostListResponseDto; import com.app.gamereview.exception.ResourceNotFoundException; -import com.app.gamereview.model.Post; import com.app.gamereview.repository.PostRepository; @Service public class PostService { - private final PostRepository postRepository; + private final PostRepository postRepository; - private final ForumRepository forumRepository; + private final ForumRepository forumRepository; - private final UserRepository userRepository; + private final UserRepository userRepository; - private final TagRepository tagRepository; - private final MongoTemplate mongoTemplate; - private final ModelMapper modelMapper; + private final TagRepository tagRepository; + private final CommentRepository commentRepository; + private final MongoTemplate mongoTemplate; + private final ModelMapper modelMapper; - @Autowired - public PostService(PostRepository postRepository, ForumRepository forumRepository, UserRepository userRepository, TagRepository tagRepository, MongoTemplate mongoTemplate, ModelMapper modelMapper) { - this.postRepository = postRepository; - this.forumRepository = forumRepository; - this.userRepository = userRepository; - this.tagRepository = tagRepository; - this.mongoTemplate = mongoTemplate; - this.modelMapper = modelMapper; - } + @Autowired + public PostService(PostRepository postRepository, ForumRepository forumRepository, UserRepository userRepository, TagRepository tagRepository, CommentRepository commentRepository, MongoTemplate mongoTemplate, ModelMapper modelMapper) { + this.postRepository = postRepository; + this.forumRepository = forumRepository; + this.userRepository = userRepository; + this.tagRepository = tagRepository; + this.commentRepository = commentRepository; + this.mongoTemplate = mongoTemplate; + this.modelMapper = modelMapper; + } + + public List getPostList(GetPostListFilterRequestDto filter) { + Query query = new Query(); + + query.addCriteria(Criteria.where("forum").is(filter.getForum())); - public List getPostList(GetPostListFilterRequestDto filter) { - Query query = new Query(); + if (filter.getFindDeleted() == null || !filter.getFindDeleted()) { + query.addCriteria(Criteria.where("isDeleted").is(false)); + } + if (filter.getSearch() != null && !filter.getSearch().isBlank()) { + query.addCriteria(Criteria.where("title").regex(filter.getSearch(), "i")); + } - query.addCriteria(Criteria.where("forum").is(filter.getForum())); + if (filter.getSortBy() != null) { + Sort.Direction sortDirection = Sort.Direction.DESC; // Default sorting direction (you can change it to ASC if needed) + if (filter.getSortDirection() != null) { + sortDirection = filter.getSortDirection().equals(SortDirection.ASCENDING.name()) ? Sort.Direction.ASC : Sort.Direction.DESC; + } + + if (filter.getSortBy().equals(SortType.CREATION_DATE.name())) { + query.with(Sort.by(sortDirection, "createdAt")); + } else if (filter.getSortBy().equals(SortType.EDIT_DATE.name())) { + query.with(Sort.by(sortDirection, "lastEditedAt")); + } else if (filter.getSortBy().equals(SortType.OVERALL_VOTE.name())) { + query.with(Sort.by(sortDirection, "overallVote")); + } else if (filter.getSortBy().equals(SortType.VOTE_COUNT.name())) { + query.with(Sort.by(sortDirection, "voteCount")); + } + } - if (filter.getFindDeleted() == null || !filter.getFindDeleted()) { - query.addCriteria(Criteria.where("isDeleted").is(false)); + List postList = mongoTemplate.find(query, Post.class); + + return postList.stream().map(this::mapToGetPostListResponseDto).collect(Collectors.toList()); } - if (filter.getSearch() != null && !filter.getSearch().isBlank()) { - query.addCriteria(Criteria.where("title").regex(filter.getSearch(), "i")); + + private GetPostListResponseDto mapToGetPostListResponseDto(Post post) { + Boolean isEdited = post.getCreatedAt().isBefore(post.getLastEditedAt()); + String posterId = post.getPoster(); + Optional poster = userRepository.findByIdAndIsDeletedFalse(posterId); + + User posterObject = poster.orElse(null); + int commentCount = commentRepository.countByPost(post.getId()); + + return new GetPostListResponseDto(post.getId(), post.getTitle(), post.getPostContent(), + posterObject, post.getLastEditedAt(), post.getCreatedAt(), isEdited, post.getTags(), + post.getInappropriate(), post.getOverallVote(), post.getVoteCount(), commentCount); } - if (filter.getSortBy() != null) { - Sort.Direction sortDirection = Sort.Direction.DESC; // Default sorting direction (you can change it to ASC if needed) - if (filter.getSortDirection() != null) { - sortDirection = filter.getSortDirection().equals(SortDirection.ASCENDING.name()) ? Sort.Direction.ASC : Sort.Direction.DESC; - } - - if (filter.getSortBy().equals(SortType.CREATION_DATE.name())) { - query.with(Sort.by(sortDirection, "createdAt")); - } else if (filter.getSortBy().equals(SortType.EDIT_DATE.name())) { - query.with(Sort.by(sortDirection, "lastEditedAt")); - } else if (filter.getSortBy().equals(SortType.OVERALL_VOTE.name())) { - query.with(Sort.by(sortDirection, "overallVote")); - } else if (filter.getSortBy().equals(SortType.VOTE_COUNT.name())) { - query.with(Sort.by(sortDirection, "voteCount")); - } + + public Post getPostById(String id, User user) { + Optional post = postRepository.findById(id); + + if (post.isEmpty()) { + throw new ResourceNotFoundException("The post with the given id was not found"); + } + Optional forum = forumRepository.findById(post.get().getForum()); + + if (forum.isPresent()) { + List bannedUsers = forum.get().getBannedUsers(); + System.out.println(); + System.out.println(bannedUsers); + if (bannedUsers.contains(user.getId())) { + throw new ResourceNotFoundException("You cannot see the post because you are banned."); + } + } + + return post.orElse(null); } - List postList = mongoTemplate.find(query, Post.class); + public List getCommentList(String postId, User user) { + Optional post = postRepository.findById(postId); + + if (post.isEmpty()) { + throw new ResourceNotFoundException("The post with the given id was not found"); + } + Optional forum = forumRepository.findById(post.get().getForum()); + if (forum.isPresent()) { + List bannedUsers = forum.get().getBannedUsers(); + if (bannedUsers.contains(user.getId())) { + throw new ResourceNotFoundException("You cannot see the comments because you are banne from this forum."); + } + } + List comments = commentRepository.findByPost(postId); - return postList.stream().map(this::mapToGetPostListResponseDto).collect(Collectors.toList()); - } + Map commentMap = new HashMap<>(); + List topLevelComments = new ArrayList<>(); - private GetPostListResponseDto mapToGetPostListResponseDto(Post post) { - Boolean isEdited = post.getCreatedAt().isBefore(post.getLastEditedAt()); - String posterId = post.getPoster(); - Optional poster = userRepository.findByIdAndIsDeletedFalse(posterId); + // First, convert all comments to DTOs and identify top-level comments + for (Comment comment : comments) { + GetPostCommentsResponseDto dto = convertToCommentDto(comment); + commentMap.put(comment.getId(), dto); - User posterObject = poster.orElse(null); + if (comment.getParentComment() == null) { + topLevelComments.add(dto); + } + } - return new GetPostListResponseDto(post.getId(), post.getTitle(), post.getPostContent(), - posterObject, post.getLastEditedAt(), post.getCreatedAt(), isEdited, post.getTags(), - post.getInappropriate(), post.getOverallVote(), post.getVoteCount()); - } + // Next, associate replies with their parent comments + for (Comment comment : comments) { + if (comment.getParentComment() != null) { + GetPostCommentsResponseDto parentDto = commentMap.get(comment.getParentComment()); + if (parentDto != null) { + parentDto.getReplies().add(convertToReplyDto(comment)); + } + } + } + return topLevelComments; + } - public Post getPostById(String id, User user) { - Optional post = postRepository.findById(id); + private GetPostCommentsResponseDto convertToCommentDto(Comment comment) { + Boolean isEdited = comment.getCreatedAt().isBefore(comment.getLastEditedAt()); + String commenterId = comment.getCommenter(); + Optional commenter = userRepository.findByIdAndIsDeletedFalse(commenterId); - if (post.isEmpty()) { - throw new ResourceNotFoundException("The post with the given id was not found"); + User commenterObject = commenter.orElse(null); + return new GetPostCommentsResponseDto(comment.getId(), comment.getCommentContent(), commenterObject, + comment.getPost(), comment.getLastEditedAt(), comment.getCreatedAt(), isEdited, comment.getOverallVote(), + comment.getVoteCount(), new ArrayList<>()); } - Optional forum = forumRepository.findById(post.get().getForum()); - - if(forum.isPresent()){ - List bannedUsers = forum.get().getBannedUsers(); - System.out.println(); - System.out.println(bannedUsers); - if (bannedUsers.contains(user.getId())){ - throw new ResourceNotFoundException("You cannot see the post because you are banned."); - } + + private CommentReplyResponseDto convertToReplyDto(Comment comment) { + Boolean isEdited = comment.getCreatedAt().isBefore(comment.getLastEditedAt()); + String commenterId = comment.getCommenter(); + Optional commenter = userRepository.findByIdAndIsDeletedFalse(commenterId); + + User commenterObject = commenter.orElse(null); + return new CommentReplyResponseDto(comment.getId(), comment.getCommentContent(), commenterObject, + comment.getPost(), comment.getLastEditedAt(), comment.getCreatedAt(), isEdited, comment.getOverallVote(), + comment.getVoteCount()); } - return post.orElse(null); - } - public Post createPost(CreatePostRequestDto request, User user) { + public Post createPost(CreatePostRequestDto request, User user) { - Optional forum = forumRepository.findById(request.getForum()); + Optional forum = forumRepository.findById(request.getForum()); - if (forum.isEmpty()) { - throw new ResourceNotFoundException("Forum is not found."); - } + if (forum.isEmpty()) { + throw new ResourceNotFoundException("Forum is not found."); + } - if (request.getTags() != null) { - for (String tagId : request.getTags()) { - Optional tag = tagRepository.findById(tagId); - if (tag.isEmpty()) { - throw new ResourceNotFoundException("One of the tags with the given Id is not found."); + if (request.getTags() != null) { + for (String tagId : request.getTags()) { + Optional tag = tagRepository.findById(tagId); + if (tag.isEmpty()) { + throw new ResourceNotFoundException("One of the tags with the given Id is not found."); + } + } + } else { + request.setTags(new ArrayList<>()); } - } - } else { - request.setTags(new ArrayList<>()); + + Post postToCreate = modelMapper.map(request, Post.class); + postToCreate.setPoster(user.getId()); + postToCreate.setLastEditedAt(postToCreate.getCreatedAt()); + postToCreate.setInappropriate(false); + postToCreate.setLocked(false); + return postRepository.save(postToCreate); } - Post postToCreate = modelMapper.map(request, Post.class); - postToCreate.setPoster(user.getId()); - postToCreate.setLastEditedAt(postToCreate.getCreatedAt()); - postToCreate.setInappropriate(false); - postToCreate.setLocked(false); - return postRepository.save(postToCreate); - } + public Post editPost(String id, EditPostRequestDto request, User user) { + Optional post = postRepository.findById(id); - public Post editPost(String id, EditPostRequestDto request, User user) { - Optional post = postRepository.findById(id); + if (post.isEmpty() || post.get().getIsDeleted()) { + throw new ResourceNotFoundException("The post with the given id is not found."); + } - if (post.isEmpty() || post.get().getIsDeleted()) { - throw new ResourceNotFoundException("The post with the given id is not found."); - } + if (!post.get().getPoster().equals(user.getId())) { + throw new BadRequestException("Only the user that created the post can edit it."); + } - if (!post.get().getPoster().equals(user.getId())){ - throw new BadRequestException("Only the user that created the post can edit it."); - } + Post editedPost = post.get(); + if (request.getTitle() != null && !request.getTitle().isEmpty()) { + editedPost.setTitle(request.getTitle()); + } - Post editedPost = post.get(); - if (request.getTitle() != null && !request.getTitle().isEmpty()) { - editedPost.setTitle(request.getTitle()); - } + if (request.getPostContent() != null && !request.getPostContent().isEmpty()) { + editedPost.setPostContent(request.getPostContent()); + } - if (request.getPostContent() != null && !request.getPostContent().isEmpty()) { - editedPost.setPostContent(request.getPostContent()); - } + if ((request.getPostContent() != null && !request.getPostContent().isEmpty()) || (request.getTitle() != null && !request.getTitle().isEmpty())) { + editedPost.setLastEditedAt(LocalDateTime.now()); + } - if((request.getPostContent() != null && !request.getPostContent().isEmpty()) || (request.getTitle() != null && !request.getTitle().isEmpty())) { - editedPost.setLastEditedAt(LocalDateTime.now()); + return postRepository.save(editedPost); } - return postRepository.save(editedPost); - } - - public Post deletePost(String id, User user) { - Optional post = postRepository.findById(id); + public Post deletePost(String id, User user) { + Optional post = postRepository.findById(id); - if (post.isEmpty() || post.get().getIsDeleted()) { - throw new ResourceNotFoundException("The post with the given id is not found."); - } + if (post.isEmpty() || post.get().getIsDeleted()) { + throw new ResourceNotFoundException("The post with the given id is not found."); + } - if (!(post.get().getPoster().equals(user.getId()) || UserRole.ADMIN.equals(user.getRole()))){ - throw new BadRequestException("Only the user that created the post or the admin can delete it."); - } + if (!(post.get().getPoster().equals(user.getId()) || UserRole.ADMIN.equals(user.getRole()))) { + throw new BadRequestException("Only the user that created the post or the admin can delete it."); + } - Post postToDelete = post.get(); + Post postToDelete = post.get(); - postToDelete.setIsDeleted(true); + postToDelete.setIsDeleted(true); - return postRepository.save(postToDelete); - } + return postRepository.save(postToDelete); + } } diff --git a/app/backend/src/main/java/com/app/gamereview/service/VoteService.java b/app/backend/src/main/java/com/app/gamereview/service/VoteService.java index 2734b8dd..abc7bd06 100644 --- a/app/backend/src/main/java/com/app/gamereview/service/VoteService.java +++ b/app/backend/src/main/java/com/app/gamereview/service/VoteService.java @@ -5,10 +5,8 @@ import com.app.gamereview.enums.VoteChoice; import com.app.gamereview.enums.VoteType; import com.app.gamereview.exception.ResourceNotFoundException; -import com.app.gamereview.model.Post; -import com.app.gamereview.model.Review; -import com.app.gamereview.model.User; -import com.app.gamereview.model.Vote; +import com.app.gamereview.model.*; +import com.app.gamereview.repository.CommentRepository; import com.app.gamereview.repository.PostRepository; import com.app.gamereview.repository.ReviewRepository; import com.app.gamereview.repository.VoteRepository; @@ -33,6 +31,7 @@ public class VoteService { private final ModelMapper modelMapper; private final PostRepository postRepository; + private final CommentRepository commentRepository; @Autowired public VoteService( @@ -40,12 +39,14 @@ public VoteService( ReviewRepository reviewRepository, MongoTemplate mongoTemplate, ModelMapper modelMapper, - PostRepository postRepository) { + PostRepository postRepository, + CommentRepository commentRepository) { this.voteRepository = voteRepository; this.reviewRepository = reviewRepository; this.mongoTemplate = mongoTemplate; this.modelMapper = modelMapper; this.postRepository = postRepository; + this.commentRepository = commentRepository; modelMapper.addMappings(new PropertyMap() { @Override @@ -77,17 +78,17 @@ public List getAllVotes(GetAllVotesFilterRequestDto filter) { return mongoTemplate.find(query, Vote.class); } - public Vote getVote(String voteId){ + public Vote getVote(String voteId) { Optional vote = voteRepository.findById(voteId); - if(vote.isEmpty() || vote.get().getIsDeleted()){ + if (vote.isEmpty() || vote.get().getIsDeleted()) { throw new ResourceNotFoundException("Vote not found"); } return vote.get(); } - public Vote addVote(CreateVoteRequestDto requestDto, User user){ + public Vote addVote(CreateVoteRequestDto requestDto, User user) { Vote voteToCreate = modelMapper.map(requestDto, Vote.class); voteToCreate.setVotedBy(user.getId()); @@ -100,37 +101,48 @@ public Vote addVote(CreateVoteRequestDto requestDto, User user){ VoteChoice choice = VoteChoice.valueOf(requestDto.getChoice()); // if user has voted this same thing before with the SAME CHOICE - if(!prevVote.isEmpty() && + if (!prevVote.isEmpty() && prevVote.get(0).getChoice().name().equals(requestDto.getChoice())) { - if(requestDto.getVoteType().equals(VoteType.REVIEW.name())){ - // delete previous vote - Review review = reviewRepository.findById(requestDto.getTypeId()).get(); - review.deleteVote(choice); - reviewRepository.save(review); - deleteVote(prevVote.get(0).getId()); - return prevVote.get(0); - } else if(requestDto.getVoteType().equals(VoteType.POST.name())){ - // delete previous vote - Optional optionalPost = postRepository.findById(requestDto.getTypeId()); - if (optionalPost.isEmpty()) { - throw new ResourceNotFoundException("Post with the given Id is not found."); - } - Post post = optionalPost.get(); - post.deleteVote(choice); - postRepository.save(post); - deleteVote(prevVote.get(0).getId()); - return prevVote.get(0); - } - - // TODO same logic will be extended to other models that have voting mechanism + if (requestDto.getVoteType().equals(VoteType.REVIEW.name())) { + // delete previous vote + Review review = reviewRepository.findById(requestDto.getTypeId()).get(); + review.deleteVote(choice); + reviewRepository.save(review); + deleteVote(prevVote.get(0).getId()); + return prevVote.get(0); + } else if (requestDto.getVoteType().equals(VoteType.POST.name())) { + // delete previous vote + Optional optionalPost = postRepository.findById(requestDto.getTypeId()); + if (optionalPost.isEmpty()) { + throw new ResourceNotFoundException("Post with the given Id is not found."); + } + Post post = optionalPost.get(); + post.deleteVote(choice); + postRepository.save(post); + deleteVote(prevVote.get(0).getId()); + return prevVote.get(0); + } else if (requestDto.getVoteType().equals(VoteType.COMMENT.name())) { + // delete previous vote + Optional optionalComment = commentRepository.findById(requestDto.getTypeId()); + if (optionalComment.isEmpty()) { + throw new ResourceNotFoundException("Comment with the given Id is not found."); + } + Comment comment = optionalComment.get(); + comment.deleteVote(choice); + commentRepository.save(comment); + deleteVote(prevVote.get(0).getId()); + return prevVote.get(0); + } + + // TODO same logic will be extended to other models that have voting mechanism } // if user has voted this same thing before but CHANGED HIS CHOICE - else if(!prevVote.isEmpty() && - !prevVote.get(0).getChoice().name().equals(requestDto.getChoice())){ + else if (!prevVote.isEmpty() && + !prevVote.get(0).getChoice().name().equals(requestDto.getChoice())) { - if(requestDto.getVoteType().equals(VoteType.REVIEW.name())){ + if (requestDto.getVoteType().equals(VoteType.REVIEW.name())) { // delete previous vote Review review = reviewRepository.findById(requestDto.getTypeId()).get(); review.deleteVote(prevVote.get(0).getChoice()); @@ -141,7 +153,7 @@ else if(!prevVote.isEmpty() && reviewRepository.save(review); return voteRepository.save(voteToCreate); - } else if(requestDto.getVoteType().equals(VoteType.POST.name())){ + } else if (requestDto.getVoteType().equals(VoteType.POST.name())) { // delete previous vote Optional optionalPost = postRepository.findById(requestDto.getTypeId()); if (optionalPost.isEmpty()) { @@ -156,21 +168,36 @@ else if(!prevVote.isEmpty() && postRepository.save(post); return voteRepository.save(voteToCreate); + } else if (requestDto.getVoteType().equals(VoteType.COMMENT.name())) { + // delete previous vote + Optional optionalComment = commentRepository.findById(requestDto.getTypeId()); + if (optionalComment.isEmpty()) { + throw new ResourceNotFoundException("Comment with the given Id is not found."); + } + Comment comment = optionalComment.get(); + comment.deleteVote(prevVote.get(0).getChoice()); + deleteVote(prevVote.get(0).getId()); + + // add new vote + comment.addVote(choice); + + commentRepository.save(comment); + return voteRepository.save(voteToCreate); } // TODO same logic will be extended to other models that have voting mechanism } // user votes something first time - else{ + else { - if(requestDto.getVoteType().equals(VoteType.REVIEW.name())){ + if (requestDto.getVoteType().equals(VoteType.REVIEW.name())) { // add new vote Review review = reviewRepository.findById(requestDto.getTypeId()).get(); review.addVote(choice); reviewRepository.save(review); return voteRepository.save(voteToCreate); - } else if(requestDto.getVoteType().equals(VoteType.POST.name())){ + } else if (requestDto.getVoteType().equals(VoteType.POST.name())) { // add new vote Optional optionalPost = postRepository.findById(requestDto.getTypeId()); if (optionalPost.isEmpty()) { @@ -180,6 +207,16 @@ else if(!prevVote.isEmpty() && post.addVote(choice); postRepository.save(post); return voteRepository.save(voteToCreate); + } else if (requestDto.getVoteType().equals(VoteType.COMMENT.name())) { + // delete previous vote + Optional optionalComment = commentRepository.findById(requestDto.getTypeId()); + if (optionalComment.isEmpty()) { + throw new ResourceNotFoundException("Comment with the given Id is not found."); + } + Comment comment = optionalComment.get(); + comment.addVote(choice); + commentRepository.save(comment); + return voteRepository.save(voteToCreate); } // TODO same logic will be extended to other models that have voting mechanism @@ -189,10 +226,10 @@ else if(!prevVote.isEmpty() && return voteToCreate; } - public Boolean deleteVote(String voteId){ + public Boolean deleteVote(String voteId) { Optional findResult = voteRepository.findById(voteId); - if(findResult.isEmpty()){ + if (findResult.isEmpty()) { throw new ResourceNotFoundException("Vote not found"); }