Skip to content

Commit

Permalink
#256 Feat: 댓글 관련 API 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
lee-haeseung committed Nov 1, 2024
1 parent 445109c commit be598ff
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -1,49 +1,68 @@
package com.codiary.backend.domain.comment.controller;

import com.codiary.backend.domain.comment.converter.CommentConverter;
import com.codiary.backend.domain.comment.dto.request.CommentRequestDTO;
import com.codiary.backend.domain.comment.dto.response.CommentResponseDTO;
import com.codiary.backend.domain.comment.entity.Comment;
import com.codiary.backend.domain.comment.service.CommentService;
import com.codiary.backend.domain.member.security.CustomMemberDetails;
import com.codiary.backend.global.apiPayload.ApiResponse;
import com.codiary.backend.global.apiPayload.code.status.SuccessStatus;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
@RequestMapping("/api/v2/posts/{post_id}/comments")
@RequestMapping("/api/v2/posts")
@Tag(name = "댓글 API", description = "댓글 관련 API 입니다.")
public class CommentController {

private final CommentService commentService;

@Operation(summary = "댓글 달기")
@PostMapping()
public ApiResponse<?> commentPost(@PathVariable("post_id") Long postId) {
return null;
@PostMapping("/posts/{post_id}/comments")
public ApiResponse<CommentResponseDTO.CommentDTO> commentOnPost(
@PathVariable("post_id") Long postId,
@RequestBody CommentRequestDTO.CreateCommentDTO request,
@AuthenticationPrincipal CustomMemberDetails memberDetails
) {
Long commenterId = memberDetails.getId();
Comment newComment = commentService.commentOnPost(postId, commenterId, request);
return ApiResponse.onSuccess(SuccessStatus.COMMENT_OK, CommentConverter.toCommentResponseDto(newComment));
}

@Operation(summary = "댓글 삭제")
@DeleteMapping("/{comment_id}")
public ApiResponse<?> deleteComment(
@PathVariable("post_id") Long postId,
@PathVariable("comment_id") Long commentId
@DeleteMapping("comments/{comment_id}")
public ApiResponse<String> deleteComment(
@PathVariable("comment_id") Long commentId,
@AuthenticationPrincipal CustomMemberDetails memberDetails
) {
return null;
Long memberId = memberDetails.getId();
String response = commentService.deleteComment(commentId, memberId);
return ApiResponse.onSuccess(SuccessStatus.COMMENT_OK, response);
}

@Operation(summary = "댓글 조회", description = "기본적으로 10개씩 페이지네이션 하여 제공됩니다.")
@GetMapping("/{comment_id}")
public ApiResponse<?> getComments(
@Operation(summary = "댓글 조회", description = "기본적으로 10개씩 페이지네이션하여 제공됩니다.")
@GetMapping("/posts/{post_id}/comments")
public ApiResponse<List<CommentResponseDTO.CommentDTO>> getComments(
@PathVariable("post_id") Long postId,
@PathVariable("comment_id") Long commentId,
@AuthenticationPrincipal CustomMemberDetails memberDetails,
@PageableDefault(size = 10) Pageable pageable
) {
return null;
Long memberId = memberDetails.getId();
List<Comment> comments = commentService.getComments(postId, memberId, pageable);
return ApiResponse.onSuccess(SuccessStatus.COMMENT_OK, CommentConverter.toCommentResponseListDto(comments));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.codiary.backend.domain.comment.converter;

import com.codiary.backend.domain.comment.dto.response.CommentResponseDTO;
import com.codiary.backend.domain.comment.entity.Comment;
import java.util.List;
import java.util.stream.Collectors;

public class CommentConverter {

public static CommentResponseDTO.CommentDTO toCommentResponseDto(Comment comment) {
return CommentResponseDTO.CommentDTO.builder()
.commentId(comment.getCommentId())
.commentBody(comment.getCommentBody())
.postId(comment.getPost().getPostId())
.commenterId(comment.getMember().getMemberId())
.commenterProfileImageUrl(
(comment.getMember().getImage() != null) ? (comment.getMember().getImage().getImageUrl()) : "")
.commenterNickname(comment.getMember().getNickname())
.createdAt(comment.getCreatedAt())
.updatedAt(comment.getUpdatedAt())
.build();
}

public static List<CommentResponseDTO.CommentDTO> toCommentResponseListDto(List<Comment> comments) {
return comments.stream()
.map(CommentConverter::toCommentResponseDto)
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.codiary.backend.domain.comment.dto.request;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Builder;

public class CommentRequestDTO {

// Comment 생성 DTO
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@Builder
public record CreateCommentDTO(
String commentBody
) {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.codiary.backend.domain.comment.dto.response;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import java.time.LocalDateTime;
import lombok.Builder;

public class CommentResponseDTO {

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@Builder
public record CommentDTO(
Long commentId,
String commentBody,
Long postId,
Long commenterId,
String commenterProfileImageUrl,
String commenterNickname,
LocalDateTime createdAt,
LocalDateTime updatedAt
) {
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
package com.codiary.backend.domain.comment.entity;

import com.codiary.backend.domain.member.entity.Member;
import com.codiary.backend.global.common.BaseEntity;
import com.codiary.backend.domain.post.entity.Post;
import jakarta.persistence.*;
import lombok.*;

import com.codiary.backend.global.common.BaseEntity;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
Expand Down Expand Up @@ -37,6 +48,13 @@ public class Comment extends BaseEntity {
@OneToMany(mappedBy = "parentId", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Comment> childComments = new ArrayList<>();

@Builder
public Comment(String commentBody, Member member, Post post) {
this.commentBody = commentBody;
this.member = member;
this.post = post;
}

public void setMember(Member member) {
if (this.member != null) {
member.getCommentList().remove(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
import com.codiary.backend.domain.comment.entity.Comment;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CommentRepository extends JpaRepository<Comment, Long> {
public interface CommentRepository extends JpaRepository<Comment, Long>, CommentRepositoryCustom {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.codiary.backend.domain.comment.repository;

import com.codiary.backend.domain.comment.entity.Comment;
import java.util.List;
import org.springframework.data.domain.Pageable;

public interface CommentRepositoryCustom {

List<Comment> findByPostWithMemberInfoOrderByCreatedAtDesc(Long postId, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.codiary.backend.domain.comment.repository;

import static com.codiary.backend.domain.comment.entity.QComment.comment;
import static com.codiary.backend.domain.member.entity.QMember.member;
import static com.codiary.backend.domain.post.entity.QPost.post;

import com.codiary.backend.domain.comment.entity.Comment;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;

@RequiredArgsConstructor
public class CommentRepositoryImpl implements CommentRepositoryCustom {

private final JPAQueryFactory queryFactory;

@Override
public List<Comment> findByPostWithMemberInfoOrderByCreatedAtDesc(Long postId, Pageable pageable) {
List<Comment> comments = queryFactory
.selectFrom(comment)
.leftJoin(comment.member, member)
.leftJoin(comment.post, post)
.where(comment.post.postId.eq(postId))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();

return comments;
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,76 @@
package com.codiary.backend.domain.comment.service;

import com.codiary.backend.domain.comment.dto.request.CommentRequestDTO;
import com.codiary.backend.domain.comment.entity.Comment;
import com.codiary.backend.domain.comment.repository.CommentRepository;
import com.codiary.backend.domain.member.entity.Member;
import com.codiary.backend.domain.member.repository.MemberRepository;
import com.codiary.backend.domain.post.entity.Post;
import com.codiary.backend.domain.post.repository.PostRepository;
import com.codiary.backend.global.apiPayload.code.status.ErrorStatus;
import com.codiary.backend.global.apiPayload.exception.GeneralException;
import com.codiary.backend.global.apiPayload.exception.handler.MemberHandler;
import com.codiary.backend.global.apiPayload.exception.handler.PostHandler;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
@Transactional
public class CommentService {

private final CommentRepository commentRepository;
private final MemberRepository memberRepository;
private final PostRepository postRepository;

public Comment commentOnPost(Long postId, Long commenterId, CommentRequestDTO.CreateCommentDTO request) {
// validation: 사용자, post 유무 확인
// + 사용자가 해당 게시물에 대한 댓글 권한 있는지( 이후 구현 )
Member commenter = memberRepository.findById(commenterId)
.orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND));
Post post = postRepository.findById(postId).orElseThrow(() -> new PostHandler(ErrorStatus.POST_NOT_FOUND));

// business logic: 댓글 생성
Comment comment = Comment.builder()
.commentBody(request.commentBody())
.member(commenter)
.post(post)
.build();

// response: 댓글 반환
return commentRepository.save(comment);
}

public String deleteComment(Long commentId, Long memberId) {
// validation: 사용자, comment 유무 확인
// + 사용자가 해당 댓글에 대한 댓글 권한 있는지( 이후 구현 )
Member commenter = memberRepository.findById(memberId)
.orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND));
Comment comment = commentRepository.findById(commentId)
.orElseThrow(() -> new GeneralException(ErrorStatus.COMMENT_NOT_FOUND));

// business logic: 댓글 삭제
commentRepository.delete(comment);

// response: 삭제 성공 반환
return "성공적으로 삭제되었습니다!";
}

@Transactional(readOnly = true)
public List<Comment> getComments(Long postId, Long memberId, Pageable pageable) {
// validation: 사용자, post 유무 확인
// + 사용자가 해당 게시물에 대한 읽기 권한 있는지( 이후 구현 )
Member commenter = memberRepository.findById(memberId)
.orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND));
Post post = postRepository.findById(postId).orElseThrow(() -> new PostHandler(ErrorStatus.POST_NOT_FOUND));

// business logic: 댓글 조회
List<Comment> comments = commentRepository.findByPostWithMemberInfoOrderByCreatedAtDesc(postId, pageable);

// response: comment list 반환
return comments;
}
}

0 comments on commit be598ff

Please sign in to comment.