CHZZK-Study / Grass-Diary-Server

취지직 2팀 프로젝트 Grass Diary Server
1 stars 1 forks source link

feat(Comment) : 댓글 기능 추가 #102

Closed chjcode closed 2 weeks ago

chjcode commented 1 month ago

주요 기능

  1. 댓글 저장
  2. 댓글 수정
  3. 댓글 삭제
  4. 댓글 불러오기

계층형 댓글 구조란?

댓글 시스템에서 댓글과 대댓글이 계층적으로 연결되어 있는 형태를 의미

일기: 오늘은 옥수수랑 팝콘을 먹었다.
└─ 댓글 1: 둘 중에 뭐가 더 맛있나요?
   └─ 답글 1-1: 콘치즈요.
       └─ 답글 1-1-1: ^^;
└─ 댓글 2: 카라멜 팝콘 짱

어떻게 구현했나?

Adjacency List(인접 리스트)를 사용해서 구현.

댓글 저장

@Transactional
    public CommentResponseDTO save(Long diaryId, CommentSaveRequestDTO requestDTO, Long logInMemberId) {
        Member member = getMemberById(logInMemberId);
        Diary diary = getDiaryById(diaryId);
        Comment parentComment = getParentCommentById(requestDTO.parentCommentId());
        Comment comment = requestDTO.toEntity(member, diary, parentComment);
        commentDAO.save(comment);

        return CommentResponseDTO.from(comment);
    }

로그인 된 사용자의 정보를 바탕으로 댓글을 생성하고, 댓글이 달린 일기와 부모 댓글 정보를 포함하여 최종적으로 데이터베이스에 저장된 댓글 정보를 클라이언트에게 반환

댓글 수정

@Transactional
    public CommentResponseDTO update(Long CommentId, CommentUpdateRequestDTO requestDTO, Long logInMemberId) {
        Member member = getMemberById(logInMemberId);
        Comment comment = getCommentById(CommentId);
        validateCommentAuthor(member, comment);
        comment.update(requestDTO.content());

        return CommentResponseDTO.from(comment);
    }

로그인된 사용자의 정보를 바탕으로 수정하려는 댓글을 조회하고, 댓글 작성자인지 확인한 후 댓글 내용을 업데이트하여 최종적으로 클라이언트에게 반환

댓글 삭제

@Transactional
    public CommentDeleteResponseDTO delete(Long CommentId, Long logInMemberId) {
        Member member = getMemberById(logInMemberId);
        Comment comment = getCommentById(CommentId);
        validateCommentAuthor(member, comment);
        validateNotDeleted(comment);

        comment.delete();

        return CommentDeleteResponseDTO.from(comment);
    }

로그인된 사용자의 정보를 바탕으로 삭제하려는 댓글을 조회하고, 댓글 작성자인지 확인한 후 댓글을 삭제 상태로 변경하여 최종적으로 클라이언트에게 반환

일기: 오늘은 옥수수랑 팝콘을 먹었다.
└─ 댓글 1: 둘 중에 뭐가 더 맛있나요?
   └─ 답글 1-1: **삭제된 댓글입니다.**
       └─ 답글 1-1-1: ^^;
└─ 댓글 2: 카라멜 팝콘 짱

댓글이 삭제 되어도 해당 댓글에 대한 대댓글을 유지하기 위해, 실질적으로 삭제하지 않고 deleted라는 삭제 여부를 저장하는 컬럼을 둠.

public record CommentResponseDTO(
        Long memberId,
        String content,
        boolean deleted,
        String createdDate,
        String createdAt,
        List<CommentResponseDTO> childComments
) {
    public static CommentResponseDTO from(Comment comment) {
        return new CommentResponseDTO(
                comment.getMember().getId(),
                comment.getContent(),
                comment.isDeleted(),
                comment.getCreatedAt().format(DateTimeFormatter.ofPattern("yy년 MM월 dd일")),
                comment.getCreatedAt().format(DateTimeFormatter.ofPattern("HH:mm")),
                comment.getChildComments().stream().map(CommentResponseDTO::from).collect(Collectors.toList())
        );
    }

    public static CommentResponseDTO fromDeleted(Comment comment) {
        return new CommentResponseDTO(
                null,
                null,
                true,
                null,
                null,
                comment.getChildComments().stream().map(CommentResponseDTO::from).collect(Collectors.toList())
        );
    }
}

CommentResponseDTO에 fromDeleted 메서드를 추가하여 삭제된 상태일 때는 댓글에 대한 정보를 null값을 받아 반환하도록 함.

댓글 불러오기

@Transactional(readOnly = true)
    public List<CommentResponseDTO> findAll(Pageable pageable, Long diaryId) {
        Diary diary = getDiaryById(diaryId);
        List<Comment> comments = commentDAO.findAllByDiaryId(diaryId, pageable);

        List<Comment> hierarchicalComments = convertHierarchy(comments);

        return hierarchicalComments.stream()
                .map(this::mapToDTO)
                .collect(Collectors.toList());
    }

    private List<Comment> convertHierarchy(List<Comment> comments) {
        Map<Long, Comment> map = new HashMap<>();
        List<Comment> parentComments = new ArrayList<>();

        for (Comment comment : comments) {
            if (comment.getParentComment() != null) {
                // 부모 댓글이 있는 경우
                Comment parentComment = map.get(comment.getParentComment().getId());
                if (parentComment != null) {
                    parentComment.getChildComments().add(comment);
                }
            } else {
                // 부모 댓글이 없는 경우
                parentComments.add(comment);
            }
            map.put(comment.getId(), comment);
        }

        return parentComments;
    }

    private CommentResponseDTO mapToDTO(Comment comment) {
        if (comment.isDeleted()) {
            return CommentResponseDTO.fromDeleted(comment);
        }
        return CommentResponseDTO.from(comment);
    }
  1. findAllByDiaryId로 db에서 부모 댓글이 null인 최상위 댓글을 먼저 오도록 정렬해서 데이터 가져옴
  2. convertHierarchy를 이용하여 평면 구조로 조회된 댓글 리스트를 계층형 구조로 변환