diff --git a/src/docs/asciidoc/api/pick-commnet/pick-comment-reply-register.adoc b/src/docs/asciidoc/api/pick-commnet/pick-comment-reply-register.adoc index cc322447..28433742 100644 --- a/src/docs/asciidoc/api/pick-commnet/pick-comment-reply-register.adoc +++ b/src/docs/asciidoc/api/pick-commnet/pick-comment-reply-register.adoc @@ -2,7 +2,8 @@ == 픽픽픽 답글 작성 API(POST: /devdevdev/api/v1/picks/{pickId}/comments/{pickOriginParentCommentId}/{pickParentCommentId}) * 픽픽픽 답글을 작성한다. -* 회원만 픽픽픽 답글을 작성 할 수 있다. +** 회원인 경우 토큰을 `Authorization` 헤더에 포함시켜야 한다. +** 익명 회원인 경우 `Anonymous-Member-Id` 헤더에 익명 회원 아이디를 포함시켜야 한다. * #픽픽픽 댓글이 삭제 상태# 이면 답글을 작성 할 수 없다. * 최초 댓글에 대한 답글을 작성할 경우 `pickCommentOriginParentId` 값과 `pickParentCommentId` 값이 동일하다. @@ -40,7 +41,7 @@ include::{snippets}/register-pick-comment-reply/response-fields.adoc[] * `픽픽픽 댓글이 없습니다.`: 픽픽픽 댓글이 존재하지 않는 경우 * `삭제된 픽픽픽 댓글에는 답글을 작성할 수 없습니다.`: 픽픽픽 댓글이 삭제된 경우 * `승인 상태가 아닌 픽픽픽에는 답글을 작성할 수 없습니다.`: 픽픽픽이 승인 상태가 아닌 경우 -* `익명 회원은 사용할 수 없는 기능 입니다.`: 익명 회원인 경우 * `회원을 찾을 수 없습니다.`: 회원이 존재하지 않는 경우 +* `익명 사용자가 아닙니다. 잘못된 메소드 호출 입니다.`: 회원이 익명 회원 메소드를 호출한 경우 include::{snippets}/register-pick-comment-reply-bind-exception/response-body.adoc[] \ No newline at end of file diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickCommentService.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickCommentService.java index f70f5fcd..eaa67ba9 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickCommentService.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickCommentService.java @@ -4,6 +4,7 @@ import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType; import com.dreamypatisiel.devdevdev.domain.policy.PickBestCommentsPolicy; +import com.dreamypatisiel.devdevdev.domain.policy.PickPopularScorePolicy; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentRecommendRepository; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentRepository; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentSort; @@ -13,7 +14,6 @@ import com.dreamypatisiel.devdevdev.openai.embeddings.EmbeddingsService; import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; import com.dreamypatisiel.devdevdev.web.dto.request.pick.ModifyPickCommentRequest; -import com.dreamypatisiel.devdevdev.web.dto.request.pick.RegisterPickRepliedCommentRequest; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickCommentRecommendResponse; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickCommentResponse; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickCommentsResponse; @@ -33,9 +33,10 @@ public class GuestPickCommentService extends PickCommonService implements PickCo public GuestPickCommentService(EmbeddingsService embeddingsService, PickBestCommentsPolicy pickBestCommentsPolicy, PickRepository pickRepository, + PickPopularScorePolicy pickPopularScorePolicy, PickCommentRepository pickCommentRepository, PickCommentRecommendRepository pickCommentRecommendRepository) { - super(embeddingsService, pickBestCommentsPolicy, pickRepository, pickCommentRepository, + super(embeddingsService, pickBestCommentsPolicy, pickPopularScorePolicy, pickRepository, pickCommentRepository, pickCommentRecommendRepository); } @@ -47,7 +48,7 @@ public PickCommentResponse registerPickComment(Long pickId, PickCommentDto pickC @Override public PickCommentResponse registerPickRepliedComment(Long pickParentCommentId, Long pickCommentOriginParentId, - Long pickId, RegisterPickRepliedCommentRequest pickSubCommentRequest, + Long pickId, PickCommentDto pickCommentDto, Authentication authentication) { throw new AccessDeniedException(INVALID_ANONYMOUS_CAN_NOT_USE_THIS_FUNCTION_MESSAGE); diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickCommentServiceV2.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickCommentServiceV2.java index 3cf6a458..5f5734f3 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickCommentServiceV2.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickCommentServiceV2.java @@ -25,7 +25,6 @@ import com.dreamypatisiel.devdevdev.openai.embeddings.EmbeddingsService; import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; import com.dreamypatisiel.devdevdev.web.dto.request.pick.ModifyPickCommentRequest; -import com.dreamypatisiel.devdevdev.web.dto.request.pick.RegisterPickRepliedCommentRequest; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickCommentRecommendResponse; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickCommentResponse; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickCommentsResponse; @@ -42,7 +41,6 @@ public class GuestPickCommentServiceV2 extends PickCommonService implements PickCommentService { private final AnonymousMemberService anonymousMemberService; - private final PickPopularScorePolicy pickPopularScorePolicy; private final PickVoteRepository pickVoteRepository; @@ -54,10 +52,9 @@ public GuestPickCommentServiceV2(EmbeddingsService embeddingsService, AnonymousMemberService anonymousMemberService, PickPopularScorePolicy pickPopularScorePolicy, PickVoteRepository pickVoteRepository) { - super(embeddingsService, pickBestCommentsPolicy, pickRepository, pickCommentRepository, - pickCommentRecommendRepository); + super(embeddingsService, pickBestCommentsPolicy, pickPopularScorePolicy, pickRepository, + pickCommentRepository, pickCommentRecommendRepository); this.anonymousMemberService = anonymousMemberService; - this.pickPopularScorePolicy = pickPopularScorePolicy; this.pickVoteRepository = pickVoteRepository; } @@ -110,11 +107,33 @@ public PickCommentResponse registerPickComment(Long pickId, PickCommentDto pickC } @Override + @Transactional public PickCommentResponse registerPickRepliedComment(Long pickParentCommentId, Long pickCommentOriginParentId, - Long pickId, RegisterPickRepliedCommentRequest pickSubCommentRequest, + Long pickId, PickCommentDto pickCommentDto, Authentication authentication) { - throw new AccessDeniedException(INVALID_ANONYMOUS_CAN_NOT_USE_THIS_FUNCTION_MESSAGE); + // 익명 회원인지 검증 + AuthenticationMemberUtils.validateAnonymousMethodCall(authentication); + + String contents = pickCommentDto.getContents(); + String anonymousMemberId = pickCommentDto.getAnonymousMemberId(); + + // 익명 회원 추출 + AnonymousMember anonymousMember = anonymousMemberService.findOrCreateAnonymousMember(anonymousMemberId); + + // 픽픽픽 댓글 로직 수행 + PickReplyContext pickReplyContext = prepareForReplyRegistration(pickParentCommentId, pickCommentOriginParentId, pickId); + + PickComment findParentPickComment = pickReplyContext.parentPickComment(); + PickComment findOriginParentPickComment = pickReplyContext.originParentPickComment(); + Pick findPick = pickReplyContext.pick(); + + // 픽픽픽 서브 댓글(답글) 생성 + PickComment pickRepliedComment = PickComment.createRepliedCommentByAnonymousMember(new CommentContents(contents), + findParentPickComment, findOriginParentPickComment, anonymousMember, findPick); + pickCommentRepository.save(pickRepliedComment); + + return new PickCommentResponse(pickRepliedComment.getId()); } @Override diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickService.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickService.java index 9274b365..cbad78dc 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickService.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickService.java @@ -55,7 +55,6 @@ public class GuestPickService extends PickCommonService implements PickService { public static final String INVALID_ANONYMOUS_CAN_NOT_USE_THIS_FUNCTION_MESSAGE = "비회원은 현재 해당 기능을 이용할 수 없습니다."; - private final PickPopularScorePolicy pickPopularScorePolicy; private final PickVoteRepository pickVoteRepository; private final TimeProvider timeProvider; private final AnonymousMemberService anonymousMemberService; @@ -67,9 +66,8 @@ public GuestPickService(PickRepository pickRepository, EmbeddingsService embeddi PickPopularScorePolicy pickPopularScorePolicy, PickVoteRepository pickVoteRepository, TimeProvider timeProvider, AnonymousMemberService anonymousMemberService) { - super(embeddingsService, pickBestCommentsPolicy, pickRepository, pickCommentRepository, + super(embeddingsService, pickBestCommentsPolicy, pickPopularScorePolicy, pickRepository, pickCommentRepository, pickCommentRecommendRepository); - this.pickPopularScorePolicy = pickPopularScorePolicy; this.pickVoteRepository = pickVoteRepository; this.anonymousMemberService = anonymousMemberService; this.timeProvider = timeProvider; diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickCommentService.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickCommentService.java index e72e1267..4a109d4a 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickCommentService.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickCommentService.java @@ -1,9 +1,7 @@ package com.dreamypatisiel.devdevdev.domain.service.pick; import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_CAN_NOT_ACTION_DELETED_PICK_COMMENT_MESSAGE; -import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_CAN_NOT_REPLY_DELETED_PICK_COMMENT_MESSAGE; import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_APPROVAL_STATUS_PICK_COMMENT_MESSAGE; -import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_APPROVAL_STATUS_PICK_REPLY_MESSAGE; import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_FOUND_PICK_COMMENT_MESSAGE; import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_FOUND_PICK_MESSAGE; import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_FOUND_PICK_VOTE_MESSAGE; @@ -29,7 +27,6 @@ import com.dreamypatisiel.devdevdev.openai.embeddings.EmbeddingsService; import com.dreamypatisiel.devdevdev.web.dto.SliceCommentCustom; import com.dreamypatisiel.devdevdev.web.dto.request.pick.ModifyPickCommentRequest; -import com.dreamypatisiel.devdevdev.web.dto.request.pick.RegisterPickRepliedCommentRequest; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickCommentRecommendResponse; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickCommentResponse; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickCommentsResponse; @@ -47,7 +44,6 @@ public class MemberPickCommentService extends PickCommonService implements PickC private final TimeProvider timeProvider; private final MemberProvider memberProvider; - private final PickPopularScorePolicy pickPopularScorePolicy; private final PickRepository pickRepository; private final PickVoteRepository pickVoteRepository; @@ -59,11 +55,10 @@ public MemberPickCommentService(TimeProvider timeProvider, MemberProvider member PickRepository pickRepository, PickVoteRepository pickVoteRepository, PickCommentRepository pickCommentRepository, PickCommentRecommendRepository pickCommentRecommendRepository) { - super(embeddingsService, pickBestCommentsPolicy, pickRepository, pickCommentRepository, + super(embeddingsService, pickBestCommentsPolicy, pickPopularScorePolicy, pickRepository, pickCommentRepository, pickCommentRecommendRepository); this.timeProvider = timeProvider; this.memberProvider = memberProvider; - this.pickPopularScorePolicy = pickPopularScorePolicy; this.pickRepository = pickRepository; this.pickVoteRepository = pickVoteRepository; this.pickCommentRepository = pickCommentRepository; @@ -126,31 +121,20 @@ public PickCommentResponse registerPickComment(Long pickId, PickCommentDto pickC public PickCommentResponse registerPickRepliedComment(Long pickParentCommentId, Long pickCommentOriginParentId, Long pickId, - RegisterPickRepliedCommentRequest pickSubCommentRequest, + PickCommentDto pickCommentDto, Authentication authentication) { - String contents = pickSubCommentRequest.getContents(); + String contents = pickCommentDto.getContents(); // 회원 조회 Member findMember = memberProvider.getMemberByAuthentication(authentication); - // 답글 대상의 픽픽픽 댓글 조회 - PickComment findParentPickComment = pickCommentRepository.findWithPickByIdAndPickId(pickParentCommentId, pickId) - .orElseThrow(() -> new NotFoundException(INVALID_NOT_FOUND_PICK_COMMENT_MESSAGE)); - - // 픽픽픽 게시글의 승인 상태 검증 - Pick findPick = findParentPickComment.getPick(); - validateIsApprovalPickContentStatus(findPick, INVALID_NOT_APPROVAL_STATUS_PICK_REPLY_MESSAGE, - REGISTER); - // 댓글 총 갯수 증가 및 인기점수 반영 - findPick.incrementCommentTotalCount(); - findPick.changePopularScore(pickPopularScorePolicy); + // 픽픽픽 댓글 로직 수행 + PickReplyContext pickReplyContext = prepareForReplyRegistration(pickParentCommentId, pickCommentOriginParentId, pickId); - // 픽픽픽 최초 댓글 검증 및 반환 - PickComment findOriginParentPickComment = getAndValidateOriginParentPickComment( - pickCommentOriginParentId, findParentPickComment); - // 픽픽픽 최초 댓글의 답글 갯수 증가 - findOriginParentPickComment.incrementReplyTotalCount(); + PickComment findParentPickComment = pickReplyContext.parentPickComment(); + PickComment findOriginParentPickComment = pickReplyContext.originParentPickComment(); + Pick findPick = pickReplyContext.pick(); // 픽픽픽 서브 댓글(답글) 생성 PickComment pickRepliedComment = PickComment.createRepliedCommentByMember(new CommentContents(contents), @@ -160,28 +144,6 @@ public PickCommentResponse registerPickRepliedComment(Long pickParentCommentId, return new PickCommentResponse(pickRepliedComment.getId()); } - private PickComment getAndValidateOriginParentPickComment(Long pickCommentOriginParentId, - PickComment parentPickComment) { - - // 픽픽픽 답글 대상의 댓글이 삭제 상태이면 - validateIsDeletedPickComment(parentPickComment, INVALID_CAN_NOT_REPLY_DELETED_PICK_COMMENT_MESSAGE, REGISTER); - - // 픽픽픽 답글 대상의 댓글이 최초 댓글이면 - if (parentPickComment.isEqualsId(pickCommentOriginParentId)) { - return parentPickComment; - } - - // 픽픽픽 답글 대상의 댓글의 메인 댓글 조회 - PickComment findOriginParentPickComment = pickCommentRepository.findById(pickCommentOriginParentId) - .orElseThrow(() -> new NotFoundException(INVALID_NOT_FOUND_PICK_COMMENT_MESSAGE)); - - // 픽픽픽 최초 댓글이 삭제 상태이면 - validateIsDeletedPickComment(findOriginParentPickComment, INVALID_CAN_NOT_REPLY_DELETED_PICK_COMMENT_MESSAGE, - REGISTER); - - return findOriginParentPickComment; - } - /** * @Note: 회원 자신이 작성한 픽픽픽 댓글/답글을 수정한다. 픽픽픽 공개 여부는 수정할 수 없다. * @Author: 장세웅 diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickService.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickService.java index 3692fa9c..3bf47be8 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickService.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickService.java @@ -85,7 +85,7 @@ public class MemberPickService extends PickCommonService implements PickService private final PickOptionRepository pickOptionRepository; private final PickOptionImageRepository pickOptionImageRepository; private final PickVoteRepository pickVoteRepository; - private final PickPopularScorePolicy pickPopularScorePolicy; + private final TimeProvider timeProvider; public MemberPickService(EmbeddingsService embeddingsService, PickRepository pickRepository, @@ -96,7 +96,7 @@ public MemberPickService(EmbeddingsService embeddingsService, PickRepository pic PickCommentRecommendRepository pickCommentRecommendRepository, PickPopularScorePolicy pickPopularScorePolicy, PickBestCommentsPolicy pickBestCommentsPolicy, TimeProvider timeProvider) { - super(embeddingsService, pickBestCommentsPolicy, pickRepository, pickCommentRepository, + super(embeddingsService, pickBestCommentsPolicy, pickPopularScorePolicy, pickRepository, pickCommentRepository, pickCommentRecommendRepository); this.awsS3Properties = awsS3Properties; this.awsS3Uploader = awsS3Uploader; @@ -104,7 +104,6 @@ public MemberPickService(EmbeddingsService embeddingsService, PickRepository pic this.pickOptionRepository = pickOptionRepository; this.pickOptionImageRepository = pickOptionImageRepository; this.pickVoteRepository = pickVoteRepository; - this.pickPopularScorePolicy = pickPopularScorePolicy; this.timeProvider = timeProvider; } @@ -129,14 +128,12 @@ public Slice findPicksMain(Pageable pageable, Long pickId, Pic } /** - * @Note: 이미지 업로드와 DB 저장을 하나의 작업(Transcation)으로 묶어서, 데이터 정합성을 유지한다.
이때 pick_option_id는 null 인 상태 - * 입니다.

