Skip to content

Commit

Permalink
#69 - 대댓글 비즈니스 로직 구현
Browse files Browse the repository at this point in the history
부모/자식 관계를 만들면서 entity <-> dto 간 변환 로직, 댓글 서비스에서 댓글과 대댓글을 저장하고 삭제하는 방법, 댓글과 대댓글을 db 데이터로부터 변환할 때 서로 계층 구조를 만들고 각각 정렬하는 과정을 구현
  • Loading branch information
lmw7414 committed Jan 15, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 38b098e commit d89e933
Showing 7 changed files with 304 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ public record ArticleCommentDto(
Long id,
Long articleId,
UserAccountDto userAccountDto,
Long parentCommentId,
String content,
LocalDateTime createdAt,
String createdBy,
@@ -18,17 +19,23 @@ public record ArticleCommentDto(
) {

public static ArticleCommentDto of(Long articleId, UserAccountDto userAccountDto, String content) {
return new ArticleCommentDto(null, articleId, userAccountDto, content, null, null, null, null);
return ArticleCommentDto.of( articleId, userAccountDto, null, content);
}
public static ArticleCommentDto of(Long id, Long articleId, UserAccountDto userAccountDto, String content, LocalDateTime createdAt, String createdBy, LocalDateTime modifiedAt, String modifiedBy) {
return new ArticleCommentDto(id, articleId, userAccountDto, content, createdAt, createdBy, modifiedAt, modifiedBy);

public static ArticleCommentDto of(Long articleId, UserAccountDto userAccountDto, Long parentCommentId, String content) {
return ArticleCommentDto.of(null, articleId, userAccountDto, parentCommentId, content, null, null, null, null);
}

public static ArticleCommentDto of(Long id, Long articleId, UserAccountDto userAccountDto,Long parentCommentId , String content, LocalDateTime createdAt, String createdBy, LocalDateTime modifiedAt, String modifiedBy) {
return new ArticleCommentDto(id, articleId, userAccountDto, parentCommentId, content, createdAt, createdBy, modifiedAt, modifiedBy);
}

public static ArticleCommentDto from(ArticleComment entity) {
return new ArticleCommentDto(
entity.getId(),
entity.getArticle().getId(),
UserAccountDto.from(entity.getUserAccount()),
entity.getParentCommentId(),
entity.getContent(),
entity.getCreatedAt(),
entity.getCreatedBy(),
Original file line number Diff line number Diff line change
@@ -3,16 +3,25 @@
import com.fastcampus.projectboard.dto.ArticleCommentDto;
import com.fastcampus.projectboard.dto.UserAccountDto;

public record ArticleCommentRequest(Long articleId, String content) {
public record ArticleCommentRequest(
Long articleId,
Long parentCommentId,
String content
) {

public static ArticleCommentRequest of(Long articleId, String content) {
return new ArticleCommentRequest(articleId, content);
return ArticleCommentRequest.of(articleId, null ,content);
}

public static ArticleCommentRequest of(Long articleId,Long parentCommentId, String content) {
return new ArticleCommentRequest(articleId,parentCommentId ,content);
}

public ArticleCommentDto toDto(UserAccountDto userAccountDto) {
return ArticleCommentDto.of(
articleId,
userAccountDto,
parentCommentId,
content
);
}
Original file line number Diff line number Diff line change
@@ -3,18 +3,30 @@
import com.fastcampus.projectboard.dto.ArticleCommentDto;

import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public record ArticleCommentResponse(
Long id,
String content,
LocalDateTime createdAt,
String email,
String nickname,
String userId
String userId,
Long parentCommentId,
Set<ArticleCommentResponse> childComments
) {

public static ArticleCommentResponse of(Long id, String content, LocalDateTime createdAt, String email, String nickname, String userId) {
return new ArticleCommentResponse(id, content, createdAt, email, nickname, userId);
return ArticleCommentResponse.of(id, content, createdAt, email, nickname, userId, null);
}

public static ArticleCommentResponse of(Long id, String content, LocalDateTime createdAt, String email, String nickname, String userId, Long parentCommentId) {
Comparator<ArticleCommentResponse> childCommentComparator = Comparator
.comparing(ArticleCommentResponse::createdAt)
.thenComparingLong(ArticleCommentResponse::id);
return new ArticleCommentResponse(id, content, createdAt, email, nickname, userId, parentCommentId, new TreeSet<>(childCommentComparator));
}

public static ArticleCommentResponse from(ArticleCommentDto dto) {
@@ -23,14 +35,19 @@ public static ArticleCommentResponse from(ArticleCommentDto dto) {
nickname = dto.userAccountDto().userId();
}

return new ArticleCommentResponse(
return ArticleCommentResponse.of(
dto.id(),
dto.content(),
dto.createdAt(),
dto.userAccountDto().email(),
nickname,
dto.userAccountDto().userId()
dto.userAccountDto().userId(),
dto.parentCommentId()
);
}

public boolean hasParentComment() {
return parentCommentId != null;
}

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.fastcampus.projectboard.dto.response;

import com.fastcampus.projectboard.dto.ArticleCommentDto;
import com.fastcampus.projectboard.dto.ArticleWithCommentsDto;
import com.fastcampus.projectboard.dto.HashtagDto;

import java.time.LocalDateTime;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public record ArticleWithCommentsResponse(
@@ -42,10 +43,31 @@ public static ArticleWithCommentsResponse from(ArticleWithCommentsDto dto) {
dto.userAccountDto().email(),
nickname,
dto.userAccountDto().userId(),
dto.articleCommentDtos().stream()
.map(ArticleCommentResponse::from)
.collect(Collectors.toCollection(LinkedHashSet::new))
organizeChildComments(dto.articleCommentDtos())
);
}

private static Set<ArticleCommentResponse> organizeChildComments(Set<ArticleCommentDto> dtos) {
Map<Long, ArticleCommentResponse> map = dtos.stream()
.map(ArticleCommentResponse::from)
.collect(Collectors.toMap(ArticleCommentResponse::id, Function.identity()));
map.values().stream()
.filter(ArticleCommentResponse::hasParentComment)
.forEach(comment -> {
ArticleCommentResponse parentComment = map.get(comment.parentCommentId());
parentComment.childComments().add(comment);
});

return map.values().stream()
.filter(comment -> !comment.hasParentComment())
.collect(Collectors.toCollection(() ->
new TreeSet<>(Comparator
.comparing(ArticleCommentResponse::createdAt)
.reversed()
.thenComparingLong(ArticleCommentResponse::id)
)

));
}

}
Original file line number Diff line number Diff line change
@@ -37,7 +37,14 @@ public void saveArticleComment(ArticleCommentDto dto) {
try {
Article article = articleRepository.getReferenceById(dto.articleId());
UserAccount userAccount = userAccountRepository.getReferenceById(dto.userAccountDto().userId());
articleCommentRepository.save(dto.toEntity(article, userAccount));
ArticleComment articleComment = dto.toEntity(article, userAccount);

if(dto.parentCommentId() != null) {
ArticleComment parentComment = articleCommentRepository.getReferenceById(dto.parentCommentId());
parentComment.addChildComment(articleComment);
} else {
articleCommentRepository.save(articleComment);
}
} catch (EntityNotFoundException e) {
log.warn("댓글 저장 샐패. 댓글 작성에 필요한 정보를 찾을 수 없습니다 - dto:{}", dto);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package com.fastcampus.projectboard.dto.response;

import com.fastcampus.projectboard.dto.ArticleCommentDto;
import com.fastcampus.projectboard.dto.ArticleWithCommentsDto;
import com.fastcampus.projectboard.dto.HashtagDto;
import com.fastcampus.projectboard.dto.UserAccountDto;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;

@DisplayName("DTO - 댓글을 포함한 게시글 응답 테스트")
class ArticleWithCommentsResponseTest {

@DisplayName("자식 댓글이 없는 게시글 + 댓글 dto를 api 응답으로 변환할 때, 댓글을 시간 내림차순 + ID 오름차순으로 정리한다.")
@Test
void givenArticleWithCommentsDtoWithoutChildComments_whenMapping_thenOrganizesCommentsWithCertainOrder() {
// Given
LocalDateTime now = LocalDateTime.now();
Set<ArticleCommentDto> articleCommentDtos = Set.of(
createArticleCommentDto(1L, null, now),
createArticleCommentDto(2L, null, now.plusDays(1L)),
createArticleCommentDto(3L, null, now.plusDays(3L)),
createArticleCommentDto(4L, null, now),
createArticleCommentDto(5L, null, now.plusDays(5L)),
createArticleCommentDto(6L, null, now.plusDays(4L)),
createArticleCommentDto(7L, null, now.plusDays(2L)),
createArticleCommentDto(8L, null, now.plusDays(7L))
);
ArticleWithCommentsDto input = createArticleWithCommentsDto(articleCommentDtos);

// When
ArticleWithCommentsResponse actual = ArticleWithCommentsResponse.from(input);

// Then
assertThat(actual.articleCommentsResponse())
.containsExactly(
createArticleCommentResponse(8L, null, now.plusDays(7L)),
createArticleCommentResponse(5L, null, now.plusDays(5L)),
createArticleCommentResponse(6L, null, now.plusDays(4L)),
createArticleCommentResponse(3L, null, now.plusDays(3L)),
createArticleCommentResponse(7L, null, now.plusDays(2L)),
createArticleCommentResponse(2L, null, now.plusDays(1L)),
createArticleCommentResponse(1L, null, now),
createArticleCommentResponse(4L, null, now)
);
}

@DisplayName("게시글 + 댓글 dto를 api 응답으로 변환할 때, 댓글 부모 자식 관계를 각각의 규칙으로 정렬하여 정리한다.")
@Test
void givenArticleWithCommentsDto_whenMapping_thenOrganizesParentAndChildCommentsWithCertainOrders() {
// Given
LocalDateTime now = LocalDateTime.now();
Set<ArticleCommentDto> articleCommentDtos = Set.of(
createArticleCommentDto(1L, null, now),
createArticleCommentDto(2L, 1L, now.plusDays(1L)),
createArticleCommentDto(3L, 1L, now.plusDays(3L)),
createArticleCommentDto(4L, 1L, now),
createArticleCommentDto(5L, null, now.plusDays(5L)),
createArticleCommentDto(6L, null, now.plusDays(4L)),
createArticleCommentDto(7L, 6L, now.plusDays(2L)),
createArticleCommentDto(8L, 6L, now.plusDays(7L))
);
ArticleWithCommentsDto input = createArticleWithCommentsDto(articleCommentDtos);

// When
ArticleWithCommentsResponse actual = ArticleWithCommentsResponse.from(input);

// Then
assertThat(actual.articleCommentsResponse())
.containsExactly(
createArticleCommentResponse(5L, null, now.plusDays(5)),
createArticleCommentResponse(6L, null, now.plusDays(4)),
createArticleCommentResponse(1L, null, now)
)
.flatExtracting(ArticleCommentResponse::childComments)
.containsExactly(
createArticleCommentResponse(7L, 6L, now.plusDays(2L)),
createArticleCommentResponse(8L, 6L, now.plusDays(7L)),
createArticleCommentResponse(4L, 1L, now),
createArticleCommentResponse(2L, 1L, now.plusDays(1L)),
createArticleCommentResponse(3L, 1L, now.plusDays(3L))
);
}

@DisplayName("게시글 + 댓글 dto를 api 응답으로 변환할 때, 부모 자식 관계 깊이(depth)는 제한이 없다.")
@Test
void givenArticleWithCommentsDto_whenMapping_thenOrganizesParentAndChildCommentsWithoutDepthLimit() {
// Given
LocalDateTime now = LocalDateTime.now();
Set<ArticleCommentDto> articleCommentDtos = Set.of(
createArticleCommentDto(1L, null, now),
createArticleCommentDto(2L, 1L, now.plusDays(1L)),
createArticleCommentDto(3L, 2L, now.plusDays(2L)),
createArticleCommentDto(4L, 3L, now.plusDays(3L)),
createArticleCommentDto(5L, 4L, now.plusDays(4L)),
createArticleCommentDto(6L, 5L, now.plusDays(5L)),
createArticleCommentDto(7L, 6L, now.plusDays(6L)),
createArticleCommentDto(8L, 7L, now.plusDays(7L))
);
ArticleWithCommentsDto input = createArticleWithCommentsDto(articleCommentDtos);

// When
ArticleWithCommentsResponse actual = ArticleWithCommentsResponse.from(input);

// Then
Iterator<ArticleCommentResponse> iterator = actual.articleCommentsResponse().iterator();
long i = 1L;
while (iterator.hasNext()) {
ArticleCommentResponse articleCommentResponse = iterator.next();
assertThat(articleCommentResponse)
.hasFieldOrPropertyWithValue("id", i)
.hasFieldOrPropertyWithValue("parentCommentId", i == 1L ? null : i - 1L)
.hasFieldOrPropertyWithValue("createdAt", now.plusDays(i - 1L));

iterator = articleCommentResponse.childComments().iterator();
i++;
}
}


private ArticleWithCommentsDto createArticleWithCommentsDto(Set<ArticleCommentDto> articleCommentDtos) {
return ArticleWithCommentsDto.of(
1L,
createUserAccountDto(),
articleCommentDtos,
"title",
"content",
Set.of(HashtagDto.of("java")),
LocalDateTime.now(),
"uno",
LocalDateTime.now(),
"uno"
);
}

private UserAccountDto createUserAccountDto() {
return UserAccountDto.of(
"uno",
"password",
"uno@mail.com",
"Uno",
"This is memo",
LocalDateTime.now(),
"uno",
LocalDateTime.now(),
"uno"
);
}

private ArticleCommentDto createArticleCommentDto(Long id, Long parentCommentId, LocalDateTime createdAt) {
return ArticleCommentDto.of(
id,
1L,
createUserAccountDto(),
parentCommentId,
"test comment " + id,
createdAt,
"uno",
createdAt,
"uno"
);
}

private ArticleCommentResponse createArticleCommentResponse(Long id, Long parentCommentId, LocalDateTime createdAt) {
return ArticleCommentResponse.of(
id,
"test comment " + id,
createdAt,
"uno@mail.com",
"Uno",
"uno",
parentCommentId
);
}

}
Loading

0 comments on commit d89e933

Please sign in to comment.