diff --git a/src/main/java/meltingpot/server/comment/controller/CommentController.java b/src/main/java/meltingpot/server/comment/controller/CommentController.java index 26ffa17..4b5edd9 100644 --- a/src/main/java/meltingpot/server/comment/controller/CommentController.java +++ b/src/main/java/meltingpot/server/comment/controller/CommentController.java @@ -2,8 +2,8 @@ import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; -import meltingpot.server.comment.dto.CommentRequestDTO; -import meltingpot.server.comment.dto.CommentResponseDTO; +import meltingpot.server.comment.dto.CommentCreateRequest; +import meltingpot.server.comment.dto.CommentsListResponse; import meltingpot.server.comment.service.CommentService; import meltingpot.server.domain.entity.Account; import meltingpot.server.util.CurrentUser; @@ -22,47 +22,45 @@ public class CommentController { @Operation(summary = "댓글 작성") @PostMapping("/{postId}") - public ResponseEntity> createComment(@RequestBody CommentRequestDTO.CreateCommentDTO createCommentDTO, @CurrentUser Account account, @PathVariable Long postId) { + public ResponseEntity createComment(@RequestBody CommentCreateRequest commentCreateRequest, @CurrentUser Account account, @PathVariable Long postId) { try { - return ResponseData.toResponseEntity(ResponseCode.CREATE_COMMENT_SUCCESS, commentService.createComment(createCommentDTO,account,postId)); + return ResponseData.toResponseEntity(commentService.createComment(commentCreateRequest,account,postId)); } catch (NoSuchElementException e) { - return ResponseData.toResponseEntity(ResponseCode.COMMENT_CREATE_FAIL, null); + return ResponseData.toResponseEntity(ResponseCode.COMMENT_CREATE_FAIL); } } @Operation(summary = "대댓글 작성") @PostMapping("/child/{commentId}") - public ResponseEntity> createChildComment(@RequestBody CommentRequestDTO.CreateCommentDTO createCommentDTO, @CurrentUser Account account,@PathVariable Long commentId) { + public ResponseEntity createChildComment(@RequestBody CommentCreateRequest commentCreateRequest, @CurrentUser Account account,@PathVariable Long commentId) { try { - return ResponseData.toResponseEntity(ResponseCode.CREATE_CHILD_COMMENT_SUCCESS, commentService.createChildComment(createCommentDTO,account,commentId)); + return ResponseData.toResponseEntity(commentService.createChildComment(commentCreateRequest,account,commentId)); } catch (NoSuchElementException e) { - return ResponseData.toResponseEntity(ResponseCode.COMMENT_CREATE_FAIL, null); + return ResponseData.toResponseEntity(ResponseCode.COMMENT_CREATE_FAIL); } } @Operation(summary = "댓글 수정") @PutMapping("/{commentId}") - public ResponseEntity> updateComment(@RequestBody CommentRequestDTO.CreateCommentDTO createCommentDTO, @CurrentUser Account account, @PathVariable Long commentId){ + public ResponseEntity updateComment(@RequestBody CommentCreateRequest commentCreateRequest, @CurrentUser Account account, @PathVariable Long commentId){ try { - return ResponseData.toResponseEntity(ResponseCode.UPDATE_COMMENT_SUCCESS, commentService.updateComment(createCommentDTO,account,commentId)); + return ResponseData.toResponseEntity(commentService.updateComment(commentCreateRequest,account,commentId)); } catch (NoSuchElementException e) { - return ResponseData.toResponseEntity(ResponseCode.COMMENT_UPDATE_FAIL, null); + return ResponseData.toResponseEntity(ResponseCode.COMMENT_UPDATE_FAIL); } } @Operation(summary = "댓글 목록 가져오기") @GetMapping("/list/{postId}") - public ResponseEntity> getCommentsList ( @CurrentUser Account account, @PathVariable Long postId, - @RequestParam(required = false) Long cursor, - @RequestParam(defaultValue = "10") int pageSize){ + public ResponseEntity> getCommentsList (@CurrentUser Account account, @PathVariable Long postId, + @RequestParam(required = false) Long cursor, + @RequestParam(defaultValue = "10") int pageSize){ try{ return ResponseData.toResponseEntity(ResponseCode.READ_COMMENTS_LIST_SUCCESS, commentService.getCommentsList(account,postId,cursor,pageSize)); }catch (NoSuchElementException e) { return ResponseData.toResponseEntity(ResponseCode.READ_COMMENT_FAIL, null); } } - - } diff --git a/src/main/java/meltingpot/server/comment/converter/CommentConverter.java b/src/main/java/meltingpot/server/comment/converter/CommentConverter.java deleted file mode 100644 index 99874fb..0000000 --- a/src/main/java/meltingpot/server/comment/converter/CommentConverter.java +++ /dev/null @@ -1,69 +0,0 @@ -package meltingpot.server.comment.converter; - -import lombok.RequiredArgsConstructor; -import meltingpot.server.comment.dto.CommentRequestDTO; -import meltingpot.server.comment.dto.CommentResponseDTO; -import meltingpot.server.domain.entity.Account; -import meltingpot.server.domain.entity.comment.Comment; -import meltingpot.server.domain.entity.post.Post; - -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -@RequiredArgsConstructor -public class CommentConverter { - public static Comment toComment(CommentRequestDTO.CreateCommentDTO createCommentDTO, Account account, Post post) { - return Comment.builder() - .content(createCommentDTO.getContent()) - .isAnonymous(createCommentDTO.getIsAnonymous()) - .post(post) - .account(account) - .build(); - } - - public static Comment toChildComment(CommentRequestDTO.CreateCommentDTO createCommentDTO, Account account,Comment parentComment){ - return Comment.builder() - .content(createCommentDTO.getContent()) - .isAnonymous(createCommentDTO.getIsAnonymous()) - .parent(parentComment) - .account(account) - .build(); - } - public static CommentResponseDTO.CreateCommentResultDTO toCreateCommentResult(String url,Comment comment){ - return CommentResponseDTO.CreateCommentResultDTO.builder() - .commentId(comment.getId()) - .commentImageUrl(url) - .build(); - } - - public static CommentResponseDTO.CommentDetailDTO toCommentDetailDTO(Comment comment) { - return CommentResponseDTO.CommentDetailDTO.builder() - .commentId(comment.getId()) - .parentId(comment.getParent() != null ? comment.getParent().getId() : null) - .userId(comment.getAccount().getId()) - .content(comment.getContent()) - .name(comment.getAccount().getName()) - .isAnonymous(comment.getIsAnonymous()) - .imageUrl(comment.getCommentImage().getImageKey()) -// .children(comment.getChildren() != null ? comment.getChildren().stream() -// .map(child -> toCommentDetailDTO(child)) -// .collect(Collectors.toList()) : Collections.emptyList()) - .updatedAt(comment.getUpdatedAt()) - .build(); - } - - public static List toCommentDetailDTOList(List comments) { - return comments.stream() - .map(CommentConverter::toCommentDetailDTO) - .collect(Collectors.toList()); - } - - public static CommentResponseDTO.CommentsListDTO toCommentsListDTO(List comments, Long nextCursor, Boolean isLast){ - return CommentResponseDTO.CommentsListDTO.builder() - .comments(comments) - .nextCursor(nextCursor) - .isLast(isLast) - .build(); - } -} \ No newline at end of file diff --git a/src/main/java/meltingpot/server/comment/converter/CommentImageConverter.java b/src/main/java/meltingpot/server/comment/converter/CommentImageConverter.java deleted file mode 100644 index 729f3ec..0000000 --- a/src/main/java/meltingpot/server/comment/converter/CommentImageConverter.java +++ /dev/null @@ -1,28 +0,0 @@ -package meltingpot.server.comment.converter; - -import meltingpot.server.comment.dto.CommentRequestDTO; -import meltingpot.server.domain.entity.Account; -import meltingpot.server.domain.entity.comment.Comment; -import meltingpot.server.domain.entity.comment.CommentImage; - - -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - - -public class CommentImageConverter { - - public static CommentImage toCommentImage(CommentRequestDTO.CreateCommentDTO createCommentDTO, Account account, Comment comment) { - if (createCommentDTO.getImageKey() == null || createCommentDTO.getImageKey().isEmpty()) { - return null; - } - - return CommentImage.builder() - .imageKey(createCommentDTO.getImageKey()) - .comment(comment) - .account(account) - .build(); - } -} - diff --git a/src/main/java/meltingpot/server/comment/dto/CommentCreateRequest.java b/src/main/java/meltingpot/server/comment/dto/CommentCreateRequest.java new file mode 100644 index 0000000..03d090b --- /dev/null +++ b/src/main/java/meltingpot/server/comment/dto/CommentCreateRequest.java @@ -0,0 +1,27 @@ +package meltingpot.server.comment.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import meltingpot.server.domain.entity.Account; +import meltingpot.server.domain.entity.comment.Comment; +import meltingpot.server.domain.entity.post.Post; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class CommentCreateRequest { + private String content; + private Boolean isAnonymous; + private String imageKey; + + public Comment toEntity(Post post, Account account, Comment parentComment){ + return Comment.builder() + .content(content) + .isAnonymous(isAnonymous) + .post(post) + .account(account) + .parent(parentComment) + .build(); + } +} diff --git a/src/main/java/meltingpot/server/comment/dto/CommentRequestDTO.java b/src/main/java/meltingpot/server/comment/dto/CommentRequestDTO.java deleted file mode 100644 index 2185976..0000000 --- a/src/main/java/meltingpot/server/comment/dto/CommentRequestDTO.java +++ /dev/null @@ -1,27 +0,0 @@ -package meltingpot.server.comment.dto; - -import lombok.Getter; - -import java.util.List; - -public class CommentRequestDTO { - - @Getter - public static class CreateCommentDTO{ - private String content; - private Boolean isAnonymous; - private String imageKey; - } - - @Getter - public class CreateChildCommentDTO { - private String content; - private boolean isAnonymous; - private String imageKey; - private Long postId; - private Long accountId; - private Long parentId; - - // Getter와 Setter 생략 - } -} diff --git a/src/main/java/meltingpot/server/comment/dto/CommentResponseDTO.java b/src/main/java/meltingpot/server/comment/dto/CommentResponseDTO.java deleted file mode 100644 index 5119d99..0000000 --- a/src/main/java/meltingpot/server/comment/dto/CommentResponseDTO.java +++ /dev/null @@ -1,51 +0,0 @@ -package meltingpot.server.comment.dto; - -import lombok.*; -import meltingpot.server.domain.entity.comment.Comment; - -import java.time.LocalDateTime; -import java.util.List; - -public class CommentResponseDTO { - - @Builder - @Getter - @Setter - @NoArgsConstructor(access = AccessLevel.PROTECTED) - @AllArgsConstructor(access = AccessLevel.PROTECTED) - public static class CreateCommentResultDTO{ - private Long commentId; - private String commentImageUrl; - } - - - @Builder - @Getter - @Setter - @NoArgsConstructor(access = AccessLevel.PROTECTED) - @AllArgsConstructor(access = AccessLevel.PROTECTED) - public static class CommentDetailDTO { - private Long commentId; - private Long parentId; - private Long userId; - private String content; - private String name; - private Boolean isAnonymous; - private String imageUrl; - private LocalDateTime updatedAt; - } - - - - @Builder - @Getter - @NoArgsConstructor(access = AccessLevel.PROTECTED) - @AllArgsConstructor(access = AccessLevel.PROTECTED) - public static class CommentsListDTO{ - private List comments; - private Long nextCursor; - private Boolean isLast; - } - - -} diff --git a/src/main/java/meltingpot/server/comment/dto/CommentsListResponse.java b/src/main/java/meltingpot/server/comment/dto/CommentsListResponse.java new file mode 100644 index 0000000..4b2e048 --- /dev/null +++ b/src/main/java/meltingpot/server/comment/dto/CommentsListResponse.java @@ -0,0 +1,57 @@ +package meltingpot.server.comment.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import meltingpot.server.domain.entity.comment.Comment; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class CommentsListResponse { + private List commentsList; + private Long nextCursor; + private Boolean isLast; + + @Getter + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class CommentDetail { + private Long commentId; + private Long parentId; + private Long userId; + private String content; + private String name; + private Boolean isAnonymous; + private String imageUrl; + private LocalDateTime updatedAt; + + public static CommentDetail from(Comment comment) { + return CommentDetail.builder() + .commentId(comment.getId()) + .parentId(comment.getParent() != null ? comment.getParent().getId() : null) + .userId(comment.getAccount().getId()) + .content(comment.getContent()) + .name(comment.getAccount().getName()) + .isAnonymous(comment.getIsAnonymous()) + .imageUrl(comment.getCommentImage() != null ? comment.getCommentImage().getImageUrl() : null) + .updatedAt(comment.getUpdatedAt()) + .build(); + } + } + + public static CommentsListResponse from(List commentDetails, Long nextCursor, Boolean isLast) { + return CommentsListResponse.builder() + .commentsList(commentDetails) + .nextCursor(nextCursor) + .isLast(isLast) + .build(); + } +} diff --git a/src/main/java/meltingpot/server/comment/service/CommentService.java b/src/main/java/meltingpot/server/comment/service/CommentService.java index 50bea8c..4b2b359 100644 --- a/src/main/java/meltingpot/server/comment/service/CommentService.java +++ b/src/main/java/meltingpot/server/comment/service/CommentService.java @@ -1,16 +1,248 @@ package meltingpot.server.comment.service; -import meltingpot.server.comment.dto.CommentRequestDTO; -import meltingpot.server.comment.dto.CommentResponseDTO; +import lombok.RequiredArgsConstructor; +import meltingpot.server.comment.dto.CommentCreateRequest; +import meltingpot.server.comment.dto.CommentsListResponse; import meltingpot.server.domain.entity.Account; -import meltingpot.server.post.dto.PostRequestDTO; +import meltingpot.server.domain.entity.comment.Comment; +import meltingpot.server.domain.entity.comment.CommentImage; +import meltingpot.server.domain.entity.post.Post; +import meltingpot.server.domain.repository.AccountRepository; +import meltingpot.server.domain.repository.CommentImageRepository; +import meltingpot.server.domain.repository.CommentRepository; +import meltingpot.server.domain.repository.PostRepository; +import meltingpot.server.util.ResponseCode; +import meltingpot.server.util.r2.FileService; +import org.springframework.data.domain.PageRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -public interface CommentService { - CommentResponseDTO.CreateCommentResultDTO createComment (CommentRequestDTO.CreateCommentDTO createCommentDTO, Account account, Long postId); +import java.util.*; +import java.util.stream.Collectors; - CommentResponseDTO.CreateCommentResultDTO createChildComment (CommentRequestDTO.CreateCommentDTO createCommentDTO, Account account, Long commentId); +@Service +@RequiredArgsConstructor +@Transactional +public class CommentService { + private final CommentRepository commentRepository; + private final AccountRepository accountRepository; + private final PostRepository postRepository; + private final CommentImageRepository commentImageRepository; - CommentResponseDTO.CreateCommentResultDTO updateComment (CommentRequestDTO.CreateCommentDTO updateCommentDTO,Account account, Long commentId); + @Autowired + private FileService fileService; + + + /* 댓글 작성 */ + public ResponseCode createComment(CommentCreateRequest commentCreateRequest , Account account, Long postId) { + Post post = findPostById(postId); + Comment comment = commentCreateRequest.toEntity(post, account, null); + + if (commentCreateRequest.getImageKey() != null && !commentCreateRequest.getImageKey().isEmpty()) { + String commentImgUrl = getCdnUrl(commentCreateRequest.getImageKey()); + createCommentImage(comment, account, commentImgUrl); + } + + commentRepository.save(comment); + return ResponseCode.CREATE_COMMENT_SUCCESS; + } + + /* 대댓글 작성 */ + public ResponseCode createChildComment(CommentCreateRequest commentCreateRequest, Account account, Long parentCommentId) { + Comment parentComment = findCommentById(parentCommentId); + Post post = parentComment.getPost(); + Comment childComment = commentCreateRequest.toEntity(post, account, parentComment); + + if (commentCreateRequest.getImageKey() != null && !commentCreateRequest.getImageKey().isEmpty()) { + String commentImgUrl = getCdnUrl(commentCreateRequest.getImageKey()); + createCommentImage(childComment, account, commentImgUrl); + } + + commentRepository.save(childComment); + return ResponseCode.CREATE_CHILD_COMMENT_SUCCESS; + } + + /* 댓글 수정 */ + public ResponseCode updateComment(CommentCreateRequest updateCommentDTO, Account account, Long commentId) { + Comment comment = findCommentById(commentId); + comment.setContent(updateCommentDTO.getContent()); + + String newImageKey = updateCommentDTO.getImageKey(); + updateCommentImage(comment, account, newImageKey); + + commentRepository.save(comment); + return ResponseCode.UPDATE_COMMENT_SUCCESS; + } + + + + /* 댓글 목록 불러오기 */ +// @Transactional(readOnly = true) +// public CommentsListResponse getCommentsList(Account account, Long postId, Long cursor, int pageSize) { +// List commentDetailDTOs = new ArrayList<>(); +// int count = 0; +// Long parentCursor = null; +// +// // Cursor가 자식 댓글에 해당하는 경우 처리 +// if (cursor != null) { +// // Cursor가 부모 댓글이 아닌 자식 댓글을 나타내는 경우 +// Comment childComment = commentRepository.findById(cursor).orElse(null); +// if (childComment != null && childComment.getParent() != null) { +// Comment parentComment = childComment.getParent(); +// List remainingChildren = commentRepository.findChildrenCommentsByParentId(parentComment.getId(), cursor); +// for (Comment child : remainingChildren) { +// if (count >= pageSize) break; +// commentDetailDTOs.add(CommentsListResponse.CommentDetail.from(child)); +// count++; +// } +// parentCursor = parentComment.getId(); +// +// // 만약 자식 댓글을 모두 가져왔고, 페이지가 꽉 차지 않았다면 다음 부모 댓글로 넘어감 +// if (count < pageSize && remainingChildren.size() < pageSize) { +// parentCursor = parentComment.getId(); +// } +// } else { +// parentCursor = cursor; // cursor가 부모 댓글인 경우 +// } +// } +// +// // 부모 댓글과 자식 댓글을 가져오는 처리 +// if (count < pageSize) { +// Pageable pageable = PageRequest.of(0, pageSize - count); +// List parentComments = commentRepository.findParentCommentsByPostId(postId, parentCursor, pageable); +// for (Comment parent : parentComments) { +// if (count >= pageSize) break; +// commentDetailDTOs.add(CommentsListResponse.CommentDetail.from(parent)); +// count++; +// +// List children = commentRepository.findChildrenCommentsByParentId(parent.getId(), null); +// for (Comment child : children) { +// if (count >= pageSize) break; +// commentDetailDTOs.add(CommentsListResponse.CommentDetail.from(child)); +// count++; +// } +// } +// } +// +// Long nextCursor = (count < pageSize) ? null : commentDetailDTOs.get(commentDetailDTOs.size() - 1).getCommentId(); +// boolean isLast = (count < pageSize); +// +// return CommentsListResponse.from(commentDetailDTOs,nextCursor,isLast); +// } + + public CommentsListResponse getCommentsList(Account account, Long postId, Long cursor, int pageSize) { + List commentDetailDTOs = new ArrayList<>(); + int count = 0; + Long parentCursor = null; + + // Cursor가 자식 댓글에 해당하는 경우 처리 + if (cursor != null) { + Comment cursorComment = commentRepository.findById(cursor).orElse(null); + if (cursorComment != null) { + Comment parentComment; + if (cursorComment.getParent() != null) { + // Cursor가 자식 댓글을 나타내는 경우 + parentComment = cursorComment.getParent(); + } else { + // Cursor가 부모 댓글을 나타내는 경우 + parentComment = cursorComment; + } + + List remainingChildren = commentRepository.findChildrenCommentsByParentId(parentComment.getId(), cursor); + for (Comment child : remainingChildren) { + if (count >= pageSize) break; + commentDetailDTOs.add(CommentsListResponse.CommentDetail.from(child)); + count++; + } + parentCursor = parentComment.getId(); + + // 만약 자식 댓글을 모두 가져왔고, 페이지가 꽉 차지 않았다면 다음 부모 댓글로 넘어감 + if (count < pageSize && remainingChildren.size() < pageSize) { + parentCursor = parentComment.getId(); + } + } else { + parentCursor = cursor; // cursor가 부모 댓글인 경우 + } + } + + // 부모 댓글과 자식 댓글을 가져오는 처리 + if (count < pageSize) { + Pageable pageable = PageRequest.of(0, pageSize - count); + List parentComments = commentRepository.findParentCommentsByPostId(postId, parentCursor, pageable); + for (Comment parent : parentComments) { + if (count >= pageSize) break; + commentDetailDTOs.add(CommentsListResponse.CommentDetail.from(parent)); + count++; + + List children = commentRepository.findChildrenCommentsByParentId(parent.getId(), null); + for (Comment child : children) { + if (count >= pageSize) break; + commentDetailDTOs.add(CommentsListResponse.CommentDetail.from(child)); + count++; + } + } + } + + Long nextCursor = (count < pageSize) ? null : commentDetailDTOs.get(commentDetailDTOs.size() - 1).getCommentId(); + boolean isLast = (count < pageSize); + + return CommentsListResponse.from(commentDetailDTOs, nextCursor, isLast); + } + + + + + private Comment findCommentById(Long commentId) { + return commentRepository.findById(commentId) + .orElseThrow(() -> new RuntimeException("댓글을 찾을 수 없습니다.")); + } + + + private Account findAccountById(Long accountId) { + return accountRepository.findById(accountId) + .orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다.")); + } + + private Post findPostById(Long postId) { + return postRepository.findById(postId) + .orElseThrow(() -> new RuntimeException("게시물을 찾을 수 없습니다.")); + } + + private String getCdnUrl(String imageKey) { + String prefix = "comment"; // 적절한 prefix 값을 설정 + return fileService.getCdnUrl(prefix, imageKey); + } + + private void createCommentImage(Comment comment, Account account, String imageUrl) { + CommentImage commentImage = CommentImage.builder() + .imageUrl(imageUrl) + .comment(comment) + .account(account) + .build(); + comment.setCommentImage(commentImage); + } + + private void updateCommentImage(Comment comment, Account account, String newImageKey) { + CommentImage oldCommentImage = comment.getCommentImage(); + if (newImageKey == null || newImageKey.isEmpty()) { + if (oldCommentImage != null) { + commentImageRepository.delete(oldCommentImage); + comment.setCommentImage(null); + } + } else { + String newImageUrl = getCdnUrl(newImageKey); + if (oldCommentImage != null) { + oldCommentImage.setImageUrl(newImageUrl); + commentImageRepository.save(oldCommentImage); + } else { + createCommentImage(comment, account, newImageUrl); + } + } + } - CommentResponseDTO.CommentsListDTO getCommentsList(Account account, Long postId, Long cursor, int pageSize); } + + + diff --git a/src/main/java/meltingpot/server/comment/service/CommentServiceImpl.java b/src/main/java/meltingpot/server/comment/service/CommentServiceImpl.java deleted file mode 100644 index 3175970..0000000 --- a/src/main/java/meltingpot/server/comment/service/CommentServiceImpl.java +++ /dev/null @@ -1,184 +0,0 @@ -package meltingpot.server.comment.service; - -import lombok.RequiredArgsConstructor; -import meltingpot.server.comment.converter.CommentConverter; -import meltingpot.server.comment.dto.CommentRequestDTO; -import meltingpot.server.comment.dto.CommentResponseDTO; -import meltingpot.server.domain.entity.Account; -import meltingpot.server.domain.entity.comment.Comment; -import meltingpot.server.domain.entity.comment.CommentImage; -import meltingpot.server.domain.entity.post.Post; -import meltingpot.server.domain.repository.AccountRepository; -import meltingpot.server.domain.repository.CommentImageRepository; -import meltingpot.server.domain.repository.CommentRepository; -import meltingpot.server.domain.repository.PostRepository; -import meltingpot.server.util.r2.FileService; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.*; -import java.util.stream.Collectors; - -import static meltingpot.server.comment.converter.CommentConverter.*; -import static meltingpot.server.comment.converter.CommentImageConverter.toCommentImage; - -@Service -@RequiredArgsConstructor -@Transactional -public class CommentServiceImpl implements CommentService { - private final CommentRepository commentRepository; - private final AccountRepository accountRepository; - private final PostRepository postRepository; - private final CommentImageRepository commentImageRepository; - - @Autowired - private FileService fileService; - - - @Override - public CommentResponseDTO.CreateCommentResultDTO createComment(CommentRequestDTO.CreateCommentDTO createCommentDTO, Account account, Long postId) { - Post post = findPostById(postId); - Comment comment = toComment(createCommentDTO, account, post); - return processCommentCreation(createCommentDTO, account, comment); - } - - @Override - public CommentResponseDTO.CreateCommentResultDTO createChildComment(CommentRequestDTO.CreateCommentDTO createCommentDTO, Account account, Long commentId) { - Comment parentComment = findCommentById(commentId); - Comment childComment = CommentConverter.toChildComment(createCommentDTO, account, parentComment); - return processCommentCreation(createCommentDTO, account, childComment); - } - - @Override - public CommentResponseDTO.CreateCommentResultDTO updateComment(CommentRequestDTO.CreateCommentDTO updateCommentDTO, Account account, Long commentId) { - // 댓글을 ID로 찾기 - Comment comment = findCommentById(commentId); - - // 댓글 내용 업데이트 - comment.setContent(updateCommentDTO.getContent()); - - // 새로운 이미지 키와 기존 댓글 이미지 가져오기 - String newImageKey = updateCommentDTO.getImageKey(); - CommentImage oldCommentImage = comment.getCommentImage(); - - if (newImageKey == null || newImageKey.isEmpty()) { - // 새로운 이미지 키가 없을 경우 기존 이미지 삭제 - if (oldCommentImage != null) { - commentImageRepository.delete(oldCommentImage); - comment.setCommentImage(null); - } - } else { - // 새로운 이미지 키가 있을 경우 이미지 업데이트 또는 생성 - if (oldCommentImage != null) { - oldCommentImage.setImageKey(newImageKey); - commentImageRepository.save(oldCommentImage); - } else { - CommentImage newCommentImage = toCommentImage(updateCommentDTO, account, comment); - comment.setCommentImage(newCommentImage); - } - } - - - // 댓글 저장 - commentRepository.save(comment); - - // 결과 DTO 생성 및 반환 - String commentImgUrl = newImageKey != null && !newImageKey.isEmpty() ? fileService.getCdnUrl("comment", newImageKey) : null; - return toCreateCommentResult(commentImgUrl, comment); - } - - public CommentResponseDTO.CommentsListDTO getCommentsList(Account account, Long postId, Long cursor, int pageSize) { - List commentDetailDTOs = new ArrayList<>(); - int count = 0; - Long parentCursor = null; - - // Cursor가 자식 댓글에 해당하는 경우 처리 - if (cursor != null) { - // Cursor가 부모 댓글이 아닌 자식 댓글을 나타내는 경우 - Comment childComment = commentRepository.findById(cursor).orElse(null); - if (childComment != null && childComment.getParent() != null) { - Comment parentComment = childComment.getParent(); - List remainingChildren = commentRepository.findChildrenCommentsByParentId(parentComment.getId(), cursor); - for (Comment child : remainingChildren) { - if (count >= pageSize) break; - commentDetailDTOs.add(toCommentDetailDTO(child)); - count++; - } - parentCursor = parentComment.getId(); - - // 만약 자식 댓글을 모두 가져왔고, 페이지가 꽉 차지 않았다면 다음 부모 댓글로 넘어감 - if (count < pageSize && remainingChildren.size() < pageSize) { - parentCursor = parentComment.getId(); - } - } else { - parentCursor = cursor; // cursor가 부모 댓글인 경우 - } - } - - // 부모 댓글과 자식 댓글을 가져오는 처리 - if (count < pageSize) { - Pageable pageable = PageRequest.of(0, pageSize - count); - List parentComments = commentRepository.findParentCommentsByPostId(postId, parentCursor, pageable); - for (Comment parent : parentComments) { - if (count >= pageSize) break; - commentDetailDTOs.add(toCommentDetailDTO(parent)); - count++; - - List children = commentRepository.findChildrenCommentsByParentId(parent.getId(), null); - for (Comment child : children) { - if (count >= pageSize) break; - commentDetailDTOs.add(toCommentDetailDTO(child)); - count++; - } - } - } - - Long nextCursor = (count < pageSize) ? null : commentDetailDTOs.get(commentDetailDTOs.size() - 1).getCommentId(); - boolean isLast = (count < pageSize); - - return toCommentsListDTO(commentDetailDTOs,nextCursor,isLast); - } - - - - - - - - private CommentResponseDTO.CreateCommentResultDTO processCommentCreation(CommentRequestDTO.CreateCommentDTO createCommentDTO, Account account, Comment comment) { - String commentImgUrl = null; - if (createCommentDTO.getImageKey() != null && !createCommentDTO.getImageKey().isEmpty()) { - String prefix = "comment"; - commentImgUrl = fileService.getCdnUrl(prefix, createCommentDTO.getImageKey()); - } - CommentImage commentImage = toCommentImage(createCommentDTO, account, comment); - comment.setCommentImage(commentImage); - commentRepository.save(comment); - - return toCreateCommentResult(commentImgUrl, comment); - } - - private Comment findCommentById(Long commentId) { - return commentRepository.findById(commentId) - .orElseThrow(() -> new RuntimeException("댓글을 찾을 수 없습니다.")); - } - - - private Account findAccountById(Long accountId) { - return accountRepository.findById(accountId) - .orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다.")); - } - - private Post findPostById(Long postId) { - return postRepository.findById(postId) - .orElseThrow(() -> new RuntimeException("게시물을 찾을 수 없습니다.")); - } - -} - - - diff --git a/src/main/java/meltingpot/server/domain/entity/comment/Comment.java b/src/main/java/meltingpot/server/domain/entity/comment/Comment.java index 4ca2859..8646bef 100644 --- a/src/main/java/meltingpot/server/domain/entity/comment/Comment.java +++ b/src/main/java/meltingpot/server/domain/entity/comment/Comment.java @@ -2,7 +2,6 @@ import jakarta.persistence.*; import lombok.*; -import meltingpot.server.comment.dto.CommentResponseDTO; import meltingpot.server.domain.entity.Account; import meltingpot.server.domain.entity.post.Post; import meltingpot.server.domain.entity.common.BaseEntity; diff --git a/src/main/java/meltingpot/server/domain/entity/comment/CommentImage.java b/src/main/java/meltingpot/server/domain/entity/comment/CommentImage.java index 6be822f..5776a91 100644 --- a/src/main/java/meltingpot/server/domain/entity/comment/CommentImage.java +++ b/src/main/java/meltingpot/server/domain/entity/comment/CommentImage.java @@ -21,7 +21,7 @@ public class CommentImage extends BaseEntity { private Long id; @NotNull - private String imageKey; + private String imageUrl; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "comment_id") @@ -34,7 +34,7 @@ public class CommentImage extends BaseEntity { public void setComment(Comment comment) { this.comment = comment; } - public void setImageKey(String imageKey) { - this.imageKey = imageKey; + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; } } diff --git a/src/main/java/meltingpot/server/domain/entity/post/PostImage.java b/src/main/java/meltingpot/server/domain/entity/post/PostImage.java index a64539f..b9e7e32 100644 --- a/src/main/java/meltingpot/server/domain/entity/post/PostImage.java +++ b/src/main/java/meltingpot/server/domain/entity/post/PostImage.java @@ -21,10 +21,7 @@ public class PostImage extends BaseEntity { private Long id; @NotNull - private String imageKey; - - @NotNull - private String postImageOriginalName; + private String imageUrl; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "post_id") diff --git a/src/main/java/meltingpot/server/domain/repository/CommentRepository.java b/src/main/java/meltingpot/server/domain/repository/CommentRepository.java index 13a5b34..b0e78e6 100644 --- a/src/main/java/meltingpot/server/domain/repository/CommentRepository.java +++ b/src/main/java/meltingpot/server/domain/repository/CommentRepository.java @@ -22,8 +22,8 @@ public interface CommentRepository extends JpaRepository { - @Query("SELECT c FROM Comment c WHERE c.post.id = :postId AND c.parent IS NULL AND (:cursor IS NULL OR c.id > :cursor) ORDER BY c.id ASC") - List findParentCommentsByPostId(@Param("postId") Long postId, @Param("cursor") Long cursor, Pageable pageable); + @Query("SELECT c FROM Comment c WHERE c.post.id = :postId AND c.parent IS NULL AND (:parentCursor IS NULL OR c.id > :parentCursor) ORDER BY c.id ASC") + List findParentCommentsByPostId(@Param("postId") Long postId, @Param("parentCursor") Long parentCursor, Pageable pageable); @Query("SELECT c FROM Comment c WHERE c.parent.id = :parentId AND (:cursor IS NULL OR c.id > :cursor) ORDER BY c.id ASC") List findChildrenCommentsByParentId(@Param("parentId") Long parentId, @Param("cursor") Long cursor); @@ -31,4 +31,6 @@ public interface CommentRepository extends JpaRepository { Slice findAllByAccountAndDeletedAtIsNullOrderByIdDesc(Account account, Pageable pageable); + + } diff --git a/src/main/java/meltingpot/server/domain/repository/PostRepository.java b/src/main/java/meltingpot/server/domain/repository/PostRepository.java index da200a8..919f43a 100644 --- a/src/main/java/meltingpot/server/domain/repository/PostRepository.java +++ b/src/main/java/meltingpot/server/domain/repository/PostRepository.java @@ -7,14 +7,18 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.data.domain.Pageable; +import java.util.List; + public interface PostRepository extends JpaRepository { - Page findByPostType(@Param("postType") PostType postType, Pageable pageable); + @Query("SELECT p FROM Post p WHERE p.postType = :postType AND (:cursor IS NULL OR p.id > :cursor) ORDER BY p.id ASC") + List findByPostTypeAndCursor(@Param("postType") PostType postType, @Param("cursor") Long cursor, Pageable pageable); Slice findAllByAccountAndDeletedAtIsNullOrderByIdDesc(Account account, Pageable page); diff --git a/src/main/java/meltingpot/server/post/controller/PostController.java b/src/main/java/meltingpot/server/post/controller/PostController.java index 1d3f2bf..e857af7 100644 --- a/src/main/java/meltingpot/server/post/controller/PostController.java +++ b/src/main/java/meltingpot/server/post/controller/PostController.java @@ -1,21 +1,21 @@ package meltingpot.server.post.controller; +import lombok.RequiredArgsConstructor; +import java.util.NoSuchElementException; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; -import lombok.RequiredArgsConstructor; +import meltingpot.server.post.dto.PostDetailResponse; +import meltingpot.server.post.dto.PostsListResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import meltingpot.server.util.*; import meltingpot.server.domain.entity.Account; import meltingpot.server.domain.entity.enums.PostType; -import meltingpot.server.post.dto.PostRequestDTO; -import meltingpot.server.post.dto.PostResponseDTO; +import meltingpot.server.post.dto.PostCreateRequest; import meltingpot.server.post.service.PostService; -import meltingpot.server.util.*; -import org.springframework.data.domain.PageRequest; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.data.domain.Pageable; -import java.util.NoSuchElementException; + @RestController @@ -26,11 +26,11 @@ public class PostController { @Operation(summary = "게시물 작성") @PostMapping("") - public ResponseEntity> createPost( @CurrentUser Account account,@RequestBody PostRequestDTO.CreatePostDTO createPostDTO) { + public ResponseEntity createPost( @CurrentUser Account account,@RequestBody PostCreateRequest createPostDTO) { try{ - return ResponseData.toResponseEntity(ResponseCode.CREATE_POST_SUCCESS, postService.createPost(createPostDTO,account)); + return ResponseData.toResponseEntity(postService.createPost(createPostDTO,account)); }catch (NoSuchElementException e) { - return ResponseData.toResponseEntity(ResponseCode.POST_CREATE_FAIL,null); + return ResponseData.toResponseEntity(ResponseCode.POST_CREATE_FAIL); } } @@ -40,10 +40,9 @@ public ResponseEntity> createP @ApiResponse(responseCode = "OK", description = "커뮤니티 글 목록 조회 성공"), @ApiResponse(responseCode = "NOT_FOUND", description = "커뮤니티 글을 찾을 수 없습니다") }) - public ResponseEntity> getPostList(@PathVariable PostType postType, @CurrentUser Account account, @RequestParam(name = "cursor") Long cursor, @RequestParam(name = "pageSize") Integer pageSize) { - Pageable pageable = (Pageable) PageRequest.of(0, pageSize); + public ResponseEntity> getPostList(@CurrentUser Account account, @PathVariable PostType postType , @RequestParam(name = "cursor") Long cursor, @RequestParam(name = "pageSize") Integer pageSize) { try { - return ResponseData.toResponseEntity(ResponseCode.POST_LIST_FETCH_SUCCESS, postService.getPostsList(postType,account,cursor, pageable)); + return ResponseData.toResponseEntity(ResponseCode.POST_LIST_FETCH_SUCCESS, postService.getPostsList(account,postType, cursor, pageSize)); } catch (NoSuchElementException e) { return ResponseData.toResponseEntity(ResponseCode.POST_NOT_FOUND, null); } @@ -51,13 +50,11 @@ public ResponseEntity> getPostList(@PathVa @GetMapping("/{postId}") @Operation(summary = "커뮤니티 글 내용 조회", description = "postId로 커뮤니티 글 내용을 조회합니다.") - public ResponseEntity> getPostDetail(@CurrentUser Account account, @PathVariable Long postId,@RequestParam(name = "cursor") Long cursor, @RequestParam(name = "pageSize") Integer pageSize) { + public ResponseEntity> getPostDetail(@CurrentUser Account account, @PathVariable Long postId, @RequestParam(name = "cursor") Long cursor, @RequestParam(name = "pageSize") Integer pageSize) { try{ return ResponseData.toResponseEntity(ResponseCode.POST_DETAIL_FETCH_SUCCEESS,postService.getPostDetail(postId,cursor, pageSize)); }catch (NoSuchElementException e) { return ResponseData.toResponseEntity(ResponseCode.POST_NOT_FOUND, null); } } - - } diff --git a/src/main/java/meltingpot/server/post/converter/PostConverter.java b/src/main/java/meltingpot/server/post/converter/PostConverter.java deleted file mode 100644 index 8ddb857..0000000 --- a/src/main/java/meltingpot/server/post/converter/PostConverter.java +++ /dev/null @@ -1,105 +0,0 @@ -package meltingpot.server.post.converter; - -import meltingpot.server.comment.dto.CommentResponseDTO; -import meltingpot.server.domain.entity.Account; -import meltingpot.server.domain.entity.post.Post; -import meltingpot.server.domain.entity.post.PostImage; -import meltingpot.server.post.dto.PostImageDTO; -import meltingpot.server.post.dto.PostRequestDTO; -import meltingpot.server.post.dto.PostResponseDTO; -import org.springframework.data.domain.Page; - -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class PostConverter { - - /*post 작성*/ - public static Post toPost(PostRequestDTO.CreatePostDTO createPostDTO, Account account) { - return Post.builder() - .title(createPostDTO.getTitle()) - .content(createPostDTO.getContent()) - .postType(createPostDTO.getPostType()) - .account(account) - .build(); - } - /*post 작성 응답*/ - public static PostResponseDTO.CreatePostResultDTO toCreatePostResult(List urls,Post post){ - return PostResponseDTO.CreatePostResultDTO.builder() - .postId(post.getId()) - .postImageUrls(urls) - .build(); - } - - public static PostResponseDTO.PostDetailDTO toPostDetailDTO(Post post, CommentResponseDTO.CommentsListDTO commentsList) { - List imgUrls = post.getPostImages().stream() - .map(PostImage::getImageKey) - .collect(Collectors.toList()); - int commentCount = post.getComments().stream() - .mapToInt(parentComment -> 1 + parentComment.getChildren().size()) - .sum(); - return PostResponseDTO.PostDetailDTO.builder() - .postId(post.getId()) - .name(post.getAccount().getName()) - .title(post.getTitle()) - .content(post.getContent()) - .imgUrls(imgUrls) - .commentCount(commentCount) - .commentsList(commentsList) - .updatedAt(post.getUpdatedAt()) - .build(); - } - - /*post 목록 조회*/ - public static PostResponseDTO.PostsListDTO toPostsListDTO(Post post) { - List imgUrls = post.getPostImages().stream() - .map(PostImage::getImageKey) - .collect(Collectors.toList()); - return PostResponseDTO.PostsListDTO.builder() - .postId(post.getId()) - .userId(post.getAccount().getId()) - .name(post.getAccount().getName()) // Assuming Account entity has getName method - .title(post.getTitle()) - .content(post.getContent()) - .commentCount(post.getComments().size()) - .imgUrls(imgUrls) - .updatedAt(post.getUpdatedAt()) - .build(); - } - - public static PostResponseDTO.PageDTO toPageDTO(Pageposts) { - List postsListDTOS = null; - Long nextCursor = null; - - if (!posts.getContent().isEmpty()) { - postsListDTOS = posts.stream().map((Post post) -> toPostsListDTO(post)).toList(); - nextCursor = posts.getContent().get(posts.getContent().size() - 1).getId(); - } - return PostResponseDTO.PageDTO.builder() - .pageDtos(postsListDTOS) - .nextCursor(nextCursor) - .isLast(posts.isLast()) - .build(); - } - -// public static PostResponseDTO.PostDetailDTO toPostDetailDTO(Post post, CommentResponseDTO.CommentsListDTO comments) { -// List allComments = comments.getParentComments().stream() -// .flatMap(parentCommentDTO -> Stream.concat( -// Stream.of(parentCommentDTO.getParentComment()), -// parentCommentDTO.getChildrenComments().stream() -// .map(CommentResponseDTO.ParentCommentDTO::getChildrenComments) -// )) -// .collect(Collectors.toList()); -// -// return PostResponseDTO.PostDetailDTO.builder() -// .postId(post.getId()) -// .name(post.getAccount().getName()) -// .title(post.getTitle()) -// .content(post.getContent()) -// .comments(allComments) -// .updatedAt(post.getUpdatedAt()) -// .build(); -// } - -} diff --git a/src/main/java/meltingpot/server/post/converter/PostImageConverter.java b/src/main/java/meltingpot/server/post/converter/PostImageConverter.java deleted file mode 100644 index 330dd24..0000000 --- a/src/main/java/meltingpot/server/post/converter/PostImageConverter.java +++ /dev/null @@ -1,27 +0,0 @@ -package meltingpot.server.post.converter; - -import meltingpot.server.domain.entity.Account; -import meltingpot.server.domain.entity.post.Post; -import meltingpot.server.domain.entity.post.PostImage; -import meltingpot.server.post.dto.PostRequestDTO; - -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - - -public class PostImageConverter { - public static List toPostImage(PostRequestDTO.CreatePostDTO createPostDTO, Account account,Post post){ - if (createPostDTO.getImageKeys() == null || createPostDTO.getImageKeys().isEmpty()) { - return Collections.emptyList(); - } - return createPostDTO.getImageKeys().stream() - .map(imageKey->PostImage.builder() - .imageKey(imageKey) - .post(post) - .account(account) - .build()) - .collect(Collectors.toList()); - } - -} diff --git a/src/main/java/meltingpot/server/post/dto/PostCreateRequest.java b/src/main/java/meltingpot/server/post/dto/PostCreateRequest.java new file mode 100644 index 0000000..5224533 --- /dev/null +++ b/src/main/java/meltingpot/server/post/dto/PostCreateRequest.java @@ -0,0 +1,30 @@ +package meltingpot.server.post.dto; + + +import lombok.*; +import meltingpot.server.domain.entity.Account; +import meltingpot.server.domain.entity.enums.PostType; +import meltingpot.server.domain.entity.post.Post; + +import java.util.List; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class PostCreateRequest { + private String title; + private String content; + private PostType postType; + private List imageKeys; + + public Post toEntity(Account account){ + return Post.builder() + .title(title) + .content(content) + .postType(postType) + .account(account) + .build(); + + } +} diff --git a/src/main/java/meltingpot/server/post/dto/PostDetailResponse.java b/src/main/java/meltingpot/server/post/dto/PostDetailResponse.java new file mode 100644 index 0000000..3bb2384 --- /dev/null +++ b/src/main/java/meltingpot/server/post/dto/PostDetailResponse.java @@ -0,0 +1,44 @@ +package meltingpot.server.post.dto; + +import lombok.*; +import meltingpot.server.comment.dto.CommentsListResponse; +import meltingpot.server.domain.entity.post.Post; +import meltingpot.server.domain.entity.post.PostImage; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class PostDetailResponse { + private Long postId; + private String name; + private String title; + private String content; + List imgUrls; + private Integer commentCount; + private CommentsListResponse commentsList; + private LocalDateTime updatedAt; + + public static PostDetailResponse of (Post post, CommentsListResponse commentsList) { + List imgUrls = post.getPostImages().stream() + .map(PostImage::getImageUrl) + .collect(Collectors.toList()); + int commentCount = post.getComments().stream() + .mapToInt(parentComment -> 1 + parentComment.getChildren().size()) + .sum(); + return PostDetailResponse.builder() + .postId(post.getId()) + .name(post.getAccount().getName()) + .title(post.getTitle()) + .content(post.getContent()) + .imgUrls(imgUrls) + .commentCount(commentCount) + .commentsList(commentsList) + .updatedAt(post.getUpdatedAt()) + .build(); + } +} diff --git a/src/main/java/meltingpot/server/post/dto/PostImageDTO.java b/src/main/java/meltingpot/server/post/dto/PostImageDTO.java deleted file mode 100644 index 5b9aaf7..0000000 --- a/src/main/java/meltingpot/server/post/dto/PostImageDTO.java +++ /dev/null @@ -1,16 +0,0 @@ -package meltingpot.server.post.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class PostImageDTO { - @JsonProperty("id") - private Long id; - - @JsonProperty("imgUrl") - private String imgUrl; - -} diff --git a/src/main/java/meltingpot/server/post/dto/PostRequestDTO.java b/src/main/java/meltingpot/server/post/dto/PostRequestDTO.java deleted file mode 100644 index d4803ec..0000000 --- a/src/main/java/meltingpot/server/post/dto/PostRequestDTO.java +++ /dev/null @@ -1,17 +0,0 @@ -package meltingpot.server.post.dto; - -import lombok.Getter; -import meltingpot.server.domain.entity.enums.PostType; - -import java.util.List; - -public class PostRequestDTO { - - @Getter - public static class CreatePostDTO { - private String title; - private String content; - private PostType postType; - private List imageKeys; - } -} diff --git a/src/main/java/meltingpot/server/post/dto/PostResponseDTO.java b/src/main/java/meltingpot/server/post/dto/PostResponseDTO.java deleted file mode 100644 index 1e97c1a..0000000 --- a/src/main/java/meltingpot/server/post/dto/PostResponseDTO.java +++ /dev/null @@ -1,62 +0,0 @@ -package meltingpot.server.post.dto; - -import lombok.*; -import meltingpot.server.comment.dto.CommentResponseDTO; -import meltingpot.server.domain.entity.enums.PostType; - -import java.time.LocalDateTime; -import java.util.List; - -public class PostResponseDTO { - @Getter - @AllArgsConstructor - @NoArgsConstructor - @Builder - public static class CreatePostResultDTO { - private Long postId; - private LocalDateTime createAt; - private List postImageUrls; - } - - @Builder - @Getter - @NoArgsConstructor(access = AccessLevel.PROTECTED) - @AllArgsConstructor(access = AccessLevel.PROTECTED) - public static class PostDetailDTO { - private Long postId; - private String name; - private String title; - private String content; - List imgUrls; - private Integer commentCount; - private CommentResponseDTO.CommentsListDTO commentsList; - private LocalDateTime updatedAt; - } - - @Builder - @Getter - @NoArgsConstructor(access = AccessLevel.PROTECTED) - @AllArgsConstructor(access = AccessLevel.PROTECTED) - public static class PostsListDTO { - private Long postId; - private Long userId; - private String name; - private String title; - private String content; - List imgUrls; - private Integer commentCount; - private LocalDateTime updatedAt; - } - - @Builder - @Getter - @NoArgsConstructor(access = AccessLevel.PROTECTED) - @AllArgsConstructor(access = AccessLevel.PROTECTED) - public static class PageDTO{ - private List< PostsListDTO> pageDtos; - private Long nextCursor; - private Boolean isLast; - } - - -} diff --git a/src/main/java/meltingpot/server/post/dto/PostsListResponse.java b/src/main/java/meltingpot/server/post/dto/PostsListResponse.java new file mode 100644 index 0000000..edcaccc --- /dev/null +++ b/src/main/java/meltingpot/server/post/dto/PostsListResponse.java @@ -0,0 +1,65 @@ +package meltingpot.server.post.dto; + + +import lombok.*; +import meltingpot.server.domain.entity.post.Post; +import meltingpot.server.domain.entity.post.PostImage; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class PostsListResponse { + private List postsList; + private Long nextCursor; + private Boolean isLast; + + @Getter + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class PostsList { + private Long postId; + private Long userId; + private String name; + private String title; + private String content; + List imgUrls; + private Integer commentCount; + private LocalDateTime updatedAt; + + public static PostsList from(Post post) { + List imgUrls = post.getPostImages().stream() + .map(PostImage::getImageUrl) + .collect(Collectors.toList()); + return PostsList.builder() + .postId(post.getId()) + .userId(post.getAccount().getId()) + .name(post.getAccount().getName()) + .title(post.getTitle()) + .content(post.getContent()) + .commentCount(post.getComments().size()) + .imgUrls(imgUrls) + .updatedAt(post.getUpdatedAt()) + .build(); + } + } + + public static PostsListResponse from(List posts,Long nextCursor,Boolean isLast) { + List postsList = posts.stream() + .map(PostsList::from) + .collect(Collectors.toList()); + return PostsListResponse.builder() + .postsList(postsList) + .nextCursor(nextCursor) + .isLast(isLast) + .build(); + } + + + +} diff --git a/src/main/java/meltingpot/server/post/service/PostService.java b/src/main/java/meltingpot/server/post/service/PostService.java index aead909..787faa8 100644 --- a/src/main/java/meltingpot/server/post/service/PostService.java +++ b/src/main/java/meltingpot/server/post/service/PostService.java @@ -1,27 +1,166 @@ package meltingpot.server.post.service; -import meltingpot.server.domain.entity.Account; -import meltingpot.server.domain.entity.enums.PostType; -import meltingpot.server.post.dto.PostRequestDTO; -import meltingpot.server.post.dto.PostResponseDTO; -import org.springframework.data.domain.Pageable; +import lombok.*; -public interface PostService { +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; - PostResponseDTO.CreatePostResultDTO createPost (PostRequestDTO.CreatePostDTO createPostDTO, Account account); +import meltingpot.server.comment.dto.CommentsListResponse; +import meltingpot.server.domain.entity.Account; +import meltingpot.server.domain.entity.comment.Comment; +import meltingpot.server.domain.entity.post.Post; +import meltingpot.server.domain.entity.enums.PostType; +import meltingpot.server.domain.entity.post.PostImage; +import meltingpot.server.domain.repository.AccountRepository; +import meltingpot.server.domain.repository.CommentRepository; +import meltingpot.server.domain.repository.PostRepository; +import meltingpot.server.post.dto.PostCreateRequest; +import meltingpot.server.post.dto.PostDetailResponse; +import meltingpot.server.post.dto.PostsListResponse; +import meltingpot.server.util.ResponseCode; +import meltingpot.server.util.r2.FileService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.data.domain.Pageable; - PostResponseDTO.PageDTO getPostsList(PostType postType,Account account, Long cursor, Pageable pageable); - PostResponseDTO.PostDetailDTO getPostDetail(Long postId, Long cursor, int pageSize); +@Service +@RequiredArgsConstructor +@Transactional +public class PostService { + private final AccountRepository accountRepository; + private final PostRepository postRepository; + private final CommentRepository commentRepository; + @Autowired + private FileService fileService; + + + /*post 작성하기*/ + public ResponseCode createPost(PostCreateRequest createPostDTO,Account account){ + Post post = createPostDTO.toEntity(account); + List postImgUrls = Collections.emptyList(); + List postImages = createPostImages(createPostDTO.getImageKeys(), post, account); + post.setPostImages(postImages); + postRepository.save(post); + return ResponseCode.POST_CREATE_SUCCESS; + } + + + /*post 내용 불러오기*/ + @Transactional(readOnly = true) + public PostDetailResponse getPostDetail(Long postId, Long cursor, int pageSize){ + Post post = findPostById(postId); + CommentsListResponse commentsList = fetchCommentsList(postId, cursor, pageSize); + return PostDetailResponse.of(post,commentsList); + } + + /*post 목록 불러오기*/ + @Transactional(readOnly = true) + public PostsListResponse getPostsList(Account account, PostType postType, Long cursor, int pageSize){ + Pageable pageable = PageRequest.of(0, pageSize); + List posts = postRepository.findByPostTypeAndCursor(postType, cursor, pageable); + Long nextCursor = posts.isEmpty() ? null : posts.get(posts.size() - 1).getId(); + boolean isLast = posts.size() < pageSize; + return PostsListResponse.from(posts, nextCursor, isLast); + } + + + + private Account findAccountById(Long accountId) { + return accountRepository.findById(accountId) + .orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다.")); + } + + private Post findPostById(Long postId) { + return postRepository.findById(postId) + .orElseThrow(() -> new RuntimeException("게시글을 찾을 수 없습니다.")); + } + + private List getCdnUrls(List imageKeys) { + return imageKeys.stream() + .map(imageKey -> { + String prefix = "post"; // 적절한 prefix 값을 설정 + return fileService.getCdnUrl(prefix, imageKey); + }) + .collect(Collectors.toList()); + } + + private List createPostImages(List imageKeys, Post post, Account account) { + List postImgUrls = getCdnUrls(imageKeys); + return postImgUrls.stream() + .map(imageUrl -> PostImage.builder() + .imageUrl(imageUrl) + .post(post) + .account(account) + .build()) + .collect(Collectors.toList()); + } + + private CommentsListResponse fetchCommentsList(Long postId, Long cursor, int pageSize) { + List commentDetailDTOs = new ArrayList<>(); + int count = 0; + Long parentCursor = null; + + // Cursor가 자식 댓글에 해당하는 경우 처리 + if (cursor != null) { + Comment cursorComment = commentRepository.findById(cursor).orElse(null); + if (cursorComment != null) { + Comment parentComment; + if (cursorComment.getParent() != null) { + // Cursor가 자식 댓글을 나타내는 경우 + parentComment = cursorComment.getParent(); + } else { + // Cursor가 부모 댓글을 나타내는 경우 + parentComment = cursorComment; + } + + List remainingChildren = commentRepository.findChildrenCommentsByParentId(parentComment.getId(), cursor); + for (Comment child : remainingChildren) { + if (count >= pageSize) break; + commentDetailDTOs.add(CommentsListResponse.CommentDetail.from(child)); + count++; + } + parentCursor = parentComment.getId(); + + // 만약 자식 댓글을 모두 가져왔고, 페이지가 꽉 차지 않았다면 다음 부모 댓글로 넘어감 + if (count < pageSize && remainingChildren.size() < pageSize) { + parentCursor = parentComment.getId(); + } + } else { + parentCursor = cursor; // cursor가 부모 댓글인 경우 + } + } + + // 부모 댓글과 자식 댓글을 가져오는 처리 + if (count < pageSize) { + Pageable pageable = PageRequest.of(0, pageSize - count); + List parentComments = commentRepository.findParentCommentsByPostId(postId, parentCursor, pageable); + for (Comment parent : parentComments) { + if (count >= pageSize) break; + commentDetailDTOs.add(CommentsListResponse.CommentDetail.from(parent)); + count++; + + List children = commentRepository.findChildrenCommentsByParentId(parent.getId(), null); + for (Comment child : children) { + if (count >= pageSize) break; + commentDetailDTOs.add(CommentsListResponse.CommentDetail.from(child)); + count++; + } + } + } + + Long nextCursor = (count < pageSize) ? null : commentDetailDTOs.get(commentDetailDTOs.size() - 1).getCommentId(); + boolean isLast = (count < pageSize); + + return CommentsListResponse.from(commentDetailDTOs, nextCursor, isLast); + } } - - - - - - diff --git a/src/main/java/meltingpot/server/post/service/PostServiceImpl.java b/src/main/java/meltingpot/server/post/service/PostServiceImpl.java deleted file mode 100644 index ef1f027..0000000 --- a/src/main/java/meltingpot/server/post/service/PostServiceImpl.java +++ /dev/null @@ -1,152 +0,0 @@ -package meltingpot.server.post.service; - - -import lombok.*; -import meltingpot.server.comment.converter.CommentConverter; -import meltingpot.server.comment.dto.CommentResponseDTO; -import meltingpot.server.domain.entity.Account; -import meltingpot.server.domain.entity.comment.Comment; -import meltingpot.server.domain.entity.post.Post; -import meltingpot.server.domain.entity.enums.PostType; -import meltingpot.server.domain.entity.post.PostImage; -import meltingpot.server.domain.repository.AccountRepository; -import meltingpot.server.domain.repository.CommentRepository; -import meltingpot.server.domain.repository.PostRepository; -import meltingpot.server.post.converter.PostConverter; -import meltingpot.server.post.dto.PostRequestDTO; -import meltingpot.server.post.dto.PostResponseDTO; -import meltingpot.server.util.r2.FileService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.data.domain.Pageable; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import static meltingpot.server.comment.converter.CommentConverter.toCommentDetailDTO; -import static meltingpot.server.comment.converter.CommentConverter.toCommentsListDTO; -import static meltingpot.server.post.converter.PostConverter.*; -import static meltingpot.server.post.converter.PostImageConverter.toPostImage; - -@Service -@RequiredArgsConstructor -@Transactional -public class PostServiceImpl implements PostService { - private final AccountRepository accountRepository; - private final PostRepository postRepository; - private final CommentRepository commentRepository; - @Autowired - private FileService fileService; - - - - @Override - public PostResponseDTO.CreatePostResultDTO createPost(PostRequestDTO.CreatePostDTO createPostDTO,Account account){ - Post post = toPost(createPostDTO,account); - List postImgUrls = Collections.emptyList(); - if (createPostDTO.getImageKeys() != null && !createPostDTO.getImageKeys().isEmpty()) { - postImgUrls = getCdnUrls(createPostDTO.getImageKeys()); - } - List postImages = toPostImage(createPostDTO,account, post); - postRepository.save(post); - post.setPostImages(postImages); - return toCreatePostResult(postImgUrls,post); - } - - @Override - public PostResponseDTO.PostDetailDTO getPostDetail(Long postId, Long cursor, int pageSize){ - Post post = findPostById(postId); - - // 댓글 목록 가져오기 - CommentResponseDTO.CommentsListDTO commentsList = fetchCommentsList(postId, cursor, pageSize); - return toPostDetailDTO(post,commentsList); - } - - private List getCdnUrls(List imageKeys) { - return imageKeys.stream() - .map(imageKey -> { - String prefix = "post"; // 적절한 prefix 값을 설정 - return fileService.getCdnUrl(prefix, imageKey); - }) - .collect(Collectors.toList()); - } - - - @Override - @Transactional(readOnly = true) - public PostResponseDTO.PageDTO getPostsList(PostType postType,Account account,Long cursor, Pageable pageable){ - Page posts = postRepository.findByPostType(postType,pageable); - return PostConverter.toPageDTO(posts); - } - - - - private Account findAccountById(Long accountId) { - return accountRepository.findById(accountId) - .orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다.")); - } - - private Post findPostById(Long postId) { - return postRepository.findById(postId) - .orElseThrow(() -> new RuntimeException("게시글을 찾을 수 없습니다.")); - } - - private CommentResponseDTO.CommentsListDTO fetchCommentsList(Long postId, Long cursor, int pageSize){ - List commentDetailDTOs = new ArrayList<>(); - int count = 0; - Long parentCursor = null; - - // Cursor가 자식 댓글에 해당하는 경우 처리 - if (cursor != null) { - // Cursor가 부모 댓글이 아닌 자식 댓글을 나타내는 경우 - Comment childComment = commentRepository.findById(cursor).orElse(null); - if (childComment != null && childComment.getParent() != null) { - Comment parentComment = childComment.getParent(); - List remainingChildren = commentRepository.findChildrenCommentsByParentId(parentComment.getId(), cursor); - for (Comment child : remainingChildren) { - if (count >= pageSize) break; - commentDetailDTOs.add(toCommentDetailDTO(child)); - count++; - } - parentCursor = parentComment.getId(); - - // 만약 자식 댓글을 모두 가져왔고, 페이지가 꽉 차지 않았다면 다음 부모 댓글로 넘어감 - if (count < pageSize && remainingChildren.size() < pageSize) { - parentCursor = parentComment.getId(); - } - } else { - parentCursor = cursor; // cursor가 부모 댓글인 경우 - } - } - - // 부모 댓글과 자식 댓글을 가져오는 처리 - if (count < pageSize) { - Pageable pageable = PageRequest.of(0, pageSize - count); - List parentComments = commentRepository.findParentCommentsByPostId(postId, parentCursor, pageable); - for (Comment parent : parentComments) { - if (count >= pageSize) break; - commentDetailDTOs.add(toCommentDetailDTO(parent)); - count++; - - List children = commentRepository.findChildrenCommentsByParentId(parent.getId(), null); - for (Comment child : children) { - if (count >= pageSize) break; - commentDetailDTOs.add(toCommentDetailDTO(child)); - count++; - } - } - } - - Long nextCursor = (count < pageSize) ? null : commentDetailDTOs.get(commentDetailDTOs.size() - 1).getCommentId(); - boolean isLast = (count < pageSize); - - return toCommentsListDTO(commentDetailDTOs,nextCursor,isLast); - } - -} - diff --git a/src/main/java/meltingpot/server/util/ResponseCode.java b/src/main/java/meltingpot/server/util/ResponseCode.java index 121fd24..47359bc 100644 --- a/src/main/java/meltingpot/server/util/ResponseCode.java +++ b/src/main/java/meltingpot/server/util/ResponseCode.java @@ -50,6 +50,8 @@ public enum ResponseCode { READ_COMMENTS_LIST_SUCCESS(OK,"댓글 목록 불러오기 성공"), + POST_CREATE_SUCCESS(OK,"게시글 생성 성공"), + /* 201 CREATED : 요청 성공, 자원 생성 */ SIGNUP_SUCCESS(CREATED, "회원가입 성공"),