+ * @Note: 이미지 업로드와 DB 저장을 하나의 작업(Transcation)으로 묶어서, 데이터 정합성을 유지한다.
이때 pick_option_id는 null 인 상태 입니다.

*

- * 이미지 업로드 실패시 IOException이 발생할 수 있는데, 이때 catch로 처리하여 데이터 정합성 유지합니다.
즉, IOException이 발생해도 rollback하지 않는다는 의미 - * 입니다.

+ * 이미지 업로드 실패시 IOException이 발생할 수 있는데, 이때 catch로 처리하여 데이터 정합성 유지합니다.
즉, IOException이 발생해도 rollback하지 않는다는 의미 입니다. + *

*

- * 단, Transcation이 길게 유지되면 추후 DB Connection을 오랫동안 유지하기 때문에 많은 트래픽이 발생할 때 DB Connection이 부족해지는 현상이 발생할 수 - * 있습니다.

+ * 단, Transcation이 길게 유지되면 추후 DB Connection을 오랫동안 유지하기 때문에 많은 트래픽이 발생할 때 DB Connection이 부족해지는 현상이 발생할 수 있습니다.

*

* (Transcation은 기본적으로 RuntimeException에 대해서만 Rollback 합니다. AmazonClient의 putObject(...)는 RuntimeException을 * 발생시킵니다.)

