diff --git a/backend/src/main/java/com/openisle/controller/CommentController.java b/backend/src/main/java/com/openisle/controller/CommentController.java index 5de15888d..07eb4cb13 100644 --- a/backend/src/main/java/com/openisle/controller/CommentController.java +++ b/backend/src/main/java/com/openisle/controller/CommentController.java @@ -1,11 +1,13 @@ package com.openisle.controller; +import com.openisle.dto.CommentContextDto; import com.openisle.dto.CommentDto; import com.openisle.dto.CommentRequest; import com.openisle.dto.PostChangeLogDto; import com.openisle.dto.TimelineItemDto; import com.openisle.mapper.CommentMapper; import com.openisle.mapper.PostChangeLogMapper; +import com.openisle.mapper.PostMapper; import com.openisle.model.Comment; import com.openisle.model.CommentSort; import com.openisle.service.*; @@ -40,6 +42,7 @@ public class CommentController { private final PointService pointService; private final PostChangeLogService changeLogService; private final PostChangeLogMapper postChangeLogMapper; + private final PostMapper postMapper; @Value("${app.captcha.enabled:false}") private boolean captchaEnabled; @@ -184,6 +187,37 @@ public class CommentController { return itemDtoList; } + @GetMapping("/comments/{commentId}/context") + @Operation( + summary = "Comment context", + description = "Get a comment along with its previous comments and related post" + ) + @ApiResponse( + responseCode = "200", + description = "Comment context", + content = @Content(schema = @Schema(implementation = CommentContextDto.class)) + ) + public ResponseEntity getCommentContext(@PathVariable Long commentId) { + log.debug("getCommentContext called for comment {}", commentId); + Comment comment = commentService.getComment(commentId); + CommentContextDto dto = new CommentContextDto(); + dto.setPost(postMapper.toSummaryDto(comment.getPost())); + dto.setTargetComment(commentMapper.toDtoWithReplies(comment)); + dto.setPreviousComments( + commentService + .getCommentsBefore(comment) + .stream() + .map(commentMapper::toDtoWithReplies) + .collect(Collectors.toList()) + ); + log.debug( + "getCommentContext returning {} previous comments for comment {}", + dto.getPreviousComments().size(), + commentId + ); + return ResponseEntity.ok(dto); + } + @DeleteMapping("/comments/{id}") @Operation(summary = "Delete comment", description = "Delete a comment") @ApiResponse(responseCode = "200", description = "Deleted") diff --git a/backend/src/main/java/com/openisle/controller/PostController.java b/backend/src/main/java/com/openisle/controller/PostController.java index d59d4cb7b..86e27b06c 100644 --- a/backend/src/main/java/com/openisle/controller/PostController.java +++ b/backend/src/main/java/com/openisle/controller/PostController.java @@ -224,6 +224,26 @@ public class PostController { .collect(Collectors.toList()); } + @GetMapping("/recent") + @Operation( + summary = "Recent posts", + description = "List posts created within the specified number of minutes" + ) + @ApiResponse( + responseCode = "200", + description = "Recent posts", + content = @Content( + array = @ArraySchema(schema = @Schema(implementation = PostSummaryDto.class)) + ) + ) + public List recentPosts(@RequestParam("minutes") int minutes) { + return postService + .listRecentPosts(minutes) + .stream() + .map(postMapper::toSummaryDto) + .collect(Collectors.toList()); + } + @GetMapping("/ranking") @Operation(summary = "Ranking posts", description = "List posts by view rankings") @ApiResponse( diff --git a/backend/src/main/java/com/openisle/dto/CommentContextDto.java b/backend/src/main/java/com/openisle/dto/CommentContextDto.java new file mode 100644 index 000000000..3fc76ae48 --- /dev/null +++ b/backend/src/main/java/com/openisle/dto/CommentContextDto.java @@ -0,0 +1,15 @@ +package com.openisle.dto; + +import java.util.List; +import lombok.Data; + +/** + * DTO representing the context of a comment including its post and previous comments. + */ +@Data +public class CommentContextDto { + + private PostSummaryDto post; + private CommentDto targetComment; + private List previousComments; +} diff --git a/backend/src/main/java/com/openisle/repository/CommentRepository.java b/backend/src/main/java/com/openisle/repository/CommentRepository.java index 180ce5c49..3a0d2fe50 100644 --- a/backend/src/main/java/com/openisle/repository/CommentRepository.java +++ b/backend/src/main/java/com/openisle/repository/CommentRepository.java @@ -3,6 +3,7 @@ package com.openisle.repository; import com.openisle.model.Comment; import com.openisle.model.Post; import com.openisle.model.User; +import java.time.LocalDateTime; import java.util.List; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -10,6 +11,10 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface CommentRepository extends JpaRepository { List findByPostAndParentIsNullOrderByCreatedAtAsc(Post post); List findByParentOrderByCreatedAtAsc(Comment parent); + List findByPostAndCreatedAtLessThanOrderByCreatedAtAsc( + Post post, + LocalDateTime createdAt + ); List findByAuthorOrderByCreatedAtDesc(User author, Pageable pageable); List findByContentContainingIgnoreCase(String keyword); diff --git a/backend/src/main/java/com/openisle/repository/PostRepository.java b/backend/src/main/java/com/openisle/repository/PostRepository.java index a063602c5..107c692de 100644 --- a/backend/src/main/java/com/openisle/repository/PostRepository.java +++ b/backend/src/main/java/com/openisle/repository/PostRepository.java @@ -19,6 +19,10 @@ public interface PostRepository extends JpaRepository { List findByStatusOrderByCreatedAtDesc(PostStatus status, Pageable pageable); List findByStatusOrderByViewsDesc(PostStatus status); List findByStatusOrderByViewsDesc(PostStatus status, Pageable pageable); + List findByStatusAndCreatedAtGreaterThanEqualOrderByCreatedAtDesc( + PostStatus status, + LocalDateTime createdAt + ); List findByAuthorAndStatusOrderByCreatedAtDesc( User author, PostStatus status, diff --git a/backend/src/main/java/com/openisle/service/CommentService.java b/backend/src/main/java/com/openisle/service/CommentService.java index 9da1af2d7..908a5f30d 100644 --- a/backend/src/main/java/com/openisle/service/CommentService.java +++ b/backend/src/main/java/com/openisle/service/CommentService.java @@ -266,6 +266,27 @@ public class CommentService { return replies; } + public Comment getComment(Long commentId) { + log.debug("getComment called for id {}", commentId); + return commentRepository + .findById(commentId) + .orElseThrow(() -> new com.openisle.exception.NotFoundException("Comment not found")); + } + + public List getCommentsBefore(Comment comment) { + log.debug("getCommentsBefore called for comment {}", comment.getId()); + List comments = commentRepository.findByPostAndCreatedAtLessThanOrderByCreatedAtAsc( + comment.getPost(), + comment.getCreatedAt() + ); + log.debug( + "getCommentsBefore returning {} comments for comment {}", + comments.size(), + comment.getId() + ); + return comments; + } + public List getRecentCommentsByUser(String username, int limit) { log.debug("getRecentCommentsByUser called for user {} with limit {}", username, limit); User user = userRepository diff --git a/backend/src/main/java/com/openisle/service/PostService.java b/backend/src/main/java/com/openisle/service/PostService.java index cec2613dc..1d0d0f2df 100644 --- a/backend/src/main/java/com/openisle/service/PostService.java +++ b/backend/src/main/java/com/openisle/service/PostService.java @@ -770,6 +770,18 @@ public class PostService { return listPostsByCategories(null, null, null); } + public List listRecentPosts(int minutes) { + if (minutes <= 0) { + throw new IllegalArgumentException("Minutes must be positive"); + } + LocalDateTime since = LocalDateTime.now().minusMinutes(minutes); + List posts = postRepository.findByStatusAndCreatedAtGreaterThanEqualOrderByCreatedAtDesc( + PostStatus.PUBLISHED, + since + ); + return sortByPinnedAndCreated(posts); + } + public List listPostsByViews(Integer page, Integer pageSize) { return listPostsByViews(null, null, page, pageSize); }