@@ -282,8 +279,8 @@ public PickDetailResponse findPickDetail(Long pickId, String anonymousMemberId, } /** - * @Note: member 1:N pick 1:N pickOption 1:N pickVote
pick 1:N pickVote N:1 member
연관관계가 다소 복잡하니, 직접 - * ERD를 확인하는 것을 권장합니다.
투표 이력이 있는 경우 - 투표 이력이 없는 경우 + * @Note: member 1:N pick 1:N pickOption 1:N pickVote
pick 1:N pickVote N:1 member
연관관계가 다소 복잡하니, 직접 ERD를 확인하는 것을 + * 권장합니다.
투표 이력이 있는 경우 - 투표 이력이 없는 경우 * @Author: ralph * @Since: 2024.05.29 */ diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/PickCommentService.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/PickCommentService.java index 9845b3e9..2311132e 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/PickCommentService.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/PickCommentService.java @@ -5,7 +5,6 @@ import com.dreamypatisiel.devdevdev.domain.service.pick.dto.PickCommentDto; import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; import com.dreamypatisiel.devdevdev.web.dto.request.pick.ModifyPickCommentRequest; -import com.dreamypatisiel.devdevdev.web.dto.request.pick.RegisterPickRepliedCommentRequest; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickCommentRecommendResponse; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickCommentResponse; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickCommentsResponse; @@ -26,7 +25,7 @@ PickCommentResponse registerPickComment(Long pickId, PickCommentResponse registerPickRepliedComment(Long pickParentCommentId, Long pickCommentOriginParentId, - Long pickId, RegisterPickRepliedCommentRequest pickSubCommentRequest, + Long pickId, PickCommentDto pickCommentDto, Authentication authentication); PickCommentResponse modifyPickComment(Long pickCommentId, Long pickId, diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/PickCommonService.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/PickCommonService.java index ff11ab6d..89c73faf 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/PickCommonService.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/PickCommonService.java @@ -1,7 +1,11 @@ package com.dreamypatisiel.devdevdev.domain.service.pick; +import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_CAN_NOT_REPLY_DELETED_PICK_COMMENT_MESSAGE; import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_APPROVAL_STATUS_PICK_MESSAGE; +import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_APPROVAL_STATUS_PICK_REPLY_MESSAGE; +import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_FOUND_PICK_COMMENT_MESSAGE; import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_FOUND_PICK_MESSAGE; +import static com.dreamypatisiel.devdevdev.domain.service.pick.PickCommentService.REGISTER; import com.dreamypatisiel.devdevdev.domain.entity.Member; import com.dreamypatisiel.devdevdev.domain.entity.Pick; @@ -9,6 +13,7 @@ import com.dreamypatisiel.devdevdev.domain.entity.enums.ContentStatus; import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType; import com.dreamypatisiel.devdevdev.domain.policy.PickBestCommentsPolicy; +import com.dreamypatisiel.devdevdev.domain.policy.PickPopularScorePolicy; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentRecommendRepository; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentRepository; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentSort; @@ -45,6 +50,7 @@ public class PickCommonService { private final EmbeddingsService embeddingsService; private final PickBestCommentsPolicy pickBestCommentsPolicy; + protected final PickPopularScorePolicy pickPopularScorePolicy; protected final PickRepository pickRepository; protected final PickCommentRepository pickCommentRepository; @@ -231,4 +237,54 @@ protected List findPickBestComments(int size, Long pickId, pickBestCommentReplies)) .toList(); } + + protected PickComment getAndValidateOriginParentPickComment(Long pickCommentOriginParentId, + PickComment parentPickComment) { + + // 픽픽픽 답글 대상의 댓글이 삭제 상태이면 + validateIsDeletedPickComment(parentPickComment, INVALID_CAN_NOT_REPLY_DELETED_PICK_COMMENT_MESSAGE, REGISTER); + + // 픽픽픽 답글 대상의 댓글이 최초 댓글이면 + if (parentPickComment.isEqualsId(pickCommentOriginParentId)) { + return parentPickComment; + } + + // 픽픽픽 답글 대상의 댓글의 메인 댓글 조회 + PickComment findOriginParentPickComment = pickCommentRepository.findById(pickCommentOriginParentId) + .orElseThrow(() -> new NotFoundException(INVALID_NOT_FOUND_PICK_COMMENT_MESSAGE)); + + // 픽픽픽 최초 댓글이 삭제 상태이면 + validateIsDeletedPickComment(findOriginParentPickComment, INVALID_CAN_NOT_REPLY_DELETED_PICK_COMMENT_MESSAGE, + REGISTER); + + return findOriginParentPickComment; + } + + @Transactional + protected PickReplyContext prepareForReplyRegistration(Long pickParentCommentId, Long pickCommentOriginParentId, + Long pickId) { + // 답글 대상의 픽픽픽 댓글 조회 + PickComment findParentPickComment = pickCommentRepository.findWithPickByIdAndPickId(pickParentCommentId, pickId) + .orElseThrow(() -> new NotFoundException(INVALID_NOT_FOUND_PICK_COMMENT_MESSAGE)); + + // 픽픽픽 게시글의 승인 상태 검증 + Pick findPick = findParentPickComment.getPick(); + validateIsApprovalPickContentStatus(findPick, INVALID_NOT_APPROVAL_STATUS_PICK_REPLY_MESSAGE, REGISTER); + + // 댓글 총 갯수 증가 및 인기점수 반영 + findPick.incrementCommentTotalCount(); + findPick.changePopularScore(pickPopularScorePolicy); + + // 픽픽픽 최초 댓글 검증 및 반환 + PickComment findOriginParentPickComment = getAndValidateOriginParentPickComment( + pickCommentOriginParentId, findParentPickComment); + + // 픽픽픽 최초 댓글의 답글 갯수 증가 + findOriginParentPickComment.incrementReplyTotalCount(); + + return new PickReplyContext(findPick, findOriginParentPickComment, findParentPickComment); + } + + public record PickReplyContext(Pick pick, PickComment originParentPickComment, PickComment parentPickComment) { + } } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/dto/PickCommentDto.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/dto/PickCommentDto.java index e845054a..8895b8fb 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/dto/PickCommentDto.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/dto/PickCommentDto.java @@ -1,6 +1,7 @@ package com.dreamypatisiel.devdevdev.domain.service.pick.dto; import com.dreamypatisiel.devdevdev.web.dto.request.pick.RegisterPickCommentRequest; +import com.dreamypatisiel.devdevdev.web.dto.request.pick.RegisterPickRepliedCommentRequest; import lombok.Builder; import lombok.Data; @@ -26,9 +27,10 @@ public static PickCommentDto createRegisterCommentDto(RegisterPickCommentRequest .build(); } - public static PickCommentDto createRepliedCommentDto(String contents, String anonymousMemberId) { + public static PickCommentDto createRepliedCommentDto(RegisterPickRepliedCommentRequest registerPickRepliedCommentRequest, + String anonymousMemberId) { return PickCommentDto.builder() - .contents(contents) + .contents(registerPickRepliedCommentRequest.getContents()) .anonymousMemberId(anonymousMemberId) .build(); } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/web/controller/pick/PickCommentController.java b/src/main/java/com/dreamypatisiel/devdevdev/web/controller/pick/PickCommentController.java index ec820507..7b7d9972 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/web/controller/pick/PickCommentController.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/web/controller/pick/PickCommentController.java @@ -65,7 +65,7 @@ public ResponseEntity> registerPickComment( return ResponseEntity.ok(BasicResponse.success(pickCommentResponse)); } - @Operation(summary = "픽픽픽 답글 작성", description = "회원은 픽픽픽 댓글에 답글을 작성할 수 있습니다.") + @Operation(summary = "픽픽픽 답글 작성", description = "픽픽픽 댓글에 답글을 작성할 수 있습니다.") @PostMapping("/picks/{pickId}/comments/{pickOriginParentCommentId}/{pickParentCommentId}") public ResponseEntity> registerPickRepliedComment( @PathVariable Long pickId, @@ -74,10 +74,14 @@ public ResponseEntity> registerPickRepliedCom @RequestBody @Validated RegisterPickRepliedCommentRequest registerPickRepliedCommentRequest) { Authentication authentication = AuthenticationMemberUtils.getAuthentication(); + String anonymousMemberId = HttpRequestUtils.getHeaderValue(HEADER_ANONYMOUS_MEMBER_ID); + + PickCommentDto repliedCommentDto = PickCommentDto.createRepliedCommentDto(registerPickRepliedCommentRequest, + anonymousMemberId); PickCommentService pickCommentService = pickServiceStrategy.pickCommentService(); PickCommentResponse pickCommentResponse = pickCommentService.registerPickRepliedComment( - pickParentCommentId, pickOriginParentCommentId, pickId, registerPickRepliedCommentRequest, + pickParentCommentId, pickOriginParentCommentId, pickId, repliedCommentDto, authentication); return ResponseEntity.ok(BasicResponse.success(pickCommentResponse)); diff --git a/src/main/java/com/dreamypatisiel/devdevdev/web/controller/pick/PickController.java b/src/main/java/com/dreamypatisiel/devdevdev/web/controller/pick/PickController.java index 1ce1ab1e..ecef1fd7 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/web/controller/pick/PickController.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/web/controller/pick/PickController.java @@ -8,6 +8,7 @@ import com.dreamypatisiel.devdevdev.domain.service.pick.PickServiceStrategy; import com.dreamypatisiel.devdevdev.domain.service.pick.dto.VotePickOptionDto; import com.dreamypatisiel.devdevdev.global.utils.AuthenticationMemberUtils; +import com.dreamypatisiel.devdevdev.global.utils.HttpRequestUtils; import com.dreamypatisiel.devdevdev.openai.data.request.EmbeddingRequest; import com.dreamypatisiel.devdevdev.openai.data.response.Embedding; import com.dreamypatisiel.devdevdev.openai.data.response.OpenAIResponse; @@ -63,10 +64,10 @@ public class PickController { public ResponseEntity>> getPicksMain( @PageableDefault(sort = "id", direction = Direction.DESC) Pageable pageable, @RequestParam(required = false) Long pickId, - @RequestParam(required = false) PickSort pickSort, - @RequestHeader(value = HEADER_ANONYMOUS_MEMBER_ID, required = false) String anonymousMemberId) { + @RequestParam(required = false) PickSort pickSort) { Authentication authentication = AuthenticationMemberUtils.getAuthentication(); + String anonymousMemberId = HttpRequestUtils.getHeaderValue(HEADER_ANONYMOUS_MEMBER_ID); PickService pickService = pickServiceStrategy.getPickService(); Slice response = pickService.findPicksMain(pageable, pickId, pickSort, anonymousMemberId, @@ -158,10 +159,10 @@ public ResponseEntity> getPickDetail(@PathVari @Operation(summary = "픽픽픽 선택지 투표", description = "픽픽픽 상세 페이지에서 픽픽픽 선택지에 투표합니다.") @PostMapping("/picks/vote") public ResponseEntity> votePickOption( - @RequestBody @Validated VotePickOptionRequest votePickOptionRequest, - @RequestHeader(value = HEADER_ANONYMOUS_MEMBER_ID, required = false) String anonymousMemberId) { + @RequestBody @Validated VotePickOptionRequest votePickOptionRequest) { Authentication authentication = AuthenticationMemberUtils.getAuthentication(); + String anonymousMemberId = HttpRequestUtils.getHeaderValue(HEADER_ANONYMOUS_MEMBER_ID); PickService pickService = pickServiceStrategy.getPickService(); diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickCommentServiceV2Test.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickCommentServiceV2Test.java index 15d7aa8c..edfbce7b 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickCommentServiceV2Test.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickCommentServiceV2Test.java @@ -1,13 +1,18 @@ package com.dreamypatisiel.devdevdev.domain.service.pick; +import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_CAN_NOT_REPLY_DELETED_PICK_COMMENT_MESSAGE; import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_APPROVAL_STATUS_PICK_COMMENT_MESSAGE; +import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_APPROVAL_STATUS_PICK_REPLY_MESSAGE; +import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_FOUND_PICK_COMMENT_MESSAGE; import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_FOUND_PICK_MESSAGE; import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_FOUND_PICK_VOTE_MESSAGE; import static com.dreamypatisiel.devdevdev.domain.service.pick.PickCommentService.REGISTER; import static com.dreamypatisiel.devdevdev.domain.service.pick.PickTestUtils.createPick; +import static com.dreamypatisiel.devdevdev.domain.service.pick.PickTestUtils.createPickComment; import static com.dreamypatisiel.devdevdev.domain.service.pick.PickTestUtils.createPickOption; import static com.dreamypatisiel.devdevdev.domain.service.pick.PickTestUtils.createPickOptionImage; import static com.dreamypatisiel.devdevdev.domain.service.pick.PickTestUtils.createPickVote; +import static com.dreamypatisiel.devdevdev.domain.service.pick.PickTestUtils.createReplidPickComment; import static com.dreamypatisiel.devdevdev.domain.service.pick.PickTestUtils.createSocialDto; import static com.dreamypatisiel.devdevdev.global.utils.AuthenticationMemberUtils.INVALID_METHODS_CALL_MESSAGE; import static org.assertj.core.api.Assertions.assertThat; @@ -26,6 +31,7 @@ import com.dreamypatisiel.devdevdev.domain.entity.PickOption; import com.dreamypatisiel.devdevdev.domain.entity.PickOptionImage; import com.dreamypatisiel.devdevdev.domain.entity.PickVote; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.CommentContents; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Count; import com.dreamypatisiel.devdevdev.domain.entity.embedded.PickOptionContents; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Title; @@ -47,15 +53,18 @@ import com.dreamypatisiel.devdevdev.global.security.oauth2.model.SocialMemberDto; import com.dreamypatisiel.devdevdev.global.security.oauth2.model.UserPrincipal; import com.dreamypatisiel.devdevdev.global.utils.AuthenticationMemberUtils; +import com.dreamypatisiel.devdevdev.web.dto.request.pick.RegisterPickRepliedCommentRequest; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickCommentResponse; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; +import java.time.LocalDateTime; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.core.Authentication; @@ -360,4 +369,281 @@ void registerPickCommentNotFoundPickMainVote() { .isInstanceOf(NotFoundException.class) .hasMessage(INVALID_NOT_FOUND_PICK_VOTE_MESSAGE); } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + @DisplayName("익명회원이 승인상태의 픽픽픽의 삭제상태가 아닌 댓글에 답글을 작성한다.") + void registerPickRepliedComment(Boolean isPublic) { + // given + // 익명회원 생성 + AnonymousMember anonymousMember = AnonymousMember.create("anonymousMemberId", "익명으로 개발하는 댑댑이"); + anonymousMemberRepository.save(anonymousMember); + + Authentication authentication = mock(Authentication.class); + when(authentication.getPrincipal()).thenReturn(AuthenticationMemberUtils.ANONYMOUS_USER); + + // 픽픽픽 작성자 생성 + SocialMemberDto authorSocialMemberDto = createSocialDto("authorId", "author", + nickname, password, "authorDreamy5patisiel@kakao.com", socialType, role); + Member author = Member.createMemberBy(authorSocialMemberDto); + memberRepository.save(author); + + // 픽픽픽 생성 + Pick pick = createPick(new Title("픽픽픽 타이틀"), ContentStatus.APPROVAL, new Count(0L), new Count(0L), + new Count(0L), new Count(0L), author); + pickRepository.save(pick); + + // 픽픽픽 댓글 생성 + PickComment pickComment = createPickComment(new CommentContents("댓글1"), isPublic, author, pick); + pickCommentRepository.save(pickComment); + + // 픽픽픽 답글 생성 + PickComment repliedPickComment = createReplidPickComment(new CommentContents("댓글1의 답글1"), anonymousMember, pick, + pickComment, pickComment); + pickCommentRepository.save(repliedPickComment); + + em.flush(); + em.clear(); + + RegisterPickRepliedCommentRequest request = new RegisterPickRepliedCommentRequest("댓글1의 답글1의 답글"); + PickCommentDto repliedCommentDto = PickCommentDto.createRepliedCommentDto(request, "anonymousMemberId"); + + // when + PickCommentResponse response = guestPickCommentServiceV2.registerPickRepliedComment( + repliedPickComment.getId(), pickComment.getId(), pick.getId(), repliedCommentDto, authentication); + + em.flush(); + em.clear(); + + // then + assertThat(response.getPickCommentId()).isNotNull(); + + PickComment findPickComment = pickCommentRepository.findById(response.getPickCommentId()).get(); + assertAll( + () -> assertThat(findPickComment.getContents().getCommentContents()).isEqualTo("댓글1의 답글1의 답글"), + () -> assertThat(findPickComment.getIsPublic()).isFalse(), + () -> assertThat(findPickComment.getDeletedAt()).isNull(), + () -> assertThat(findPickComment.getBlameTotalCount().getCount()).isEqualTo(0L), + () -> assertThat(findPickComment.getRecommendTotalCount().getCount()).isEqualTo(0L), + () -> assertThat(findPickComment.getPick().getId()).isEqualTo(pick.getId()), + () -> assertThat(findPickComment.getCreatedAnonymousBy().getId()).isEqualTo(anonymousMember.getId()), + () -> assertThat(findPickComment.getParent().getId()).isEqualTo(repliedPickComment.getId()), + () -> assertThat(findPickComment.getOriginParent().getId()).isEqualTo(pickComment.getId()), + () -> assertThat(findPickComment.getOriginParent().getReplyTotalCount().getCount()).isEqualTo(1L) + ); + } + + @Test + @DisplayName("회원이 익명회원 전용 픽픽픽 답글을 작성할 때 예외가 발생한다.") + void registerPickRepliedCommentMemberException() { + // given + // 회원 생성 + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + UserPrincipal userPrincipal = UserPrincipal.createByMember(member); + SecurityContext context = SecurityContextHolder.getContext(); + context.setAuthentication(new OAuth2AuthenticationToken(userPrincipal, userPrincipal.getAuthorities(), + userPrincipal.getSocialType().name())); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + RegisterPickRepliedCommentRequest request = new RegisterPickRepliedCommentRequest("댓글1의 답글1의 답글"); + PickCommentDto repliedCommentDto = PickCommentDto.createRepliedCommentDto(request, "anonymousMemberId"); + + // when // then + assertThatThrownBy( + () -> guestPickCommentServiceV2.registerPickRepliedComment(0L, 0L, 0L, repliedCommentDto, authentication)) + .isInstanceOf(IllegalStateException.class) + .hasMessage(INVALID_METHODS_CALL_MESSAGE); + } + + @Test + @DisplayName("익명회원이 픽픽픽 답글을 작성할 때, 답글 대상의 댓글이 존재하지 않으면 예외가 발생한다.") + void registerPickRepliedCommentNotFoundExceptionParent() { + // given + // 익명회원 생성 + AnonymousMember anonymousMember = AnonymousMember.create("anonymousMemberId", "익명으로 개발하는 댑댑이"); + anonymousMemberRepository.save(anonymousMember); + + Authentication authentication = mock(Authentication.class); + when(authentication.getPrincipal()).thenReturn(AuthenticationMemberUtils.ANONYMOUS_USER); + + // 회원 생성 + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + // 픽픽픽 생성 + Pick pick = createPick(new Title("픽픽픽 타이틀"), ContentStatus.APPROVAL, member); + pickRepository.save(pick); + + RegisterPickRepliedCommentRequest request = new RegisterPickRepliedCommentRequest("댓글1의 답글1의 답글"); + PickCommentDto repliedCommentDto = PickCommentDto.createRepliedCommentDto(request, "anonymousMemberId"); + + // when // then + assertThatThrownBy( + () -> guestPickCommentServiceV2.registerPickRepliedComment(0L, 0L, 0L, repliedCommentDto, authentication)) + .isInstanceOf(NotFoundException.class) + .hasMessage(INVALID_NOT_FOUND_PICK_COMMENT_MESSAGE); + } + + @ParameterizedTest + @EnumSource(value = ContentStatus.class, mode = Mode.EXCLUDE, names = {"APPROVAL"}) + @DisplayName("익명회원이 픽픽픽 답글을 작성할 때, 픽픽픽이 승인상태가 아니면 예외가 발생한다.") + void registerPickRepliedCommentPickIsNotApproval(ContentStatus contentStatus) { + // given + // 익명회원 생성 + AnonymousMember anonymousMember = AnonymousMember.create("anonymousMemberId", "익명으로 개발하는 댑댑이"); + anonymousMemberRepository.save(anonymousMember); + + Authentication authentication = mock(Authentication.class); + when(authentication.getPrincipal()).thenReturn(AuthenticationMemberUtils.ANONYMOUS_USER); + + // 회원 생성 + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + // 픽픽픽 생성 + Pick pick = createPick(new Title("픽픽픽 타이틀"), contentStatus, member); + pickRepository.save(pick); + + // 픽픽픽 댓글 생성 + PickComment pickComment = createPickComment(new CommentContents("댓글1"), false, member, pick); + pickCommentRepository.save(pickComment); + + RegisterPickRepliedCommentRequest request = new RegisterPickRepliedCommentRequest("댓글1의 답글1의 답글"); + PickCommentDto repliedCommentDto = PickCommentDto.createRepliedCommentDto(request, "anonymousMemberId"); + + // when // then + assertThatThrownBy( + () -> guestPickCommentServiceV2.registerPickRepliedComment( + pickComment.getId(), pickComment.getId(), pick.getId(), repliedCommentDto, authentication)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(INVALID_NOT_APPROVAL_STATUS_PICK_REPLY_MESSAGE, REGISTER); + } + + @Test + @DisplayName("익명회원이 픽픽픽 답글을 작성할 때 답글 대상의 댓글이 삭제 상태 이면 예외가 발생한다." + + "(최초 댓글이 삭제상태이고 해당 댓글에 답글을 작성하는 경우)") + void registerPickRepliedCommentDeleted() { + // given + // 익명회원 생성 + AnonymousMember anonymousMember = AnonymousMember.create("anonymousMemberId", "익명으로 개발하는 댑댑이"); + anonymousMemberRepository.save(anonymousMember); + + Authentication authentication = mock(Authentication.class); + when(authentication.getPrincipal()).thenReturn(AuthenticationMemberUtils.ANONYMOUS_USER); + + // 회원 생성 + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + // 픽픽픽 생성 + Pick pick = createPick(new Title("픽픽픽 타이틀"), ContentStatus.APPROVAL, new Count(0L), new Count(0L), + new Count(0L), new Count(0L), member); + pickRepository.save(pick); + + // 삭제상태의 픽픽픽 댓글 생성 + PickComment pickComment = createPickComment(new CommentContents("댓글1"), false, member, pick); + pickComment.changeDeletedAtByMember(LocalDateTime.now(), member); + pickCommentRepository.save(pickComment); + + RegisterPickRepliedCommentRequest request = new RegisterPickRepliedCommentRequest("댓글1의 답글1의 답글"); + PickCommentDto repliedCommentDto = PickCommentDto.createRepliedCommentDto(request, "anonymousMemberId"); + + // when // then + assertThatThrownBy( + () -> guestPickCommentServiceV2.registerPickRepliedComment( + pickComment.getId(), pickComment.getId(), pick.getId(), repliedCommentDto, authentication)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(INVALID_CAN_NOT_REPLY_DELETED_PICK_COMMENT_MESSAGE, REGISTER); + } + + @Test + @DisplayName("익명회원이 픽픽픽 답글을 작성할 때 답글 대상의 댓글이 삭제 상태 이면 예외가 발생한다." + + "(최초 댓글의 답글이 삭제상태이고 그 답글에 답글을 작성하는 경우)") + void registerPickRepliedCommentRepliedDeleted() { + // given + // 익명회원 생성 + AnonymousMember anonymousMember = AnonymousMember.create("anonymousMemberId", "익명으로 개발하는 댑댑이"); + anonymousMemberRepository.save(anonymousMember); + + Authentication authentication = mock(Authentication.class); + when(authentication.getPrincipal()).thenReturn(AuthenticationMemberUtils.ANONYMOUS_USER); + + // 회원 생성 + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + // 픽픽픽 생성 + Pick pick = createPick(new Title("픽픽픽 타이틀"), ContentStatus.APPROVAL, new Count(0L), new Count(0L), + new Count(0L), new Count(0L), member); + pickRepository.save(pick); + + // 픽픽픽 댓글 생성 + PickComment pickComment = createPickComment(new CommentContents("댓글1"), false, member, pick); + pickCommentRepository.save(pickComment); + + // 삭제상태의 픽픽픽 댓글의 답글 생성 + PickComment repliedPickComment = createReplidPickComment(new CommentContents("댓글1의 답글"), member, pick, + pickComment, pickComment); + repliedPickComment.changeDeletedAtByMember(LocalDateTime.now(), member); + pickCommentRepository.save(repliedPickComment); + + RegisterPickRepliedCommentRequest request = new RegisterPickRepliedCommentRequest("댓글1의 답글1의 답글"); + PickCommentDto repliedCommentDto = PickCommentDto.createRepliedCommentDto(request, "anonymousMemberId"); + + // when // then + assertThatThrownBy( + () -> guestPickCommentServiceV2.registerPickRepliedComment( + repliedPickComment.getId(), pickComment.getId(), pick.getId(), repliedCommentDto, authentication)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(INVALID_CAN_NOT_REPLY_DELETED_PICK_COMMENT_MESSAGE, REGISTER); + } + + @Test + @DisplayName("익명회원이 픽픽픽 답글을 작성할 때 답글 대상의 댓글이 존재하지 않으면 예외가 발생한다." + + "(최초 댓글의 답글이 존재하지 않고 그 답글에 답글을 작성하는 경우)") + void registerPickRepliedCommentRepliedNotFoundException() { + // given + // 익명회원 생성 + AnonymousMember anonymousMember = AnonymousMember.create("anonymousMemberId", "익명으로 개발하는 댑댑이"); + anonymousMemberRepository.save(anonymousMember); + + Authentication authentication = mock(Authentication.class); + when(authentication.getPrincipal()).thenReturn(AuthenticationMemberUtils.ANONYMOUS_USER); + + // 회원 생성 + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + // 픽픽픽 생성 + Pick pick = createPick(new Title("픽픽픽 타이틀"), ContentStatus.APPROVAL, member); + pickRepository.save(pick); + + // 픽픽픽 댓글 생성 + PickComment pickComment = createPickComment(new CommentContents("댓글1"), false, member, pick); + pickCommentRepository.save(pickComment); + + // 삭제상태의 픽픽픽 댓글의 답글(삭제 상태) + PickComment repliedPickComment = createReplidPickComment(new CommentContents("댓글1의 답글"), member, pick, + pickComment, pickComment); + pickCommentRepository.save(repliedPickComment); + repliedPickComment.changeDeletedAtByMember(LocalDateTime.now(), member); + + RegisterPickRepliedCommentRequest request = new RegisterPickRepliedCommentRequest("댓글1의 답글1의 답글"); + PickCommentDto repliedCommentDto = PickCommentDto.createRepliedCommentDto(request, "anonymousMemberId"); + + // when // then + assertThatThrownBy( + () -> guestPickCommentServiceV2.registerPickRepliedComment( + 0L, pickComment.getId(), pick.getId(), repliedCommentDto, authentication)) + .isInstanceOf(NotFoundException.class) + .hasMessage(INVALID_NOT_FOUND_PICK_COMMENT_MESSAGE); + } } \ No newline at end of file diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickCommentServiceTest.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickCommentServiceTest.java index e73bdd7f..5b1f0c11 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickCommentServiceTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickCommentServiceTest.java @@ -441,10 +441,11 @@ void registerPickRepliedComment(Boolean isPublic) { em.clear(); RegisterPickRepliedCommentRequest request = new RegisterPickRepliedCommentRequest("댓글1의 답글1의 답글"); + PickCommentDto repliedCommentDto = PickCommentDto.createRepliedCommentDto(request, "anonymousMemberId"); // when PickCommentResponse response = memberPickCommentService.registerPickRepliedComment( - replidPickComment.getId(), pickComment.getId(), pick.getId(), request, authentication); + replidPickComment.getId(), pickComment.getId(), pick.getId(), repliedCommentDto, authentication); em.flush(); em.clear(); @@ -482,10 +483,11 @@ void registerPickRepliedCommentMemberException() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); RegisterPickRepliedCommentRequest request = new RegisterPickRepliedCommentRequest("댓글1의 답글1의 답글"); + PickCommentDto repliedCommentDto = PickCommentDto.createRepliedCommentDto(request, "anonymousMemberId"); // when // then assertThatThrownBy( - () -> memberPickCommentService.registerPickRepliedComment(0L, 0L, 0L, request, authentication)) + () -> memberPickCommentService.registerPickRepliedComment(0L, 0L, 0L, repliedCommentDto, authentication)) .isInstanceOf(MemberException.class) .hasMessage(INVALID_MEMBER_NOT_FOUND_MESSAGE); } @@ -510,10 +512,11 @@ void registerPickRepliedCommentNotFoundExceptionParent() { pickRepository.save(pick); RegisterPickRepliedCommentRequest request = new RegisterPickRepliedCommentRequest("댓글1의 답글1의 답글"); + PickCommentDto repliedCommentDto = PickCommentDto.createRepliedCommentDto(request, "anonymousMemberId"); // when // then assertThatThrownBy( - () -> memberPickCommentService.registerPickRepliedComment(0L, 0L, 0L, request, authentication)) + () -> memberPickCommentService.registerPickRepliedComment(0L, 0L, 0L, repliedCommentDto, authentication)) .isInstanceOf(NotFoundException.class) .hasMessage(INVALID_NOT_FOUND_PICK_COMMENT_MESSAGE); } @@ -543,11 +546,12 @@ void registerPickRepliedCommentPickIsNotApproval(ContentStatus contentStatus) { pickCommentRepository.save(pickComment); RegisterPickRepliedCommentRequest request = new RegisterPickRepliedCommentRequest("댓글1의 답글1의 답글"); + PickCommentDto repliedCommentDto = PickCommentDto.createRepliedCommentDto(request, "anonymousMemberId"); // when // then assertThatThrownBy( () -> memberPickCommentService.registerPickRepliedComment( - pickComment.getId(), pickComment.getId(), pick.getId(), request, authentication)) + pickComment.getId(), pickComment.getId(), pick.getId(), repliedCommentDto, authentication)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(INVALID_NOT_APPROVAL_STATUS_PICK_REPLY_MESSAGE, REGISTER); } @@ -579,11 +583,12 @@ void registerPickRepliedCommentDeleted() { pickCommentRepository.save(pickComment); RegisterPickRepliedCommentRequest request = new RegisterPickRepliedCommentRequest("댓글1의 답글1의 답글"); + PickCommentDto repliedCommentDto = PickCommentDto.createRepliedCommentDto(request, "anonymousMemberId"); // when // then assertThatThrownBy( () -> memberPickCommentService.registerPickRepliedComment( - pickComment.getId(), pickComment.getId(), pick.getId(), request, authentication)) + pickComment.getId(), pickComment.getId(), pick.getId(), repliedCommentDto, authentication)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(INVALID_CAN_NOT_REPLY_DELETED_PICK_COMMENT_MESSAGE, REGISTER); } @@ -620,11 +625,12 @@ void registerPickRepliedCommentRepliedDeleted() { pickCommentRepository.save(replidPickComment); RegisterPickRepliedCommentRequest request = new RegisterPickRepliedCommentRequest("댓글1의 답글1의 답글"); + PickCommentDto repliedCommentDto = PickCommentDto.createRepliedCommentDto(request, "anonymousMemberId"); // when // then assertThatThrownBy( () -> memberPickCommentService.registerPickRepliedComment( - replidPickComment.getId(), pickComment.getId(), pick.getId(), request, authentication)) + replidPickComment.getId(), pickComment.getId(), pick.getId(), repliedCommentDto, authentication)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(INVALID_CAN_NOT_REPLY_DELETED_PICK_COMMENT_MESSAGE, REGISTER); } @@ -653,16 +659,19 @@ void registerPickRepliedCommentRepliedNotFoundException() { PickComment pickComment = createPickComment(new CommentContents("댓글1"), false, member, pick); pickCommentRepository.save(pickComment); - // 삭제상태의 픽픽픽 댓글의 답글 + // 삭제상태의 픽픽픽 댓글의 답글(삭제 상태) PickComment replidPickComment = createReplidPickComment(new CommentContents("댓글1의 답글"), member, pick, pickComment, pickComment); + pickCommentRepository.save(replidPickComment); + replidPickComment.changeDeletedAtByMember(LocalDateTime.now(), member); RegisterPickRepliedCommentRequest request = new RegisterPickRepliedCommentRequest("댓글1의 답글1의 답글"); + PickCommentDto repliedCommentDto = PickCommentDto.createRepliedCommentDto(request, "anonymousMemberId"); // when // then assertThatThrownBy( () -> memberPickCommentService.registerPickRepliedComment( - 0L, pickComment.getId(), pick.getId(), request, authentication)) + 0L, pickComment.getId(), pick.getId(), repliedCommentDto, authentication)) .isInstanceOf(NotFoundException.class) .hasMessage(INVALID_NOT_FOUND_PICK_COMMENT_MESSAGE); } diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/PickTestUtils.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/PickTestUtils.java index e0f82ca7..1c6b7599 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/PickTestUtils.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/PickTestUtils.java @@ -124,6 +124,24 @@ public static PickComment createReplidPickComment(CommentContents contents, Memb return pickComment; } + public static PickComment createReplidPickComment(CommentContents contents, AnonymousMember anonymousMember, Pick pick, + PickComment originParent, PickComment parent) { + PickComment pickComment = PickComment.builder() + .contents(contents) + .createdAnonymousBy(anonymousMember) + .pick(pick) + .originParent(originParent) + .isPublic(false) + .parent(parent) + .recommendTotalCount(new Count(0)) + .replyTotalCount(new Count(0)) + .build(); + + pickComment.changePick(pick); + + return pickComment; + } + public static PickComment createPickComment(CommentContents contents, Boolean isPublic, Member member, Pick pick) { PickComment pickComment = PickComment.builder() .contents(contents) diff --git a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/PickCommentControllerDocsTest.java b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/PickCommentControllerDocsTest.java index 76c81242..f2f1264a 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/PickCommentControllerDocsTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/PickCommentControllerDocsTest.java @@ -61,7 +61,6 @@ import com.dreamypatisiel.devdevdev.domain.repository.pick.PickVoteRepository; import com.dreamypatisiel.devdevdev.global.constant.SecurityConstant; import com.dreamypatisiel.devdevdev.global.security.oauth2.model.SocialMemberDto; -import com.dreamypatisiel.devdevdev.web.WebConstant; import com.dreamypatisiel.devdevdev.web.dto.request.pick.ModifyPickCommentRequest; import com.dreamypatisiel.devdevdev.web.dto.request.pick.ModifyPickOptionRequest; import com.dreamypatisiel.devdevdev.web.dto.request.pick.ModifyPickRequest; @@ -239,7 +238,8 @@ void registerPickCommentBindExceptionPickVotePublicIsNull(Boolean isPickVotePubl preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( - headerWithName(AUTHORIZATION_HEADER).description("Bearer 엑세스 토큰") + headerWithName(AUTHORIZATION_HEADER).optional().description("Bearer 엑세스 토큰"), + headerWithName(HEADER_ANONYMOUS_MEMBER_ID).optional().description("익명회원 아이디") ), pathParameters( parameterWithName("pickId").description("픽픽픽 아이디") @@ -301,7 +301,8 @@ void registerPickRepliedComment() throws Exception { preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( - headerWithName(AUTHORIZATION_HEADER).description("Bearer 엑세스 토큰") + headerWithName(AUTHORIZATION_HEADER).optional().description("Bearer 엑세스 토큰"), + headerWithName(HEADER_ANONYMOUS_MEMBER_ID).optional().description("익명회원 아이디") ), pathParameters( parameterWithName("pickId").description("픽픽픽 아이디"), @@ -367,7 +368,8 @@ void registerPickRepliedCommentBindException(String contents) throws Exception { preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( - headerWithName(AUTHORIZATION_HEADER).description("Bearer 엑세스 토큰") + headerWithName(AUTHORIZATION_HEADER).optional().description("Bearer 엑세스 토큰"), + headerWithName(HEADER_ANONYMOUS_MEMBER_ID).optional().description("익명회원 아이디") ), pathParameters( parameterWithName("pickId").description("픽픽픽 아이디"),