From c27d664451334c2e98ade67861fc33966e2573d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:13:53 +0900 Subject: [PATCH 01/47] =?UTF-8?q?[refactor]=20=EA=B2=8C=EC=8B=9C=EB=AC=BC?= =?UTF-8?q?=20=EA=B3=B5=ED=86=B5=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20CountUpdatable=EB=A1=9C=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/application/service/CommentCreateService.java | 4 ++-- .../comment/application/service/CommentDeleteService.java | 4 ++-- .../comment/application/service/CommentLikeService.java | 4 ++-- .../application/service/policy/CommentAccessPolicy.java | 4 ++-- .../service/validator/CommentAuthorizationValidator.java | 8 ++++---- .../konkuk/thip/common/post/CommentCountUpdatable.java | 7 ------- src/main/java/konkuk/thip/common/post/CountUpdatable.java | 8 ++++++++ 7 files changed, 20 insertions(+), 19 deletions(-) delete mode 100644 src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java create mode 100644 src/main/java/konkuk/thip/common/post/CountUpdatable.java diff --git a/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java b/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java index f779766a7..fb349fe00 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java @@ -6,7 +6,7 @@ import konkuk.thip.comment.application.service.validator.CommentAuthorizationValidator; import konkuk.thip.comment.domain.Comment; import konkuk.thip.common.exception.InvalidStateException; -import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.CountUpdatable; import konkuk.thip.common.post.service.PostHandler; import konkuk.thip.common.post.PostType; import lombok.RequiredArgsConstructor; @@ -34,7 +34,7 @@ public Long createComment(CommentCreateCommand command) { PostType type = PostType.from(command.postType()); // 2. 게시물 타입에 맞게 조회 - CommentCountUpdatable post = postHandler.findPost(type, command.postId()); + CountUpdatable post = postHandler.findPost(type, command.postId()); // 2-1. 게시글 타입에 따른 댓글 생성 권한 검증 commentAuthorizationValidator.validateUserCanAccessPostForComment(type, post, command.userId()); diff --git a/src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java b/src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java index c85e522e7..1199ad850 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java @@ -6,7 +6,7 @@ import konkuk.thip.comment.application.port.out.CommentLikeCommandPort; import konkuk.thip.comment.application.service.validator.CommentAuthorizationValidator; import konkuk.thip.comment.domain.Comment; -import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.CountUpdatable; import konkuk.thip.common.post.service.PostHandler; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -28,7 +28,7 @@ public Long deleteComment(Long commentId, Long userId) { // 1. 댓글 조회 및 권한 검증 Comment comment = commentCommandPort.getByIdOrThrow(commentId); // 1-1. 게시글 타입에 따른 댓글 삭제 권한 검증 - CommentCountUpdatable post = postHandler.findPost(comment.getPostType(), comment.getTargetPostId()); + CountUpdatable post = postHandler.findPost(comment.getPostType(), comment.getTargetPostId()); commentAuthorizationValidator.validateUserCanAccessPostForComment(comment.getPostType(), post, userId); // 2. 댓글 삭제 권한 검증 및 소프트 딜리트 diff --git a/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java b/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java index cf601a84f..e497ab55f 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java @@ -9,7 +9,7 @@ import konkuk.thip.comment.application.port.out.CommentLikeQueryPort; import konkuk.thip.comment.application.service.validator.CommentAuthorizationValidator; import konkuk.thip.comment.domain.Comment; -import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.CountUpdatable; import konkuk.thip.common.post.service.PostHandler; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -32,7 +32,7 @@ public CommentIsLikeResult changeLikeStatusComment(CommentIsLikeCommand command) // 1. 댓글 조회 및 검증 (존재 여부) Comment comment = commentCommandPort.getByIdOrThrow(command.commentId()); // 1-1. 게시글 타입에 따른 댓글 좋아요 권한 검증 - CommentCountUpdatable post = postHandler.findPost(comment.getPostType(), comment.getTargetPostId()); + CountUpdatable post = postHandler.findPost(comment.getPostType(), comment.getTargetPostId()); commentAuthorizationValidator.validateUserCanAccessPostForComment(comment.getPostType(), post, command.userId()); // 2. 유저가 해당 댓글에 대해 좋아요 했는지 조회 diff --git a/src/main/java/konkuk/thip/comment/application/service/policy/CommentAccessPolicy.java b/src/main/java/konkuk/thip/comment/application/service/policy/CommentAccessPolicy.java index c9a5b8e96..0e91edd00 100644 --- a/src/main/java/konkuk/thip/comment/application/service/policy/CommentAccessPolicy.java +++ b/src/main/java/konkuk/thip/comment/application/service/policy/CommentAccessPolicy.java @@ -1,7 +1,7 @@ package konkuk.thip.comment.application.service.policy; -import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.CountUpdatable; public interface CommentAccessPolicy { - void validateCommentAccess(CommentCountUpdatable post, Long userId); + void validateCommentAccess(CountUpdatable post, Long userId); } diff --git a/src/main/java/konkuk/thip/comment/application/service/validator/CommentAuthorizationValidator.java b/src/main/java/konkuk/thip/comment/application/service/validator/CommentAuthorizationValidator.java index 6d6be7c9d..63fad7cb0 100644 --- a/src/main/java/konkuk/thip/comment/application/service/validator/CommentAuthorizationValidator.java +++ b/src/main/java/konkuk/thip/comment/application/service/validator/CommentAuthorizationValidator.java @@ -2,7 +2,7 @@ import konkuk.thip.comment.application.service.policy.CommentAccessPolicy; import konkuk.thip.common.exception.InvalidStateException; -import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.CountUpdatable; import konkuk.thip.common.post.PostType; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -15,14 +15,14 @@ @RequiredArgsConstructor public class CommentAuthorizationValidator { - private final Map policyMap; + private final Map commentAccessPolicyMap; - public void validateUserCanAccessPostForComment(PostType type, CommentCountUpdatable post, Long userId) { + public void validateUserCanAccessPostForComment(PostType type, CountUpdatable post, Long userId) { getPolicy(type).validateCommentAccess(post, userId); } private CommentAccessPolicy getPolicy(PostType type) { - CommentAccessPolicy policy = policyMap.get(type); + CommentAccessPolicy policy = commentAccessPolicyMap.get(type); if (policy == null) { throw new InvalidStateException(POST_TYPE_NOT_MATCH); } diff --git a/src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java b/src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java deleted file mode 100644 index accccc90e..000000000 --- a/src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java +++ /dev/null @@ -1,7 +0,0 @@ -package konkuk.thip.common.post; - -public interface CommentCountUpdatable { - void increaseCommentCount(); - void decreaseCommentCount(); - Long getId(); -} \ No newline at end of file diff --git a/src/main/java/konkuk/thip/common/post/CountUpdatable.java b/src/main/java/konkuk/thip/common/post/CountUpdatable.java new file mode 100644 index 000000000..c6ea2a645 --- /dev/null +++ b/src/main/java/konkuk/thip/common/post/CountUpdatable.java @@ -0,0 +1,8 @@ +package konkuk.thip.common.post; + +public interface CountUpdatable { //TODO 패키지 구조 충돌안나게 한번에 옮기기 + void increaseCommentCount(); + void decreaseCommentCount(); + void updateLikeCount(boolean like); + Long getId(); +} \ No newline at end of file From 51a57c8fbad5c589f6511a3366ef1d9f2773c80a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:14:03 +0900 Subject: [PATCH 02/47] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A0=A8=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/common/exception/code/ErrorCode.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java index bf9831087..4d6b735da 100644 --- a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java @@ -91,6 +91,7 @@ public enum ErrorCode implements ResponseCode { ROOM_MEMBER_COUNT_EXCEEDED(HttpStatus.BAD_REQUEST, 100006, "방의 최대 인원 수를 초과했습니다."), ROOM_MEMBER_COUNT_UNDERFLOW(HttpStatus.BAD_REQUEST, 100007, "방의 인원 수가 1 이하(방장 포함)입니다."), ROOM_IS_EXPIRED(HttpStatus.BAD_REQUEST, 100008, "방이 만료되었습니다."), + ROOM_POST_TYPE_NOT_MATCH(HttpStatus.BAD_REQUEST, 100009, "일치하는 방 게시물 타입 이름이 없습니다. [RECORD, VOTE] 중 하나여야 합니다."), /** * 110000 : vote error @@ -167,6 +168,14 @@ public enum ErrorCode implements ResponseCode { */ POST_TYPE_NOT_MATCH(HttpStatus.BAD_REQUEST, 180000, "일치하는 게시물 타입 이름이 없습니다. [FEED, RECORD, VOTE] 중 하나여야 합니다."), + /** + * 185000 : PostLike error + * + */ + POST_LIKE_COUNT_UNDERFLOW(HttpStatus.BAD_REQUEST, 185000, "좋아요 수는 0 이하로 감소할 수 없습니다."), + POST_ALREADY_LIKED(HttpStatus.BAD_REQUEST, 185001, "사용자가 이미 좋아요한 게시물입니다."), + POST_NOT_LIKED_CANNOT_CANCEL(HttpStatus.BAD_REQUEST, 185002, "사용자가 좋아요하지 않은 게시물은 좋아요 취소 할 수 없습니다."), + /** * 190000 : Comment error */ From 9756e36fffe5ecdd4b1246dd096cbbc05f1db606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:14:30 +0900 Subject: [PATCH 03/47] =?UTF-8?q?[feat]=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/konkuk/thip/feed/domain/Feed.java | 27 ++++++++++++++++--- .../konkuk/thip/record/domain/Record.java | 20 ++++++++++++-- .../java/konkuk/thip/vote/domain/Vote.java | 20 ++++++++++++-- 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/main/java/konkuk/thip/feed/domain/Feed.java b/src/main/java/konkuk/thip/feed/domain/Feed.java index 3c1533f10..468f92ca7 100644 --- a/src/main/java/konkuk/thip/feed/domain/Feed.java +++ b/src/main/java/konkuk/thip/feed/domain/Feed.java @@ -3,7 +3,7 @@ import konkuk.thip.common.entity.BaseDomainEntity; import konkuk.thip.common.exception.BusinessException; import konkuk.thip.common.exception.InvalidStateException; -import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.CountUpdatable; import lombok.Builder; import lombok.Getter; import lombok.experimental.SuperBuilder; @@ -18,7 +18,7 @@ @Getter @SuperBuilder -public class Feed extends BaseDomainEntity implements CommentCountUpdatable { +public class Feed extends BaseDomainEntity implements CountUpdatable { private Long id; @@ -110,11 +110,16 @@ public static void validateImageCount(int imageSize) { public void validateCreateComment(Long userId){ if (!this.isPublic && !this.creatorId.equals(userId)) { - validateCreator(userId); throw new InvalidStateException(FEED_ACCESS_FORBIDDEN, new IllegalArgumentException("비공개 글은 작성자만 댓글을 쓸 수 있습니다.")); } } + public void validateLike(Long userId){ + if (!this.isPublic && !this.creatorId.equals(userId)) { + throw new InvalidStateException(FEED_ACCESS_FORBIDDEN, new IllegalArgumentException("비공개 글은 작성자만 좋아요 할 수 있습니다.")); + } + } + private void validateCreator(Long userId) { if (!this.creatorId.equals(userId)) { throw new InvalidStateException(FEED_ACCESS_FORBIDDEN, new IllegalArgumentException("피드 작성자만 피드를 수정할 수 있습니다.")); @@ -168,12 +173,28 @@ public void decreaseCommentCount() { commentCount--; } + @Override + public void updateLikeCount(boolean like) { + if (like) { + likeCount++; + } else { + checkLikeCountNotUnderflow(); + likeCount--; + } + } + private void checkCommentCountNotUnderflow() { if (commentCount <= 0) { throw new InvalidStateException(COMMENT_COUNT_UNDERFLOW); } } + private void checkLikeCountNotUnderflow() { + if (likeCount <= 0) { + throw new InvalidStateException(POST_LIKE_COUNT_UNDERFLOW); + } + } + /** * 유저가 현재 피드를 조회할 수 있는지를 검증하는 메서드 */ diff --git a/src/main/java/konkuk/thip/record/domain/Record.java b/src/main/java/konkuk/thip/record/domain/Record.java index fde6543b7..de6656338 100644 --- a/src/main/java/konkuk/thip/record/domain/Record.java +++ b/src/main/java/konkuk/thip/record/domain/Record.java @@ -2,7 +2,7 @@ import konkuk.thip.common.entity.BaseDomainEntity; import konkuk.thip.common.exception.InvalidStateException; -import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.CountUpdatable; import konkuk.thip.room.domain.RoomPost; import lombok.Builder; import lombok.Getter; @@ -12,7 +12,7 @@ @Getter @SuperBuilder -public class Record extends BaseDomainEntity implements CommentCountUpdatable, RoomPost { +public class Record extends BaseDomainEntity implements CountUpdatable, RoomPost { private Long id; @@ -85,9 +85,25 @@ public void decreaseCommentCount() { commentCount--; } + @Override + public void updateLikeCount(boolean like) { + if (like) { + likeCount++; + } else { + checkLikeCountNotUnderflow(); + likeCount--; + } + } + private void checkCommentCountNotUnderflow() { if (commentCount <= 0) { throw new InvalidStateException(COMMENT_COUNT_UNDERFLOW); } } + + private void checkLikeCountNotUnderflow() { + if (likeCount <= 0) { + throw new InvalidStateException(POST_LIKE_COUNT_UNDERFLOW); + } + } } diff --git a/src/main/java/konkuk/thip/vote/domain/Vote.java b/src/main/java/konkuk/thip/vote/domain/Vote.java index fd56781a0..952014960 100644 --- a/src/main/java/konkuk/thip/vote/domain/Vote.java +++ b/src/main/java/konkuk/thip/vote/domain/Vote.java @@ -2,7 +2,7 @@ import konkuk.thip.common.entity.BaseDomainEntity; import konkuk.thip.common.exception.InvalidStateException; -import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.CountUpdatable; import konkuk.thip.room.domain.RoomPost; import lombok.Builder; import lombok.Getter; @@ -12,7 +12,7 @@ @Getter @SuperBuilder -public class Vote extends BaseDomainEntity implements CommentCountUpdatable, RoomPost { +public class Vote extends BaseDomainEntity implements CountUpdatable, RoomPost { private Long id; @@ -79,9 +79,25 @@ public void decreaseCommentCount() { commentCount--; } + @Override + public void updateLikeCount(boolean like) { + if (like) { + likeCount++; + } else { + checkLikeCountNotUnderflow(); + likeCount--; + } + } + private void checkCommentCountNotUnderflow() { if (commentCount <= 0) { throw new InvalidStateException(COMMENT_COUNT_UNDERFLOW); } } + + private void checkLikeCountNotUnderflow() { + if (likeCount <= 0) { + throw new InvalidStateException(POST_LIKE_COUNT_UNDERFLOW); + } + } } From 08305f9543b50b9aafc7123a64edfa3d9b28b3f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:14:41 +0900 Subject: [PATCH 04/47] =?UTF-8?q?[feat]=20=ED=94=BC=EB=93=9C=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=9E=91=EC=84=B1=20(#1?= =?UTF-8?q?59)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/FeedCommandController.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/konkuk/thip/feed/adapter/in/web/FeedCommandController.java b/src/main/java/konkuk/thip/feed/adapter/in/web/FeedCommandController.java index 70970ec72..0921d3ec2 100644 --- a/src/main/java/konkuk/thip/feed/adapter/in/web/FeedCommandController.java +++ b/src/main/java/konkuk/thip/feed/adapter/in/web/FeedCommandController.java @@ -8,13 +8,16 @@ import konkuk.thip.common.security.annotation.UserId; import konkuk.thip.common.swagger.annotation.ExceptionDescription; import konkuk.thip.feed.adapter.in.web.request.FeedCreateRequest; +import konkuk.thip.feed.adapter.in.web.request.FeedIsLikeRequest; import konkuk.thip.feed.adapter.in.web.request.FeedIsSavedRequest; import konkuk.thip.feed.adapter.in.web.request.FeedUpdateRequest; import konkuk.thip.feed.adapter.in.web.response.FeedIdResponse; +import konkuk.thip.feed.adapter.in.web.response.FeedIsLikeResponse; import konkuk.thip.feed.adapter.in.web.response.FeedIsSavedResponse; import konkuk.thip.feed.application.port.in.FeedCreateUseCase; import konkuk.thip.feed.application.port.in.FeedSavedUseCase; import konkuk.thip.feed.application.port.in.FeedUpdateUseCase; +import konkuk.thip.post.application.port.in.PostLikeUseCase; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -31,6 +34,7 @@ public class FeedCommandController { private final FeedCreateUseCase feedCreateUseCase; private final FeedUpdateUseCase feedUpdateUseCase; private final FeedSavedUseCase feedSavedUseCase; + private final PostLikeUseCase postLikeUseCase; @Operation( summary = "피드 작성", @@ -74,4 +78,17 @@ public BaseResponse changeSavedFeed( return BaseResponse.ok(FeedIsSavedResponse.of(feedSavedUseCase.changeSavedFeed(FeedIsSavedRequest.toCommand(userId,feedId,request.type())))); } + @Operation( + summary = "피드 좋아요 상태 변경", + description = "사용자가 피드의 좋아요 상태를 변경합니다. (true -> 좋아요, false -> 좋아요 취소)" + ) + @ExceptionDescription(CHANGE_FEED_LIKE_STATE) + @PostMapping("/feeds/{feedId}/likes") + public BaseResponse likeFeed( + @RequestBody @Valid final FeedIsLikeRequest request, + @Parameter(description = "좋아요 상태를 변경하려는 피드 ID", example = "1")@PathVariable("feedId") final Long feedId, + @Parameter(hidden = true) @UserId final Long userId) { + return BaseResponse.ok(FeedIsLikeResponse.of(postLikeUseCase.changeLikeStatusPost(request.toCommand(userId, feedId)))); + } + } From 52a6f6f433796bb7ee0a48f4ab9498e98d5fc007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:14:55 +0900 Subject: [PATCH 05/47] =?UTF-8?q?[feat]=20=ED=94=BC=EB=93=9C=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?request=20dto=20=EC=9E=91=EC=84=B1=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/request/FeedIsLikeRequest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/konkuk/thip/feed/adapter/in/web/request/FeedIsLikeRequest.java diff --git a/src/main/java/konkuk/thip/feed/adapter/in/web/request/FeedIsLikeRequest.java b/src/main/java/konkuk/thip/feed/adapter/in/web/request/FeedIsLikeRequest.java new file mode 100644 index 000000000..fdebfc734 --- /dev/null +++ b/src/main/java/konkuk/thip/feed/adapter/in/web/request/FeedIsLikeRequest.java @@ -0,0 +1,18 @@ +package konkuk.thip.feed.adapter.in.web.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import konkuk.thip.post.application.port.in.dto.PostIsLikeCommand; + +import static konkuk.thip.common.post.PostType.FEED; + +@Schema(description = "피드 좋아요 상태 변경 요청 DTO") +public record FeedIsLikeRequest( + @Schema(description = "좋아요 여부 type (true -> 좋아요, false -> 좋아요 취소)", example = "true") + @NotNull(message = "좋아요 여부는 필수입니다.") + Boolean type +) { + public PostIsLikeCommand toCommand(Long userId, Long feedId) { + return new PostIsLikeCommand(userId, feedId, FEED ,type); + } +} \ No newline at end of file From 633a964cfaa4265cc27ef4c10148552c7d75255d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:15:02 +0900 Subject: [PATCH 06/47] =?UTF-8?q?[feat]=20=ED=94=BC=EB=93=9C=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?reponse=20dto=20=EC=9E=91=EC=84=B1=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/response/FeedIsLikeResponse.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedIsLikeResponse.java diff --git a/src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedIsLikeResponse.java b/src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedIsLikeResponse.java new file mode 100644 index 000000000..6d5e58cc2 --- /dev/null +++ b/src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedIsLikeResponse.java @@ -0,0 +1,12 @@ +package konkuk.thip.feed.adapter.in.web.response; + +import konkuk.thip.post.application.port.in.dto.PostIsLikeResult; + +public record FeedIsLikeResponse( + Long feedId, + boolean isLiked +) { + public static FeedIsLikeResponse of(PostIsLikeResult postIsLikeResult) { + return new FeedIsLikeResponse(postIsLikeResult.postId(), postIsLikeResult.isLiked()); + } +} From e9877990b4373f9bc7025eacccfec58d66d22277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:15:49 +0900 Subject: [PATCH 07/47] =?UTF-8?q?[refactor]=20=EA=B2=8C=EC=8B=9C=EB=AC=BC?= =?UTF-8?q?=20=EA=B3=B5=ED=86=B5=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20CountUpdatable=EB=A1=9C=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/policy/FeedCommentAccessPolicy.java | 4 ++-- .../java/konkuk/thip/common/post/service/PostHandler.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/konkuk/thip/comment/application/service/policy/FeedCommentAccessPolicy.java b/src/main/java/konkuk/thip/comment/application/service/policy/FeedCommentAccessPolicy.java index c53badbd0..b553041fd 100644 --- a/src/main/java/konkuk/thip/comment/application/service/policy/FeedCommentAccessPolicy.java +++ b/src/main/java/konkuk/thip/comment/application/service/policy/FeedCommentAccessPolicy.java @@ -1,6 +1,6 @@ package konkuk.thip.comment.application.service.policy; -import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.CountUpdatable; import konkuk.thip.feed.domain.Feed; import org.springframework.stereotype.Component; @@ -8,7 +8,7 @@ public class FeedCommentAccessPolicy implements CommentAccessPolicy { @Override - public void validateCommentAccess(CommentCountUpdatable post, Long userId) { + public void validateCommentAccess(CountUpdatable post, Long userId) { Feed feed = (Feed) post; feed.validateCreateComment(userId); diff --git a/src/main/java/konkuk/thip/common/post/service/PostHandler.java b/src/main/java/konkuk/thip/common/post/service/PostHandler.java index 06428b418..7dce5f1f5 100644 --- a/src/main/java/konkuk/thip/common/post/service/PostHandler.java +++ b/src/main/java/konkuk/thip/common/post/service/PostHandler.java @@ -1,7 +1,7 @@ package konkuk.thip.common.post.service; import konkuk.thip.common.annotation.HelperService; -import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.CountUpdatable; import konkuk.thip.common.post.PostType; import konkuk.thip.feed.application.port.out.FeedCommandPort; import konkuk.thip.feed.domain.Feed; @@ -19,7 +19,7 @@ public class PostHandler { private final RecordCommandPort recordCommandPort; private final VoteCommandPort voteCommandPort; - public CommentCountUpdatable findPost(PostType type, Long postId) { + public CountUpdatable findPost(PostType type, Long postId) { return switch (type) { case FEED -> feedCommandPort.getByIdOrThrow(postId); case RECORD -> recordCommandPort.getByIdOrThrow(postId); @@ -27,7 +27,7 @@ public CommentCountUpdatable findPost(PostType type, Long postId) { }; } - public void updatePost(PostType type, CommentCountUpdatable post) { + public void updatePost(PostType type, CountUpdatable post) { switch (type) { case FEED -> feedCommandPort.update((Feed) post); case RECORD -> recordCommandPort.update((Record) post); From b2d746d5d3aee8e66912e011ba6a5ac184c6dcb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:16:16 +0900 Subject: [PATCH 08/47] =?UTF-8?q?[feat]=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9A=A9=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=B6=94=EA=B0=80=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java | 5 +++++ .../konkuk/thip/record/adapter/out/jpa/RecordJpaEntity.java | 6 ++++++ .../konkuk/thip/vote/adapter/out/jpa/VoteJpaEntity.java | 6 ++++++ 3 files changed, 17 insertions(+) diff --git a/src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java b/src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java index 648ec3868..0b3dffbc6 100644 --- a/src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java +++ b/src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java @@ -56,4 +56,9 @@ public void updateCommentCount(int commentCount) { this.commentCount = commentCount; } + @VisibleForTesting + public void updateLikeCount(int likeCount) { + this.likeCount = likeCount; + } + } \ No newline at end of file diff --git a/src/main/java/konkuk/thip/record/adapter/out/jpa/RecordJpaEntity.java b/src/main/java/konkuk/thip/record/adapter/out/jpa/RecordJpaEntity.java index 48ce26a0c..33f3998c3 100644 --- a/src/main/java/konkuk/thip/record/adapter/out/jpa/RecordJpaEntity.java +++ b/src/main/java/konkuk/thip/record/adapter/out/jpa/RecordJpaEntity.java @@ -1,5 +1,6 @@ package konkuk.thip.record.adapter.out.jpa; +import com.google.common.annotations.VisibleForTesting; import jakarta.persistence.*; import konkuk.thip.record.domain.Record; import konkuk.thip.post.adapter.out.jpa.PostJpaEntity; @@ -43,5 +44,10 @@ public RecordJpaEntity updateFrom(Record record) { return this; } + @VisibleForTesting + public void updateLikeCount(int likeCount) { + this.likeCount = likeCount; + } + } diff --git a/src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteJpaEntity.java b/src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteJpaEntity.java index 2d8a28378..1a3087978 100644 --- a/src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteJpaEntity.java +++ b/src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteJpaEntity.java @@ -1,5 +1,6 @@ package konkuk.thip.vote.adapter.out.jpa; +import com.google.common.annotations.VisibleForTesting; import jakarta.persistence.*; import konkuk.thip.post.adapter.out.jpa.PostJpaEntity; import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; @@ -42,4 +43,9 @@ public VoteJpaEntity updateFrom(Vote vote) { this.isOverview = vote.isOverview(); return this; } + + @VisibleForTesting + public void updateLikeCount(int likeCount) { + this.likeCount = likeCount; + } } \ No newline at end of file From c83cdf4ed780974a80b90490ebd4a442b9ae67d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:16:37 +0900 Subject: [PATCH 09/47] =?UTF-8?q?[feat]=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=A0=91=EA=B7=BC=20=EC=A0=95?= =?UTF-8?q?=EC=B1=85=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/policy/PostLikeAccessPolicy.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/konkuk/thip/post/application/service/policy/PostLikeAccessPolicy.java diff --git a/src/main/java/konkuk/thip/post/application/service/policy/PostLikeAccessPolicy.java b/src/main/java/konkuk/thip/post/application/service/policy/PostLikeAccessPolicy.java new file mode 100644 index 000000000..486e50683 --- /dev/null +++ b/src/main/java/konkuk/thip/post/application/service/policy/PostLikeAccessPolicy.java @@ -0,0 +1,7 @@ +package konkuk.thip.post.application.service.policy; + +import konkuk.thip.common.post.CountUpdatable; + +public interface PostLikeAccessPolicy { + void validatePostLikeAccess(CountUpdatable post, Long userId); +} From 142649f65dad5abcc24719a54d6214e5f6bcc8f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:16:45 +0900 Subject: [PATCH 10/47] =?UTF-8?q?[feat]=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=A0=91=EA=B7=BC=20=EC=A0=95?= =?UTF-8?q?=EC=B1=85=20config=20=EC=9E=91=EC=84=B1=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...yConfig.java => PostAccessPolicyConfig.java} | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) rename src/main/java/konkuk/thip/config/{CommentAccessPolicyConfig.java => PostAccessPolicyConfig.java} (58%) diff --git a/src/main/java/konkuk/thip/config/CommentAccessPolicyConfig.java b/src/main/java/konkuk/thip/config/PostAccessPolicyConfig.java similarity index 58% rename from src/main/java/konkuk/thip/config/CommentAccessPolicyConfig.java rename to src/main/java/konkuk/thip/config/PostAccessPolicyConfig.java index ddf53ceed..2b3dd13fe 100644 --- a/src/main/java/konkuk/thip/config/CommentAccessPolicyConfig.java +++ b/src/main/java/konkuk/thip/config/PostAccessPolicyConfig.java @@ -4,6 +4,9 @@ import konkuk.thip.comment.application.service.policy.FeedCommentAccessPolicy; import konkuk.thip.comment.application.service.policy.RoomPostCommentAccessPolicy; import konkuk.thip.common.post.PostType; +import konkuk.thip.post.application.service.policy.FeedLikeAccessPolicy; +import konkuk.thip.post.application.service.policy.PostLikeAccessPolicy; +import konkuk.thip.post.application.service.policy.RoomPostLikeAccessPolicy; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -12,11 +15,14 @@ @Configuration @RequiredArgsConstructor -public class CommentAccessPolicyConfig { +public class PostAccessPolicyConfig { private final FeedCommentAccessPolicy feedCommentPolicy; private final RoomPostCommentAccessPolicy roomCommentPolicy; + private final FeedLikeAccessPolicy feedLikePolicy; + private final RoomPostLikeAccessPolicy roomLikePolicy; + @Bean public Map commentAccessPolicyMap() { return Map.of( @@ -25,4 +31,13 @@ public Map commentAccessPolicyMap() { PostType.VOTE, roomCommentPolicy ); } + + @Bean + public Map roomPostAccessPolicyMap() { + return Map.of( + PostType.FEED, feedLikePolicy, + PostType.RECORD, roomLikePolicy, + PostType.VOTE, roomLikePolicy + ); + } } \ No newline at end of file From c66515a275727e5c9490af778705184e34c83d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:16:56 +0900 Subject: [PATCH 11/47] =?UTF-8?q?[feat]=20=ED=94=BC=EB=93=9C=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EC=A0=91=EA=B7=BC=20=EC=A0=95=EC=B1=85=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/policy/FeedLikeAccessPolicy.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/main/java/konkuk/thip/post/application/service/policy/FeedLikeAccessPolicy.java diff --git a/src/main/java/konkuk/thip/post/application/service/policy/FeedLikeAccessPolicy.java b/src/main/java/konkuk/thip/post/application/service/policy/FeedLikeAccessPolicy.java new file mode 100644 index 000000000..146d71f8c --- /dev/null +++ b/src/main/java/konkuk/thip/post/application/service/policy/FeedLikeAccessPolicy.java @@ -0,0 +1,15 @@ +package konkuk.thip.post.application.service.policy; + +import konkuk.thip.common.post.CountUpdatable; +import konkuk.thip.feed.domain.Feed; +import org.springframework.stereotype.Component; + +@Component +public class FeedLikeAccessPolicy implements PostLikeAccessPolicy { + + @Override + public void validatePostLikeAccess(CountUpdatable post, Long userId) { + Feed feed = (Feed) post; + feed.validateLike(userId); + } +} From 5e0456e432a604df26dd2128f2550a29a11a1535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:17:44 +0900 Subject: [PATCH 12/47] =?UTF-8?q?[feat]=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20command=20=EC=9E=91=EC=84=B1=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../port/in/dto/PostIsLikeCommand.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/java/konkuk/thip/post/application/port/in/dto/PostIsLikeCommand.java diff --git a/src/main/java/konkuk/thip/post/application/port/in/dto/PostIsLikeCommand.java b/src/main/java/konkuk/thip/post/application/port/in/dto/PostIsLikeCommand.java new file mode 100644 index 000000000..100f5a290 --- /dev/null +++ b/src/main/java/konkuk/thip/post/application/port/in/dto/PostIsLikeCommand.java @@ -0,0 +1,16 @@ +package konkuk.thip.post.application.port.in.dto; + +import konkuk.thip.common.post.PostType; + +public record PostIsLikeCommand( + + Long userId, + + Long postId, + + PostType postType, + + boolean isLike +) +{ +} From 6e6f0d273c566100cf51fb5ca8d029c4620a0bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:17:51 +0900 Subject: [PATCH 13/47] =?UTF-8?q?[feat]=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20PostIsLikeResult=20=EC=9E=91=EC=84=B1=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/port/in/dto/PostIsLikeResult.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/konkuk/thip/post/application/port/in/dto/PostIsLikeResult.java diff --git a/src/main/java/konkuk/thip/post/application/port/in/dto/PostIsLikeResult.java b/src/main/java/konkuk/thip/post/application/port/in/dto/PostIsLikeResult.java new file mode 100644 index 000000000..4e1e1c0bd --- /dev/null +++ b/src/main/java/konkuk/thip/post/application/port/in/dto/PostIsLikeResult.java @@ -0,0 +1,11 @@ +package konkuk.thip.post.application.port.in.dto; + +public record PostIsLikeResult( + Long postId, + boolean isLiked +) +{ + public static PostIsLikeResult of(Long postId, boolean isLiked) { + return new PostIsLikeResult(postId, isLiked); + } +} \ No newline at end of file From 455d85a29b62add4d2ec087859ca27fc121367a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:18:17 +0900 Subject: [PATCH 14/47] =?UTF-8?q?[feat]=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=A0=91=EA=B7=BC=20=EC=A0=95?= =?UTF-8?q?=EC=B1=85=20PostLikeAuthorizationValidator=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PostLikeAuthorizationValidator.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/main/java/konkuk/thip/post/application/service/validator/PostLikeAuthorizationValidator.java diff --git a/src/main/java/konkuk/thip/post/application/service/validator/PostLikeAuthorizationValidator.java b/src/main/java/konkuk/thip/post/application/service/validator/PostLikeAuthorizationValidator.java new file mode 100644 index 000000000..f3e76a4a5 --- /dev/null +++ b/src/main/java/konkuk/thip/post/application/service/validator/PostLikeAuthorizationValidator.java @@ -0,0 +1,43 @@ +package konkuk.thip.post.application.service.validator; + +import konkuk.thip.common.exception.InvalidStateException; +import konkuk.thip.common.post.CountUpdatable; +import konkuk.thip.common.post.PostType; +import konkuk.thip.post.application.service.policy.PostLikeAccessPolicy; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static konkuk.thip.common.exception.code.ErrorCode.*; + +@Component +@RequiredArgsConstructor +public class PostLikeAuthorizationValidator { + + private final Map likeAccessPolicyMap; + + public void validateUserCanAccessPostLike(PostType type, CountUpdatable post, Long userId) { + getPolicy(type).validatePostLikeAccess(post, userId); + } + + public void validateUserCanLike(boolean alreadyLiked) { + if (alreadyLiked) { + throw new InvalidStateException(POST_ALREADY_LIKED); + } + } + + public void validateUserCanUnLike(boolean alreadyLiked) { + if (!alreadyLiked) { + throw new InvalidStateException(POST_NOT_LIKED_CANNOT_CANCEL); + } + } + + private PostLikeAccessPolicy getPolicy(PostType type) { + PostLikeAccessPolicy policy = likeAccessPolicyMap.get(type); + if (policy == null) { + throw new InvalidStateException(POST_TYPE_NOT_MATCH); + } + return policy; + } +} From 6c9b7dcfda330b8d33cea0d94ca14b8917be820b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:18:35 +0900 Subject: [PATCH 15/47] =?UTF-8?q?[feat]=20PostLikeCommandPersistenceAdapte?= =?UTF-8?q?r=20=EA=B4=80=EB=A0=A8=20=ED=95=A8=EC=88=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PostLikeCommandPersistenceAdapter.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeCommandPersistenceAdapter.java b/src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeCommandPersistenceAdapter.java index 8030e20f2..2b17182f4 100644 --- a/src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeCommandPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeCommandPersistenceAdapter.java @@ -1,12 +1,58 @@ package konkuk.thip.post.adapter.out.persistence; +import konkuk.thip.common.exception.EntityNotFoundException; +import konkuk.thip.common.post.PostType; +import konkuk.thip.feed.adapter.out.persistence.repository.FeedJpaRepository; +import konkuk.thip.post.adapter.out.jpa.PostJpaEntity; +import konkuk.thip.post.adapter.out.mapper.PostLikeMapper; import konkuk.thip.post.application.port.out.PostLikeCommandPort; +import konkuk.thip.record.adapter.out.persistence.repository.RecordJpaRepository; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; +import konkuk.thip.vote.adapter.out.persistence.repository.VoteJpaRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import static konkuk.thip.common.exception.code.ErrorCode.*; +import static konkuk.thip.common.exception.code.ErrorCode.RECORD_NOT_FOUND; +import static konkuk.thip.common.exception.code.ErrorCode.VOTE_NOT_FOUND; + @Repository @RequiredArgsConstructor public class PostLikeCommandPersistenceAdapter implements PostLikeCommandPort { + private final PostLikeJpaRepository postLikeJpaRepository; + private final FeedJpaRepository feedJpaRepository; + private final RecordJpaRepository recordJpaRepository; + private final VoteJpaRepository voteJpaRepository; + private final UserJpaRepository userJpaRepository; + private final PostLikeMapper postLikeMapper; + + @Override + public void save(Long userId, Long postId, PostType postType) { + + UserJpaEntity userJpaEntity = userJpaRepository.findById(userId) + .orElseThrow(() -> new EntityNotFoundException(USER_NOT_FOUND)); + + PostJpaEntity postJpaEntity = findPostJpaEntity(postType, postId); + + postLikeJpaRepository.save(postLikeMapper.toJpaEntity(postJpaEntity, userJpaEntity)); + } + + @Override + public void delete(Long userId, Long postId) { + postLikeJpaRepository.deleteByUserIdAndPostId(userId, postId); + } + + private PostJpaEntity findPostJpaEntity(PostType postType, Long postId) { + return switch (postType) { + case FEED -> feedJpaRepository.findById(postId) + .orElseThrow(() -> new EntityNotFoundException(FEED_NOT_FOUND)); + case RECORD -> recordJpaRepository.findById(postId) + .orElseThrow(() -> new EntityNotFoundException(RECORD_NOT_FOUND)); + case VOTE -> voteJpaRepository.findById(postId) + .orElseThrow(() -> new EntityNotFoundException(VOTE_NOT_FOUND)); + }; + } } From f539ee37df39c28b043eb110fb9b4e3ebd3386bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:18:43 +0900 Subject: [PATCH 16/47] =?UTF-8?q?[feat]=20PostLikeCommandPort=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/post/application/port/out/PostLikeCommandPort.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/post/application/port/out/PostLikeCommandPort.java b/src/main/java/konkuk/thip/post/application/port/out/PostLikeCommandPort.java index 69f57e238..1aebce592 100644 --- a/src/main/java/konkuk/thip/post/application/port/out/PostLikeCommandPort.java +++ b/src/main/java/konkuk/thip/post/application/port/out/PostLikeCommandPort.java @@ -1,5 +1,8 @@ package konkuk.thip.post.application.port.out; -public interface PostLikeCommandPort { +import konkuk.thip.common.post.PostType; +public interface PostLikeCommandPort { + void save(Long userId, Long postId, PostType postType); + void delete(Long userId, Long postId); } From 0440180da45b3161666bd05738741a489e56ca8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:18:50 +0900 Subject: [PATCH 17/47] =?UTF-8?q?[feat]=20PostLikeJpaRepository=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80(#1?= =?UTF-8?q?59)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/out/persistence/PostLikeJpaRepository.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeJpaRepository.java b/src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeJpaRepository.java index e341b17a9..ce052cb6e 100644 --- a/src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeJpaRepository.java +++ b/src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeJpaRepository.java @@ -2,6 +2,7 @@ import konkuk.thip.post.adapter.out.jpa.PostLikeJpaEntity; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -14,4 +15,13 @@ public interface PostLikeJpaRepository extends JpaRepository findPostIdsLikedByUser(@Param("postIds") Set postIds, @Param("userId") Long userId); + + @Query("SELECT CASE WHEN COUNT(pl) > 0 THEN true ELSE false END " + + "FROM PostLikeJpaEntity pl " + + "WHERE pl.userJpaEntity.userId = :userId AND pl.postJpaEntity.postId = :postId") + boolean existsByUserIdAndPostId(@Param("userId") Long userId, @Param("postId") Long postId); + + @Modifying + @Query("DELETE FROM PostLikeJpaEntity pl WHERE pl.userJpaEntity.userId = :userId AND pl.postJpaEntity.postId = :postId") + void deleteByUserIdAndPostId(@Param("userId") Long userId, @Param("postId") Long postId); } From 879df310ad4a77b627b2816b72e274a41711dae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:19:10 +0900 Subject: [PATCH 18/47] =?UTF-8?q?[feat]=20PostLikeQueryPersistenceAdapter.?= =?UTF-8?q?isLikedPostByUser=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80(#15?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../out/persistence/PostLikeQueryPersistenceAdapter.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeQueryPersistenceAdapter.java b/src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeQueryPersistenceAdapter.java index 54051b3f2..cd3975869 100644 --- a/src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeQueryPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeQueryPersistenceAdapter.java @@ -16,4 +16,9 @@ public class PostLikeQueryPersistenceAdapter implements PostLikeQueryPort { public Set findPostIdsLikedByUser(Set postIds, Long userId) { return postLikeJpaRepository.findPostIdsLikedByUser(postIds, userId); } + + @Override + public boolean isLikedPostByUser(Long userId, Long postId) { + return postLikeJpaRepository.existsByUserIdAndPostId(userId, postId); + } } From b8a54baf0b489aa218f91741732ee8318522e455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:20:02 +0900 Subject: [PATCH 19/47] =?UTF-8?q?[feat]=20PostLikeQueryPort.isLikedPostByU?= =?UTF-8?q?ser=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/post/application/port/out/PostLikeQueryPort.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/konkuk/thip/post/application/port/out/PostLikeQueryPort.java b/src/main/java/konkuk/thip/post/application/port/out/PostLikeQueryPort.java index 32ad51287..9c1c8a281 100644 --- a/src/main/java/konkuk/thip/post/application/port/out/PostLikeQueryPort.java +++ b/src/main/java/konkuk/thip/post/application/port/out/PostLikeQueryPort.java @@ -5,4 +5,6 @@ public interface PostLikeQueryPort { Set findPostIdsLikedByUser(Set postIds, Long userId); + + boolean isLikedPostByUser(Long userId, Long postId); } From 0d812fd7b14665f0aa01ee251e512f931e4c2720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:20:25 +0900 Subject: [PATCH 20/47] =?UTF-8?q?[feat]=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20PostLikeUseCase=20=EC=9C=A0=EC=A6=88=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=9E=91=EC=84=B1=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/post/application/port/in/PostLikeUseCase.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/konkuk/thip/post/application/port/in/PostLikeUseCase.java diff --git a/src/main/java/konkuk/thip/post/application/port/in/PostLikeUseCase.java b/src/main/java/konkuk/thip/post/application/port/in/PostLikeUseCase.java new file mode 100644 index 000000000..303eace5e --- /dev/null +++ b/src/main/java/konkuk/thip/post/application/port/in/PostLikeUseCase.java @@ -0,0 +1,8 @@ +package konkuk.thip.post.application.port.in; + +import konkuk.thip.post.application.port.in.dto.PostIsLikeCommand; +import konkuk.thip.post.application.port.in.dto.PostIsLikeResult; + +public interface PostLikeUseCase { + PostIsLikeResult changeLikeStatusPost(PostIsLikeCommand PostIsLikeCommand); +} \ No newline at end of file From 7854ac97ce38e388e207e3ef7c96b6bd898fd92c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:20:33 +0900 Subject: [PATCH 21/47] =?UTF-8?q?[feat]=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EC=9C=A0=EC=A6=88=EC=BC=80=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=EC=B2=B4=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/PostLikeService.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/main/java/konkuk/thip/post/application/service/PostLikeService.java diff --git a/src/main/java/konkuk/thip/post/application/service/PostLikeService.java b/src/main/java/konkuk/thip/post/application/service/PostLikeService.java new file mode 100644 index 000000000..9a3042cc8 --- /dev/null +++ b/src/main/java/konkuk/thip/post/application/service/PostLikeService.java @@ -0,0 +1,53 @@ +package konkuk.thip.post.application.service; + +import jakarta.transaction.Transactional; +import konkuk.thip.common.post.CountUpdatable; +import konkuk.thip.common.post.service.PostHandler; +import konkuk.thip.post.application.port.in.dto.PostIsLikeCommand; +import konkuk.thip.post.application.port.in.dto.PostIsLikeResult; +import konkuk.thip.post.application.port.in.PostLikeUseCase; +import konkuk.thip.post.application.port.out.PostLikeCommandPort; +import konkuk.thip.post.application.port.out.PostLikeQueryPort; +import konkuk.thip.post.application.service.validator.PostLikeAuthorizationValidator; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PostLikeService implements PostLikeUseCase { + + private final PostLikeQueryPort postLikeQueryPort; + private final PostLikeCommandPort postLikeCommandPort; + + private final PostHandler postHandler; + private final PostLikeAuthorizationValidator postLikeAuthorizationValidator; + + @Override + @Transactional + public PostIsLikeResult changeLikeStatusPost(PostIsLikeCommand command) { + + // 1. 게시물 타입에 맞게 검증 및 조회 + CountUpdatable post = postHandler.findPost(command.postType(), command.postId()); + // 1-1. 게시글 타입에 따른 게시물 좋아요 권한 검증 + postLikeAuthorizationValidator.validateUserCanAccessPostLike(command.postType(), post, command.userId()); + + // 2. 유저가 해당 게시물에 대해 좋아요 했는지 조회 + boolean alreadyLiked = postLikeQueryPort.isLikedPostByUser(command.userId(), command.postId()); + + // 3. 좋아요 상태변경 + //TODO 게시물의 좋아요 수 증가/감소 동시성 제어 로직 추가해야됨 + if (command.isLike()) { + postLikeAuthorizationValidator.validateUserCanLike(alreadyLiked); // 좋아요 가능 여부 검증 + postLikeCommandPort.save(command.userId(), command.postId(),command.postType()); + } else { + postLikeAuthorizationValidator.validateUserCanUnLike(alreadyLiked); // 좋아요 취소 가능 여부 검증 + postLikeCommandPort.delete(command.userId(), command.postId()); + } + + // 4. 게시물 좋아요 수 업데이트 + post.updateLikeCount(command.isLike()); + postHandler.updatePost(command.postType(), post); + + return PostIsLikeResult.of(post.getId(), command.isLike()); + } +} From 626d4e110b55af21509f7dc6e40aeb68983a0b3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:21:10 +0900 Subject: [PATCH 22/47] =?UTF-8?q?[refactor]=20=EC=95=88=EC=93=B0=EB=8A=94?= =?UTF-8?q?=20=ED=95=A8=EC=88=98=20=EC=82=AD=EC=A0=9C=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/common/post/PostType.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/main/java/konkuk/thip/common/post/PostType.java b/src/main/java/konkuk/thip/common/post/PostType.java index 10875289e..b17f35e27 100644 --- a/src/main/java/konkuk/thip/common/post/PostType.java +++ b/src/main/java/konkuk/thip/common/post/PostType.java @@ -26,16 +26,4 @@ public static PostType from(String type) { new InvalidStateException(POST_TYPE_NOT_MATCH) ); } - - public boolean isFeed() { - return this == FEED; - } - - public boolean isRecord() { - return this == RECORD; - } - - public boolean isVote() { - return this == VOTE; - } } From ffd4a2011f2a05fd5cc108a6d37cb915ac637f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:21:39 +0900 Subject: [PATCH 23/47] =?UTF-8?q?[feat]=20=EB=B0=A9=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#163)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/RoomCommandController.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/konkuk/thip/room/adapter/in/web/RoomCommandController.java b/src/main/java/konkuk/thip/room/adapter/in/web/RoomCommandController.java index 58bb09c8f..aabec5e3a 100644 --- a/src/main/java/konkuk/thip/room/adapter/in/web/RoomCommandController.java +++ b/src/main/java/konkuk/thip/room/adapter/in/web/RoomCommandController.java @@ -7,8 +7,11 @@ import konkuk.thip.common.dto.BaseResponse; import konkuk.thip.common.security.annotation.UserId; import konkuk.thip.common.swagger.annotation.ExceptionDescription; +import konkuk.thip.post.application.port.in.PostLikeUseCase; import konkuk.thip.room.adapter.in.web.request.RoomCreateRequest; import konkuk.thip.room.adapter.in.web.request.RoomJoinRequest; +import konkuk.thip.room.adapter.in.web.request.RoomPostIsLikeRequest; +import konkuk.thip.room.adapter.in.web.response.RoomPostIsLikeResponse; import konkuk.thip.room.adapter.in.web.response.RoomRecruitCloseResponse; import konkuk.thip.room.adapter.in.web.response.RoomJoinResponse; import konkuk.thip.room.adapter.in.web.response.RoomCreateResponse; @@ -31,6 +34,7 @@ public class RoomCommandController { private final RoomCreateUseCase roomCreateUseCase; private final RoomJoinUseCase roomJoinUsecase; private final RoomRecruitCloseUseCase roomRecruitCloseUsecase; + private final PostLikeUseCase postLikeUseCase; /** * 방 생성 요청 @@ -85,4 +89,17 @@ public BaseResponse closeRoomRecruit( RoomRecruitCloseResponse.of(roomRecruitCloseUsecase.closeRoomRecruit(userId, roomId)) ); } + + @Operation( + summary = "방 게시물(기록,투표) 좋아요 상태 변경", + description = "사용자가 방 게시물의 좋아요 상태를 변경합니다. (true -> 좋아요, false -> 좋아요 취소)" + ) + @ExceptionDescription(CHANGE_ROOM_LIKE_STATE) + @PostMapping("/room-posts/{postId}/likes") + public BaseResponse likeRoomPost( + @RequestBody @Valid final RoomPostIsLikeRequest request, + @Parameter(description = "좋아요 상태를 변경하려는 방 게시물 ID", example = "1")@PathVariable("postId") final Long postId, + @Parameter(hidden = true) @UserId final Long userId) { + return BaseResponse.ok(RoomPostIsLikeResponse.of(postLikeUseCase.changeLikeStatusPost(request.toCommand(userId, postId)))); + } } From 2af4c79573895845bc554506afaa75bda254f01d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:21:50 +0900 Subject: [PATCH 24/47] =?UTF-8?q?[refactor]=20=EA=B2=8C=EC=8B=9C=EB=AC=BC?= =?UTF-8?q?=20=EA=B3=B5=ED=86=B5=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20CountUpdatable=EB=A1=9C=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/room/domain/RoomPost.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/konkuk/thip/room/domain/RoomPost.java b/src/main/java/konkuk/thip/room/domain/RoomPost.java index 59124104d..f5631e5e3 100644 --- a/src/main/java/konkuk/thip/room/domain/RoomPost.java +++ b/src/main/java/konkuk/thip/room/domain/RoomPost.java @@ -1,7 +1,7 @@ package konkuk.thip.room.domain; -import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.CountUpdatable; -public interface RoomPost extends CommentCountUpdatable { +public interface RoomPost extends CountUpdatable { Long getRoomId(); } \ No newline at end of file From 36234fe70d14c2661f7ab1c758a3e30c6732d0e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:21:58 +0900 Subject: [PATCH 25/47] =?UTF-8?q?[refactor]=20=EA=B2=8C=EC=8B=9C=EB=AC=BC?= =?UTF-8?q?=20=EA=B3=B5=ED=86=B5=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20CountUpdatable=EB=A1=9C=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/policy/RoomPostCommentAccessPolicy.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java b/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java index 125c9b7e6..f8a93b11b 100644 --- a/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java +++ b/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java @@ -1,6 +1,6 @@ package konkuk.thip.comment.application.service.policy; -import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.CountUpdatable; import konkuk.thip.room.application.service.validator.RoomParticipantValidator; import konkuk.thip.room.domain.RoomPost; import lombok.RequiredArgsConstructor; @@ -13,7 +13,7 @@ public class RoomPostCommentAccessPolicy implements CommentAccessPolicy { private final RoomParticipantValidator roomParticipantValidator; @Override - public void validateCommentAccess(CommentCountUpdatable post, Long userId) { + public void validateCommentAccess(CountUpdatable post, Long userId) { RoomPost roomPost = (RoomPost) post; roomParticipantValidator.validateUserIsRoomMember(roomPost.getRoomId(), userId); } From 369a6d46d62b1b7905823b5d24a5542f6b3329f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:22:13 +0900 Subject: [PATCH 26/47] =?UTF-8?q?[feat]=20=EB=B0=A9=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EB=AC=BC=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20request=20dto=20=EC=9E=91=EC=84=B1=20(#159?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/request/RoomPostIsLikeRequest.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/java/konkuk/thip/room/adapter/in/web/request/RoomPostIsLikeRequest.java diff --git a/src/main/java/konkuk/thip/room/adapter/in/web/request/RoomPostIsLikeRequest.java b/src/main/java/konkuk/thip/room/adapter/in/web/request/RoomPostIsLikeRequest.java new file mode 100644 index 000000000..8d329bddd --- /dev/null +++ b/src/main/java/konkuk/thip/room/adapter/in/web/request/RoomPostIsLikeRequest.java @@ -0,0 +1,22 @@ +package konkuk.thip.room.adapter.in.web.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import konkuk.thip.post.application.port.in.dto.PostIsLikeCommand; +import konkuk.thip.room.domain.RoomPostType; + +@Schema(description = "방기록 좋아요 상태 변경 요청 DTO") +public record RoomPostIsLikeRequest( + @Schema(description = "좋아요 여부 type (true -> 좋아요, false -> 좋아요 취소)", example = "true") + @NotNull(message = "좋아요 여부는 필수입니다.") + Boolean type, + + @Schema(description = "게시물 타입 (RECORD, VOTE)", example = "RECORD") + @NotBlank(message = "게시물 타입은 필수입니다.") + String roomPostType +) { + public PostIsLikeCommand toCommand(Long userId, Long feedId) { + return new PostIsLikeCommand(userId, feedId, RoomPostType.from(roomPostType).toPostType(), type); + } +} \ No newline at end of file From b12f92aab81049c9804521e0ec7abeb3de487fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:22:21 +0900 Subject: [PATCH 27/47] =?UTF-8?q?[feat]=20=EB=B0=A9=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EB=AC=BC=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20response=20dto=20=EC=9E=91=EC=84=B1=20(#15?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/response/RoomPostIsLikeResponse.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/java/konkuk/thip/room/adapter/in/web/response/RoomPostIsLikeResponse.java diff --git a/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomPostIsLikeResponse.java b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomPostIsLikeResponse.java new file mode 100644 index 000000000..da6592c90 --- /dev/null +++ b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomPostIsLikeResponse.java @@ -0,0 +1,12 @@ +package konkuk.thip.room.adapter.in.web.response; + +import konkuk.thip.post.application.port.in.dto.PostIsLikeResult; + +public record RoomPostIsLikeResponse( + Long postId, + boolean isLiked +) { + public static RoomPostIsLikeResponse of(PostIsLikeResult postIsLikeResult) { + return new RoomPostIsLikeResponse(postIsLikeResult.postId(), postIsLikeResult.isLiked()); + } +} \ No newline at end of file From 951fa12a85605b3e59249348facc965d78d9b065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:22:40 +0900 Subject: [PATCH 28/47] =?UTF-8?q?[feat]=20=EB=B0=A9=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EB=AC=BC=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=A0=91=EA=B7=BC=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=20=EC=9E=91=EC=84=B1=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../policy/RoomPostLikeAccessPolicy.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/java/konkuk/thip/post/application/service/policy/RoomPostLikeAccessPolicy.java diff --git a/src/main/java/konkuk/thip/post/application/service/policy/RoomPostLikeAccessPolicy.java b/src/main/java/konkuk/thip/post/application/service/policy/RoomPostLikeAccessPolicy.java new file mode 100644 index 000000000..fa9f9f51c --- /dev/null +++ b/src/main/java/konkuk/thip/post/application/service/policy/RoomPostLikeAccessPolicy.java @@ -0,0 +1,20 @@ +package konkuk.thip.post.application.service.policy; + +import konkuk.thip.common.post.CountUpdatable; +import konkuk.thip.room.application.service.validator.RoomParticipantValidator; +import konkuk.thip.room.domain.RoomPost; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class RoomPostLikeAccessPolicy implements PostLikeAccessPolicy { + + private final RoomParticipantValidator roomParticipantValidator; + + @Override + public void validatePostLikeAccess(CountUpdatable post, Long userId) { + RoomPost roomPost = (RoomPost) post; + roomParticipantValidator.validateUserIsRoomMember(roomPost.getRoomId(), userId); + } +} \ No newline at end of file From d2a74a261e285d3540179825fb30679ca25d1528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:22:47 +0900 Subject: [PATCH 29/47] =?UTF-8?q?[feat]=20=EB=B0=A9=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EB=AC=BC=ED=83=80=EC=9E=85=20enum=20=EC=9E=91=EC=84=B1=20(#159?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/room/domain/RoomPostType.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/main/java/konkuk/thip/room/domain/RoomPostType.java diff --git a/src/main/java/konkuk/thip/room/domain/RoomPostType.java b/src/main/java/konkuk/thip/room/domain/RoomPostType.java new file mode 100644 index 000000000..f2d8a3aee --- /dev/null +++ b/src/main/java/konkuk/thip/room/domain/RoomPostType.java @@ -0,0 +1,33 @@ +package konkuk.thip.room.domain; + +import konkuk.thip.common.exception.InvalidStateException; +import konkuk.thip.common.post.PostType; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +import static konkuk.thip.common.exception.code.ErrorCode.ROOM_POST_TYPE_NOT_MATCH; + +@Getter +@RequiredArgsConstructor +public enum RoomPostType { + + RECORD("RECORD"), + VOTE("VOTE"); + + private final String type; + + public static RoomPostType from(String type) { + return Arrays.stream(RoomPostType.values()) + .filter(p -> p.getType().equalsIgnoreCase(type)) + .findFirst() + .orElseThrow(() -> + new InvalidStateException(ROOM_POST_TYPE_NOT_MATCH) + ); + } + + public PostType toPostType() { + return PostType.from(this.type); + } +} \ No newline at end of file From 1083e7e630e07e33aaf11844e9886cffecfeff30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:23:04 +0900 Subject: [PATCH 30/47] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A0=A8=20api=20?= =?UTF-8?q?=EC=8A=A4=EC=9B=A8=EA=B1=B0=20=EC=97=90=EB=9F=AC=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../swagger/SwaggerResponseDescription.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java b/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java index 54be1162f..2b3d6ec96 100644 --- a/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java +++ b/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java @@ -104,6 +104,16 @@ public enum SwaggerResponseDescription { ROOM_GET_DEADLINE_POPULAR(new LinkedHashSet<>(Set.of( CATEGORY_NOT_MATCH ))), + CHANGE_ROOM_LIKE_STATE(new LinkedHashSet<>(Set.of( + USER_NOT_FOUND, + RECORD_NOT_FOUND, + VOTE_NOT_FOUND, + POST_ALREADY_LIKED, + POST_NOT_LIKED_CANNOT_CANCEL, + POST_LIKE_COUNT_UNDERFLOW, + ROOM_ACCESS_FORBIDDEN, + ROOM_POST_TYPE_NOT_MATCH + ))), // Record @@ -174,6 +184,14 @@ public enum SwaggerResponseDescription { BOOK_NOT_FOUND, FEED_CAN_NOT_SHOW_PRIVATE_ONE ))), + CHANGE_FEED_LIKE_STATE(new LinkedHashSet<>(Set.of( + USER_NOT_FOUND, + FEED_NOT_FOUND, + POST_ALREADY_LIKED, + POST_NOT_LIKED_CANNOT_CANCEL, + POST_LIKE_COUNT_UNDERFLOW, + FEED_ACCESS_FORBIDDEN + ))), // Comment COMMENT_CREATE(new LinkedHashSet<>(Set.of( From 97f7faca78dc6543978933d24127ff3add08c844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:23:26 +0900 Subject: [PATCH 31/47] =?UTF-8?q?[test]=20=ED=94=BC=EB=93=9C=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/FeedChangeLikeStatusAPITest.java | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 src/test/java/konkuk/thip/feed/adapter/in/web/FeedChangeLikeStatusAPITest.java diff --git a/src/test/java/konkuk/thip/feed/adapter/in/web/FeedChangeLikeStatusAPITest.java b/src/test/java/konkuk/thip/feed/adapter/in/web/FeedChangeLikeStatusAPITest.java new file mode 100644 index 000000000..7f51c0202 --- /dev/null +++ b/src/test/java/konkuk/thip/feed/adapter/in/web/FeedChangeLikeStatusAPITest.java @@ -0,0 +1,154 @@ +package konkuk.thip.feed.adapter.in.web; + +import com.fasterxml.jackson.databind.ObjectMapper; +import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; +import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; +import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.feed.adapter.in.web.request.FeedIsLikeRequest; +import konkuk.thip.feed.adapter.out.jpa.FeedJpaEntity; +import konkuk.thip.feed.adapter.out.persistence.repository.FeedJpaRepository; +import konkuk.thip.post.adapter.out.persistence.PostLikeJpaRepository; +import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; +import konkuk.thip.user.adapter.out.persistence.repository.alias.AliasJpaRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import static konkuk.thip.common.exception.code.ErrorCode.POST_ALREADY_LIKED; +import static konkuk.thip.common.exception.code.ErrorCode.POST_NOT_LIKED_CANNOT_CANCEL; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + + +@SpringBootTest +@ActiveProfiles("test") +@AutoConfigureMockMvc(addFilters = false) +@Transactional +@DisplayName("[통합] 피드 좋아요 api 통합 테스트") +class FeedChangeLikeStatusAPITest { + + @Autowired + private MockMvc mockMvc; + + @Autowired private ObjectMapper objectMapper; + @Autowired private AliasJpaRepository aliasJpaRepository; + @Autowired private UserJpaRepository userJpaRepository; + @Autowired private BookJpaRepository bookJpaRepository; + @Autowired private FeedJpaRepository feedJpaRepository; + @Autowired private PostLikeJpaRepository postLikeJpaRepository; + + private UserJpaEntity user; + private BookJpaEntity book; + private FeedJpaEntity feed; + + private static final String FEED_LIKE_API_PATH = "/feeds/{feedId}/likes"; + + @BeforeEach + void setUp() { + AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createLiteratureAlias()); + user = userJpaRepository.save(TestEntityFactory.createUser(alias)); + book = bookJpaRepository.save(TestEntityFactory.createBookWithISBN("9788954682152")); + feed = feedJpaRepository.save(TestEntityFactory.createFeed(user,book, true)); + } + + @Test + @DisplayName("피드를 처음 좋아요하면 좋아요 저장 및 카운트 증가 [성공]") + void likeFeed_Success() throws Exception { + + // given + FeedIsLikeRequest request = new FeedIsLikeRequest(true); // 좋아요 상태 변경 요청(true=좋아요) + + // when + mockMvc.perform(post(FEED_LIKE_API_PATH, feed.getPostId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.feedId").value(feed.getPostId())) + .andExpect(jsonPath("$.data.isLiked").value(true)); + + // 좋아요 저장 여부 확인 + boolean liked = postLikeJpaRepository.existsByUserIdAndPostId(user.getUserId(),feed.getPostId()); + assertThat(liked).isTrue(); + + // 좋아요 카운트 증가 확인 + FeedJpaEntity updatedFeed = feedJpaRepository.findById(feed.getPostId()).orElseThrow(); + assertThat(updatedFeed.getLikeCount()).isEqualTo(1); + } + + @Test + @DisplayName("이미 좋아요한 피드를 다시 좋아요하면 [400 에러 발생]") + void likeFeed_AlreadyLiked_Fail() throws Exception { + + // given: 미리 좋아요 저장 + postLikeJpaRepository.save(TestEntityFactory.createPostLike(user, feed)); + FeedIsLikeRequest request = new FeedIsLikeRequest(true); + + // when & then + mockMvc.perform(post(FEED_LIKE_API_PATH, feed.getPostId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(POST_ALREADY_LIKED.getCode())); + } + + @Test + @DisplayName("좋아요 한 피드를 취소하면 좋아요 삭제 및 카운트 감소 [성공]") + void unlikeFeed_Success() throws Exception { + + // given: 좋아요가 저장되어 있고, likeCount도 1 반영 + postLikeJpaRepository.save(TestEntityFactory.createPostLike(user, feed)); + feed.updateLikeCount(1); // 좋아요 1개로 세팅 + feedJpaRepository.save(feed); + + FeedIsLikeRequest request = new FeedIsLikeRequest(false); + + // when + mockMvc.perform(post(FEED_LIKE_API_PATH, feed.getPostId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.feedId").value(feed.getPostId())) + .andExpect(jsonPath("$.data.isLiked").value(false)); + + // 좋아요 삭제 확인 + boolean liked = postLikeJpaRepository.existsByUserIdAndPostId(user.getUserId(),feed.getPostId()); + assertThat(liked).isFalse(); + + // 좋아요 카운트 감소 확인 + FeedJpaEntity updatedFeed = feedJpaRepository.findById(feed.getPostId()).orElseThrow(); + assertThat(updatedFeed.getLikeCount()).isEqualTo(0); + } + + @Test + @DisplayName("좋아요 하지 않은 피드를 좋아요 취소하면 [400 에러 발생]") + void unlikeFeed_NotLiked_Fail() throws Exception { + // given: 좋아요 없음 + FeedIsLikeRequest request = new FeedIsLikeRequest(false); + + // when & then + mockMvc.perform(post(FEED_LIKE_API_PATH, feed.getPostId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(POST_NOT_LIKED_CANNOT_CANCEL.getCode())); + } + +} From ece397c81e19a8fdc64935345adaf0d9fd0f59f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:23:35 +0900 Subject: [PATCH 32/47] =?UTF-8?q?[test]=20=ED=94=BC=EB=93=9C=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=94=BC=EB=93=9C=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/feed/domain/FeedTest.java | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/src/test/java/konkuk/thip/feed/domain/FeedTest.java b/src/test/java/konkuk/thip/feed/domain/FeedTest.java index a4b3d39f1..aee96483d 100644 --- a/src/test/java/konkuk/thip/feed/domain/FeedTest.java +++ b/src/test/java/konkuk/thip/feed/domain/FeedTest.java @@ -110,7 +110,7 @@ void validateCreateComment_nonCreatorOnPrivateFeed_throws() { () -> feed.validateCreateComment(OTHER_USER_ID)); assertEquals(FEED_ACCESS_FORBIDDEN, ex.getErrorCode()); - assertFalse(ex.getCause().getMessage().contains("비공개 글은 작성자만")); + assertTrue(ex.getCause().getMessage().contains("비공개 글은 작성자만")); } @Test @@ -267,4 +267,75 @@ private Feed makeFeedWithPublicStatus(Boolean isPublic) { .contentList(Collections.emptyList()) .build(); } + + @Test + @DisplayName("updateLikeCount: like == true 면 likeCount 가 1씩 증가한다.") + void updateLikeCount_likeTrue_increments() { + Feed feed = createPublicFeed(); + + feed.updateLikeCount(true); + assertEquals(1, feed.getLikeCount()); + + feed.updateLikeCount(true); + assertEquals(2, feed.getLikeCount()); + } + + @Test + @DisplayName("updateLikeCount: like == false 면 likeCount 가 1씩 감소한다.") + void updateLikeCount_likeFalse_decrements() { + Feed feed = createPublicFeed(); + // 먼저 likeCount 증가 셋업 + feed.updateLikeCount(true); + feed.updateLikeCount(true); + assertEquals(2, feed.getLikeCount()); + + feed.updateLikeCount(false); + assertEquals(1, feed.getLikeCount()); + + feed.updateLikeCount(false); + assertEquals(0, feed.getLikeCount()); + } + + @Test + @DisplayName("updateLikeCount: like == false 면 likeCount 가 0 이하로 내려가면 InvalidStateException이 발생한다.") + void updateLikeCount_likeFalse_underflow_throws() { + Feed feed = createPublicFeed(); + assertEquals(0, feed.getLikeCount()); + + InvalidStateException ex = assertThrows(InvalidStateException.class, () -> { + feed.updateLikeCount(false); + }); + + assertEquals(POST_LIKE_COUNT_UNDERFLOW, ex.getErrorCode()); + } + + @Test + @DisplayName("validateLike: 공개 피드는 누구나 좋아요 할 수 있다") + void validateLike_publicFeed_anyUser_passes() { + Feed feed = createPublicFeed(); + + assertDoesNotThrow(() -> feed.validateLike(OTHER_USER_ID)); + assertDoesNotThrow(() -> feed.validateLike(CREATOR_ID)); + } + + @Test + @DisplayName("validateLike: 비공개 피드이고 작성자가 아닌 경우 좋아요 시도하면 InvalidStateException이 발생한다.") + void validateLike_privateFeed_nonCreator_throws() { + Feed feed = createPrivateFeed(); + + InvalidStateException ex = assertThrows(InvalidStateException.class, + () -> feed.validateLike(OTHER_USER_ID)); + + assertEquals(FEED_ACCESS_FORBIDDEN, ex.getErrorCode()); + assertTrue(ex.getCause().getMessage().contains("비공개 글은 작성자만 좋아요 할 수 있습니다.")); + } + + @Test + @DisplayName("validateLike: 비공개 피드이고 작성자인 경우 좋아요 할 수 있다") + void validateLike_privateFeed_creator_passes() { + Feed feed = createPrivateFeed(); + + assertDoesNotThrow(() -> feed.validateLike(CREATOR_ID)); + } + } From a55267effbcfb6f66a04ef21f41ed9bafd33fcd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:23:45 +0900 Subject: [PATCH 33/47] =?UTF-8?q?[test]=20=EA=B4=80=EB=A0=A8=20=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/konkuk/thip/common/util/TestEntityFactory.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/java/konkuk/thip/common/util/TestEntityFactory.java b/src/test/java/konkuk/thip/common/util/TestEntityFactory.java index dd9fc44fe..509dcc937 100644 --- a/src/test/java/konkuk/thip/common/util/TestEntityFactory.java +++ b/src/test/java/konkuk/thip/common/util/TestEntityFactory.java @@ -9,6 +9,7 @@ import konkuk.thip.feed.adapter.out.jpa.FeedTagJpaEntity; import konkuk.thip.post.adapter.out.jpa.PostJpaEntity; import konkuk.thip.feed.adapter.out.jpa.TagJpaEntity; +import konkuk.thip.post.adapter.out.jpa.PostLikeJpaEntity; import konkuk.thip.record.adapter.out.jpa.RecordJpaEntity; import konkuk.thip.room.adapter.out.jpa.CategoryJpaEntity; import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; @@ -313,4 +314,11 @@ public static VoteParticipantJpaEntity createVoteParticipant(UserJpaEntity user, .voteItemJpaEntity(item) .build(); } + + public static PostLikeJpaEntity createPostLike(UserJpaEntity user, PostJpaEntity post) { + return PostLikeJpaEntity.builder() + .userJpaEntity(user) + .postJpaEntity(post) + .build(); + } } \ No newline at end of file From c49896bfe0473a90a416a778055e1653f558e8e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:24:09 +0900 Subject: [PATCH 34/47] =?UTF-8?q?[test]=20=EB=B0=A9=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EB=AC=BC=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#163)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/RoomPostChangeLikeStatusAPITest.java | 254 ++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 src/test/java/konkuk/thip/room/adapter/in/web/RoomPostChangeLikeStatusAPITest.java diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomPostChangeLikeStatusAPITest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomPostChangeLikeStatusAPITest.java new file mode 100644 index 000000000..c9d920af3 --- /dev/null +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomPostChangeLikeStatusAPITest.java @@ -0,0 +1,254 @@ +package konkuk.thip.room.adapter.in.web; + +import com.fasterxml.jackson.databind.ObjectMapper; +import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; +import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; +import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.feed.adapter.out.jpa.FeedJpaEntity; +import konkuk.thip.feed.adapter.out.persistence.repository.FeedJpaRepository; +import konkuk.thip.post.adapter.out.persistence.PostLikeJpaRepository; +import konkuk.thip.record.adapter.out.jpa.RecordJpaEntity; +import konkuk.thip.record.adapter.out.persistence.repository.RecordJpaRepository; +import konkuk.thip.room.adapter.in.web.request.RoomPostIsLikeRequest; +import konkuk.thip.room.adapter.out.jpa.CategoryJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomParticipantRole; +import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; +import konkuk.thip.room.adapter.out.persistence.repository.category.CategoryJpaRepository; +import konkuk.thip.room.adapter.out.persistence.repository.roomparticipant.RoomParticipantJpaRepository; +import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; +import konkuk.thip.user.adapter.out.persistence.repository.alias.AliasJpaRepository; +import konkuk.thip.vote.adapter.out.jpa.VoteJpaEntity; +import konkuk.thip.vote.adapter.out.persistence.repository.VoteJpaRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import static konkuk.thip.common.exception.code.ErrorCode.POST_ALREADY_LIKED; +import static konkuk.thip.common.exception.code.ErrorCode.POST_NOT_LIKED_CANNOT_CANCEL; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@SpringBootTest +@ActiveProfiles("test") +@AutoConfigureMockMvc(addFilters = false) +@Transactional +@DisplayName("[통합] 방 게시물(기록,투표) 좋아요 api 통합 테스트") +class RoomPostChangeLikeStatusAPITest { + + @Autowired + private MockMvc mockMvc; + + @Autowired private ObjectMapper objectMapper; + @Autowired private AliasJpaRepository aliasJpaRepository; + @Autowired private UserJpaRepository userJpaRepository; + @Autowired private BookJpaRepository bookJpaRepository; + @Autowired private FeedJpaRepository feedJpaRepository; + @Autowired private PostLikeJpaRepository postLikeJpaRepository; + @Autowired private CategoryJpaRepository categoryJpaRepository; + @Autowired private RoomJpaRepository roomJpaRepository; + @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; + @Autowired private RecordJpaRepository recordJpaRepository; + @Autowired private VoteJpaRepository voteJpaRepository; + + + private UserJpaEntity user; + private BookJpaEntity book; + private FeedJpaEntity feed; + private CategoryJpaEntity category; + private RoomJpaEntity room; + private RecordJpaEntity record; + private VoteJpaEntity vote; + + private static final String ROOM_POST_LIKE_API_PATH = "/room-posts/{postId}/likes"; + + @BeforeEach + void setUp() { + AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createLiteratureAlias()); + user = userJpaRepository.save(TestEntityFactory.createUser(alias)); + book = bookJpaRepository.save(TestEntityFactory.createBookWithISBN("9788954682152")); + feed = feedJpaRepository.save(TestEntityFactory.createFeed(user,book, true)); + category = categoryJpaRepository.save(TestEntityFactory.createLiteratureCategory(alias)); + room = roomJpaRepository.save(TestEntityFactory.createRoom(book, category)); + // 1번방에 유저 1이 호스트 + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room,user, RoomParticipantRole.HOST, 80.0)); + record = recordJpaRepository.save(TestEntityFactory.createRecord(user,room)); + vote = voteJpaRepository.save(TestEntityFactory.createVote(user,room)); + } + + @Test + @DisplayName("기록 게시물을 처음 좋아요하면 좋아요 저장 및 카운트 증가 [성공]") + void likeRecordPost_Success() throws Exception { + // given + RoomPostIsLikeRequest request = new RoomPostIsLikeRequest(true, "RECORD"); + + //when + mockMvc.perform(post(ROOM_POST_LIKE_API_PATH, record.getPostId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.postId").value(record.getPostId())) + .andExpect(jsonPath("$.data.isLiked").value(true)); + + //then + // 좋아요 저장 확인 + boolean liked = postLikeJpaRepository.existsByUserIdAndPostId(user.getUserId(), record.getPostId()); + assertThat(liked).isTrue(); + + // 좋아요 카운트 증가 확인 + RecordJpaEntity updatedRecord = recordJpaRepository.findById(record.getPostId()).orElseThrow(); + assertThat(updatedRecord.getLikeCount()).isEqualTo(1); + } + + + @Test + @DisplayName("이미 좋아요한 기록 게시물을 다시 좋아요하면 [400 에러 발생]") + void likeRecordPost_AlreadyLiked_Fail() throws Exception { + //given + postLikeJpaRepository.save(TestEntityFactory.createPostLike(user, record)); + RoomPostIsLikeRequest request = new RoomPostIsLikeRequest(true, "RECORD"); + + //when & then + mockMvc.perform(post(ROOM_POST_LIKE_API_PATH, record.getPostId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(POST_ALREADY_LIKED.getCode())); + } + + @Test + @DisplayName("좋아요한 기록 게시물 좋아요 취소하면 좋아요 삭제 및 카운트 감소 [성공]") + void unlikeRecordPost_Success() throws Exception { + //given + postLikeJpaRepository.save(TestEntityFactory.createPostLike(user, record)); + record.updateLikeCount(1); + recordJpaRepository.save(record); + RoomPostIsLikeRequest request = new RoomPostIsLikeRequest(false, "RECORD"); + + //when + mockMvc.perform(post(ROOM_POST_LIKE_API_PATH, record.getPostId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.postId").value(record.getPostId())) + .andExpect(jsonPath("$.data.isLiked").value(false)); + + //then + boolean liked = postLikeJpaRepository.existsByUserIdAndPostId(user.getUserId(), record.getPostId()); + assertThat(liked).isFalse(); + + RecordJpaEntity updatedRecord = recordJpaRepository.findById(record.getPostId()).orElseThrow(); + assertThat(updatedRecord.getLikeCount()).isEqualTo(0); + } + + @Test + @DisplayName("좋아요 하지 않은 기록 게시물을 좋아요 취소하면 [400 에러 발생]") + void unlikeRecordPost_NotLiked_Fail() throws Exception { + //given + RoomPostIsLikeRequest request = new RoomPostIsLikeRequest(false, "RECORD"); + + //when & then + mockMvc.perform(post(ROOM_POST_LIKE_API_PATH, record.getPostId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(POST_NOT_LIKED_CANNOT_CANCEL.getCode())); + } + + // --- Vote 게시물에 대해서도 동일 패턴 테스트 --- + + @Test + @DisplayName("투표 게시물을 처음 좋아요하면 좋아요 저장 및 카운트 증가 [성공]") + void likeVotePost_Success() throws Exception { + //given + RoomPostIsLikeRequest request = new RoomPostIsLikeRequest(true, "VOTE"); + + //when + mockMvc.perform(post(ROOM_POST_LIKE_API_PATH, vote.getPostId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.postId").value(vote.getPostId())) + .andExpect(jsonPath("$.data.isLiked").value(true)); + + //then + boolean liked = postLikeJpaRepository.existsByUserIdAndPostId(user.getUserId(), vote.getPostId()); + assertThat(liked).isTrue(); + + VoteJpaEntity updatedVote = voteJpaRepository.findById(vote.getPostId()).orElseThrow(); + assertThat(updatedVote.getLikeCount()).isEqualTo(1); + } + + @Test + @DisplayName("이미 좋아요한 투표 게시물을 다시 좋아요하면 [400 에러 발생]") + void likeVotePost_AlreadyLiked_Fail() throws Exception { + //given + postLikeJpaRepository.save(TestEntityFactory.createPostLike(user, vote)); + RoomPostIsLikeRequest request = new RoomPostIsLikeRequest(true, "VOTE"); + + //when & then + mockMvc.perform(post(ROOM_POST_LIKE_API_PATH, vote.getPostId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(POST_ALREADY_LIKED.getCode())); + } + + @Test + @DisplayName("좋아요한 투표 게시물 좋아요 취소하면 좋아요 삭제 및 카운트 감소 [성공]") + void unlikeVotePost_Success() throws Exception { + //given + postLikeJpaRepository.save(TestEntityFactory.createPostLike(user, vote)); + vote.updateLikeCount(1); + voteJpaRepository.save(vote); + RoomPostIsLikeRequest request = new RoomPostIsLikeRequest(false, "VOTE"); + + //when + mockMvc.perform(post(ROOM_POST_LIKE_API_PATH, vote.getPostId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.postId").value(vote.getPostId())) + .andExpect(jsonPath("$.data.isLiked").value(false)); + + boolean liked = postLikeJpaRepository.existsByUserIdAndPostId(user.getUserId(), vote.getPostId()); + assertThat(liked).isFalse(); + + VoteJpaEntity updatedVote = voteJpaRepository.findById(vote.getPostId()).orElseThrow(); + assertThat(updatedVote.getLikeCount()).isEqualTo(0); + } + + @Test + @DisplayName("좋아요 하지 않은 투표 게시물을 좋아요 취소하면 [400 에러 발생]") + void unlikeVotePost_NotLiked_Fail() throws Exception { + //given + RoomPostIsLikeRequest request = new RoomPostIsLikeRequest(false, "VOTE"); + + //when & then + mockMvc.perform(post(ROOM_POST_LIKE_API_PATH, vote.getPostId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(POST_NOT_LIKED_CANNOT_CANCEL.getCode())); + } +} From d66129e34afc5e0a15ff74283e6179cc833a5aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:24:38 +0900 Subject: [PATCH 35/47] =?UTF-8?q?[test]=20=EB=B0=A9=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EB=AC=BC=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EA=B4=80=EB=A0=A8=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#163)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...oomPostChangeLikeStatusControllerTest.java | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 src/test/java/konkuk/thip/room/adapter/in/web/RoomPostChangeLikeStatusControllerTest.java diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomPostChangeLikeStatusControllerTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomPostChangeLikeStatusControllerTest.java new file mode 100644 index 000000000..c336e4a31 --- /dev/null +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomPostChangeLikeStatusControllerTest.java @@ -0,0 +1,122 @@ +package konkuk.thip.room.adapter.in.web; + +import com.fasterxml.jackson.databind.ObjectMapper; +import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; +import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; +import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.record.adapter.out.jpa.RecordJpaEntity; +import konkuk.thip.record.adapter.out.persistence.repository.RecordJpaRepository; +import konkuk.thip.room.adapter.out.jpa.CategoryJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomParticipantRole; +import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; +import konkuk.thip.room.adapter.out.persistence.repository.category.CategoryJpaRepository; +import konkuk.thip.room.adapter.out.persistence.repository.roomparticipant.RoomParticipantJpaRepository; +import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; +import konkuk.thip.user.adapter.out.persistence.repository.alias.AliasJpaRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashMap; +import java.util.Map; + +import static konkuk.thip.common.exception.code.ErrorCode.*; +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@SpringBootTest +@ActiveProfiles("test") +@AutoConfigureMockMvc(addFilters = false) +@Transactional +@DisplayName("[단위] 방 게시물(기록,투표) 좋아요 api controller 단위 테스트") +class RoomPostChangeLikeStatusControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired private ObjectMapper objectMapper; + @Autowired private AliasJpaRepository aliasJpaRepository; + @Autowired private UserJpaRepository userJpaRepository; + @Autowired private BookJpaRepository bookJpaRepository; + @Autowired private CategoryJpaRepository categoryJpaRepository; + @Autowired private RoomJpaRepository roomJpaRepository; + @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; + @Autowired private RecordJpaRepository recordJpaRepository; + + private UserJpaEntity user1; + private UserJpaEntity user2; + private BookJpaEntity book; + private CategoryJpaEntity category; + private RoomJpaEntity room; + private RecordJpaEntity record; + + private static final String ROOM_POST_LIKE_API_PATH = "/room-posts/{postId}/likes"; + + @BeforeEach + void setUp() { + AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createLiteratureAlias()); + user1 = userJpaRepository.save(TestEntityFactory.createUser(alias)); + user2 = userJpaRepository.save(TestEntityFactory.createUser(alias)); + book = bookJpaRepository.save(TestEntityFactory.createBookWithISBN("9788954682152")); + category = categoryJpaRepository.save(TestEntityFactory.createLiteratureCategory(alias)); + room = roomJpaRepository.save(TestEntityFactory.createRoom(book, category)); + // 1번방에 유저 1이 호스트 + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room,user1, RoomParticipantRole.HOST, 80.0)); + record = recordJpaRepository.save(TestEntityFactory.createRecord(user1,room)); + } + + private Map buildValidLikeRequest(Boolean isLike, String postType) { + Map request = new HashMap<>(); + request.put("type", isLike); + request.put("roomPostType", postType); + return request; + } + + private void assertBadRequest(int expectedCode, Map request, String message) throws Exception { + mockMvc.perform(post(ROOM_POST_LIKE_API_PATH, record.getPostId()) + .requestAttr("userId", user1.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsBytes(request)) + ) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(expectedCode)) + .andExpect(jsonPath("$.message", containsString(message))); + } + + @Test + @DisplayName("잘못된 RoomPostType 값이 들어오면 400 Bad Request 반환") + void invalidPostType_shouldReturnBadRequest() throws Exception { + Map req = buildValidLikeRequest(true,"FEED"); + assertBadRequest(ROOM_POST_TYPE_NOT_MATCH.getCode(), req, "일치하는 방 게시물 타입 이름이 없습니다."); + } + + @Test + @DisplayName("방 참여자가 아닌 사용자가 방 게시물에 좋아요 하려고 하면 400 Bad Request 반환") + void nonParticipantUser_likeRoomPost_shouldReturnBadRequest() throws Exception { + Map req = buildValidLikeRequest(true, "RECORD"); + + mockMvc.perform(post(ROOM_POST_LIKE_API_PATH, record.getPostId()) + .requestAttr("userId", user2.getUserId()) // 방 참여하지 않은 user2로 요청 + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsBytes(req)) + ) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(ROOM_ACCESS_FORBIDDEN.getCode())) + .andExpect(jsonPath("$.message", containsString("사용자가 이 방의 참가자가 아닙니다."))); + } + + +} From cd7881b94f4eb0d9b2c9b88343d8f74fc948ddd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:24:49 +0900 Subject: [PATCH 36/47] =?UTF-8?q?[test]=20=EB=B0=A9=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EB=AC=BC=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EA=B4=80=EB=A0=A8=20=EB=A0=88=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=8B=A8=EC=9C=84=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#163)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/record/domain/RecordTest.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/test/java/konkuk/thip/record/domain/RecordTest.java b/src/test/java/konkuk/thip/record/domain/RecordTest.java index 1a5485c4a..e5a337395 100644 --- a/src/test/java/konkuk/thip/record/domain/RecordTest.java +++ b/src/test/java/konkuk/thip/record/domain/RecordTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import static konkuk.thip.common.exception.code.ErrorCode.COMMENT_COUNT_UNDERFLOW; +import static konkuk.thip.common.exception.code.ErrorCode.POST_LIKE_COUNT_UNDERFLOW; import static org.junit.jupiter.api.Assertions.*; @DisplayName("[단위] Record 도메인 테스트") @@ -124,5 +125,46 @@ void decreaseCommentCount_belowZero_throws() { assertEquals(COMMENT_COUNT_UNDERFLOW, ex.getErrorCode()); } + @Test + @DisplayName("updateLikeCount: like == true 면 likeCount 가 1씩 증가한다.") + void updateLikeCount_likeTrue_increments() { + Record record = createWithCommentRecord(); + + record.updateLikeCount(true); + assertEquals(1, record.getLikeCount()); + + record.updateLikeCount(true); + assertEquals(2, record.getLikeCount()); + } + + @Test + @DisplayName("updateLikeCount: like == false 면 likeCount 가 1씩 감소한다.") + void updateLikeCount_likeFalse_decrements() { + Record record = createWithCommentRecord(); + + // 먼저 likeCount 증가 셋업 + record.updateLikeCount(true); + record.updateLikeCount(true); + assertEquals(2, record.getLikeCount()); + + record.updateLikeCount(false); + assertEquals(1, record.getLikeCount()); + + record.updateLikeCount(false); + assertEquals(0, record.getLikeCount()); + } + + @Test + @DisplayName("updateLikeCount: like == false 면 likeCount 가 0 이하로 내려가면 InvalidStateException이 발생한다.") + void updateLikeCount_likeFalse_underflow_throws() { + Record record = createWithCommentRecord(); + assertEquals(0, record.getLikeCount()); + + InvalidStateException ex = assertThrows(InvalidStateException.class, () -> { + record.updateLikeCount(false); + }); + + assertEquals(POST_LIKE_COUNT_UNDERFLOW, ex.getErrorCode()); + } } \ No newline at end of file From 7d1959eef873372ec51c8708229472c3824ad978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:24:58 +0900 Subject: [PATCH 37/47] =?UTF-8?q?[test]=20=EB=B0=A9=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EB=AC=BC=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EA=B4=80=EB=A0=A8=20=ED=88=AC=ED=91=9C=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=8B=A8=EC=9C=84=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20?= =?UTF-8?q?(#163)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/vote/domain/VoteTest.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/test/java/konkuk/thip/vote/domain/VoteTest.java b/src/test/java/konkuk/thip/vote/domain/VoteTest.java index 95b871931..ecd7b42e7 100644 --- a/src/test/java/konkuk/thip/vote/domain/VoteTest.java +++ b/src/test/java/konkuk/thip/vote/domain/VoteTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import static konkuk.thip.common.exception.code.ErrorCode.COMMENT_COUNT_UNDERFLOW; +import static konkuk.thip.common.exception.code.ErrorCode.POST_LIKE_COUNT_UNDERFLOW; import static org.junit.jupiter.api.Assertions.*; @DisplayName("[단위] Vote 도메인 테스트") @@ -124,4 +125,46 @@ void decreaseCommentCount_belowZero_throws() { assertEquals(COMMENT_COUNT_UNDERFLOW, ex.getErrorCode()); } + @Test + @DisplayName("updateLikeCount: like == true 면 likeCount 가 1씩 증가한다.") + void updateLikeCount_likeTrue_increments() { + Vote vote = createWithCommentVote(); + + vote.updateLikeCount(true); + assertEquals(1, vote.getLikeCount()); + + vote.updateLikeCount(true); + assertEquals(2, vote.getLikeCount()); + } + + @Test + @DisplayName("updateLikeCount: like == false 면 likeCount 가 1씩 감소한다.") + void updateLikeCount_likeFalse_decrements() { + Vote vote = createWithCommentVote(); + + // 먼저 likeCount 증가 셋업 + vote.updateLikeCount(true); + vote.updateLikeCount(true); + assertEquals(2, vote.getLikeCount()); + + vote.updateLikeCount(false); + assertEquals(1, vote.getLikeCount()); + + vote.updateLikeCount(false); + assertEquals(0, vote.getLikeCount()); + } + + @Test + @DisplayName("updateLikeCount: like == false 면 likeCount 가 0 이하로 내려가면 InvalidStateException이 발생한다.") + void updateLikeCount_likeFalse_underflow_throws() { + Vote vote = createWithCommentVote(); + assertEquals(0, vote.getLikeCount()); + + InvalidStateException ex = assertThrows(InvalidStateException.class, () -> { + vote.updateLikeCount(false); + }); + + assertEquals(POST_LIKE_COUNT_UNDERFLOW, ex.getErrorCode()); + } + } From 9f463447be19ad44891134a8c9ae53ce03d4a845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 00:31:17 +0900 Subject: [PATCH 38/47] =?UTF-8?q?[refactor]=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD=20(#163)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/konkuk/thip/config/PostAccessPolicyConfig.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/konkuk/thip/config/PostAccessPolicyConfig.java b/src/main/java/konkuk/thip/config/PostAccessPolicyConfig.java index 2b3dd13fe..52abb6434 100644 --- a/src/main/java/konkuk/thip/config/PostAccessPolicyConfig.java +++ b/src/main/java/konkuk/thip/config/PostAccessPolicyConfig.java @@ -21,7 +21,7 @@ public class PostAccessPolicyConfig { private final RoomPostCommentAccessPolicy roomCommentPolicy; private final FeedLikeAccessPolicy feedLikePolicy; - private final RoomPostLikeAccessPolicy roomLikePolicy; + private final RoomPostLikeAccessPolicy roomPostLikePolicy; @Bean public Map commentAccessPolicyMap() { @@ -36,8 +36,8 @@ public Map commentAccessPolicyMap() { public Map roomPostAccessPolicyMap() { return Map.of( PostType.FEED, feedLikePolicy, - PostType.RECORD, roomLikePolicy, - PostType.VOTE, roomLikePolicy + PostType.RECORD, roomPostLikePolicy, + PostType.VOTE, roomPostLikePolicy ); } } \ No newline at end of file From fc9421e50fd42ed5d0d3f86905ea7ad450da94b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 01:09:10 +0900 Subject: [PATCH 39/47] =?UTF-8?q?[refactor]=20=EC=8A=A4=ED=8A=B8=EB=A6=BC?= =?UTF-8?q?=20=EB=8C=80=EC=8B=A0=20=EC=A0=81=EC=9D=80=20=EC=88=98=EC=9D=98?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EC=A7=81=EC=A0=91=20=EB=B9=84=EA=B5=90=20?= =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=EC=B5=9C=EC=A0=81=ED=99=94(#163)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/konkuk/thip/room/domain/RoomPostType.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/konkuk/thip/room/domain/RoomPostType.java b/src/main/java/konkuk/thip/room/domain/RoomPostType.java index f2d8a3aee..84d59c7f7 100644 --- a/src/main/java/konkuk/thip/room/domain/RoomPostType.java +++ b/src/main/java/konkuk/thip/room/domain/RoomPostType.java @@ -5,8 +5,6 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -import java.util.Arrays; - import static konkuk.thip.common.exception.code.ErrorCode.ROOM_POST_TYPE_NOT_MATCH; @Getter @@ -19,12 +17,12 @@ public enum RoomPostType { private final String type; public static RoomPostType from(String type) { - return Arrays.stream(RoomPostType.values()) - .filter(p -> p.getType().equalsIgnoreCase(type)) - .findFirst() - .orElseThrow(() -> - new InvalidStateException(ROOM_POST_TYPE_NOT_MATCH) - ); + for (RoomPostType roomPostType : values()) { + if (roomPostType.getType().equalsIgnoreCase(type)) { + return roomPostType; + } + } + throw new InvalidStateException(ROOM_POST_TYPE_NOT_MATCH); } public PostType toPostType() { From 5a926bdb9696690937dd2a19449d15338a2087ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 01:09:22 +0900 Subject: [PATCH 40/47] =?UTF-8?q?[refactor]=20feedId=20=EC=98=A4=ED=83=80?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20(#163)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../room/adapter/in/web/request/RoomPostIsLikeRequest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/konkuk/thip/room/adapter/in/web/request/RoomPostIsLikeRequest.java b/src/main/java/konkuk/thip/room/adapter/in/web/request/RoomPostIsLikeRequest.java index 8d329bddd..efc86a872 100644 --- a/src/main/java/konkuk/thip/room/adapter/in/web/request/RoomPostIsLikeRequest.java +++ b/src/main/java/konkuk/thip/room/adapter/in/web/request/RoomPostIsLikeRequest.java @@ -16,7 +16,7 @@ public record RoomPostIsLikeRequest( @NotBlank(message = "게시물 타입은 필수입니다.") String roomPostType ) { - public PostIsLikeCommand toCommand(Long userId, Long feedId) { - return new PostIsLikeCommand(userId, feedId, RoomPostType.from(roomPostType).toPostType(), type); + public PostIsLikeCommand toCommand(Long userId, Long postId) { + return new PostIsLikeCommand(userId, postId, RoomPostType.from(roomPostType).toPostType(), type); } } \ No newline at end of file From 27e85d9bef8c3f78440cf5d26473799ea6bd3729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 01:09:34 +0900 Subject: [PATCH 41/47] =?UTF-8?q?[refactor]=20roomPost=20=EC=98=A4?= =?UTF-8?q?=ED=83=80=20=EC=88=98=EC=A0=95=20(#163)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/config/PostAccessPolicyConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/config/PostAccessPolicyConfig.java b/src/main/java/konkuk/thip/config/PostAccessPolicyConfig.java index 52abb6434..e2491cb47 100644 --- a/src/main/java/konkuk/thip/config/PostAccessPolicyConfig.java +++ b/src/main/java/konkuk/thip/config/PostAccessPolicyConfig.java @@ -33,7 +33,7 @@ public Map commentAccessPolicyMap() { } @Bean - public Map roomPostAccessPolicyMap() { + public Map postLikeAccessPolicyMap() { return Map.of( PostType.FEED, feedLikePolicy, PostType.RECORD, roomPostLikePolicy, From 3b2e89660f6791555a1234fc6f10119a43bb6cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 01:10:04 +0900 Subject: [PATCH 42/47] =?UTF-8?q?[refactor]=20=EB=B9=84=EA=B3=B5=EA=B0=9C?= =?UTF-8?q?=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=9D=BC=20=EA=B2=BD=EC=9A=B0?= =?UTF-8?q?=20=EB=8C=93=EA=B8=80,=EC=A2=8B=EC=95=84=EC=9A=94=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=ED=95=A8=EC=88=98=20=ED=95=A9=EC=B9=98=EA=B8=B0=20?= =?UTF-8?q?(#163)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/feed/domain/Feed.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/konkuk/thip/feed/domain/Feed.java b/src/main/java/konkuk/thip/feed/domain/Feed.java index 468f92ca7..ea6896d50 100644 --- a/src/main/java/konkuk/thip/feed/domain/Feed.java +++ b/src/main/java/konkuk/thip/feed/domain/Feed.java @@ -108,16 +108,22 @@ public static void validateImageCount(int imageSize) { } } - public void validateCreateComment(Long userId){ + // 공통된 비공개 접근 권한 검증 로직 + private void validatePrivateAccessPermission(Long userId, String action) { if (!this.isPublic && !this.creatorId.equals(userId)) { - throw new InvalidStateException(FEED_ACCESS_FORBIDDEN, new IllegalArgumentException("비공개 글은 작성자만 댓글을 쓸 수 있습니다.")); + throw new InvalidStateException(FEED_ACCESS_FORBIDDEN, + new IllegalArgumentException(String.format("비공개 글은 작성자만 %s 있습니다.", action))); } } + // 댓글 작성 권한 검증 + public void validateCreateComment(Long userId){ + validatePrivateAccessPermission(userId, "댓글을 쓸 수"); + } + + // 좋아요 권한 검증 public void validateLike(Long userId){ - if (!this.isPublic && !this.creatorId.equals(userId)) { - throw new InvalidStateException(FEED_ACCESS_FORBIDDEN, new IllegalArgumentException("비공개 글은 작성자만 좋아요 할 수 있습니다.")); - } + validatePrivateAccessPermission(userId, "좋아요 할 수"); } private void validateCreator(Long userId) { From 96e7bb25aa5863beeca6653378c16dae952aa4e9 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Thu, 7 Aug 2025 14:53:33 +0900 Subject: [PATCH 43/47] =?UTF-8?q?[docs]=20=EC=A7=84=ED=96=89=EC=A4=91?= =?UTF-8?q?=EC=9D=B8=20=EB=B0=A9=20=EC=83=81=EC=84=B8=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=EB=AA=85=EC=84=B8=20=EC=88=98=EC=A0=95=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/room/adapter/in/web/RoomQueryController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java b/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java index d35d26f05..e1b55a560 100644 --- a/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java +++ b/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java @@ -105,7 +105,7 @@ public BaseResponse getRoomMemberList( @ExceptionDescription(ROOM_PLAYING_DETAIL) @GetMapping("/rooms/{roomId}/playing") public BaseResponse getPlayingRoomDetailView( - @UserId final Long userId, + @Parameter(hidden = true) @UserId final Long userId, @PathVariable("roomId") final Long roomId ) { return BaseResponse.ok(roomShowPlayingDetailViewUseCase.getPlayingRoomDetailView(userId, roomId)); From c15a9f5451692a4e86f47141cf3c4b492d5a1d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 23:32:43 +0900 Subject: [PATCH 44/47] =?UTF-8?q?[refactor]=20=EA=B2=8C=EC=8B=9C=EB=AC=BC?= =?UTF-8?q?=20=EC=88=98=20=EC=A6=9D=EA=B0=80=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=9E=91=EC=84=B1(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/domain/service/PostCountService.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/main/java/konkuk/thip/post/domain/service/PostCountService.java diff --git a/src/main/java/konkuk/thip/post/domain/service/PostCountService.java b/src/main/java/konkuk/thip/post/domain/service/PostCountService.java new file mode 100644 index 000000000..d0a367674 --- /dev/null +++ b/src/main/java/konkuk/thip/post/domain/service/PostCountService.java @@ -0,0 +1,26 @@ +package konkuk.thip.post.domain.service; + +import konkuk.thip.common.exception.InvalidStateException; +import org.springframework.stereotype.Service; + +import static konkuk.thip.common.exception.code.ErrorCode.POST_LIKE_COUNT_UNDERFLOW; + +@Service +public class PostCountService { + + public int updatePostLikeCount(boolean isLike, int likeCount) { + if (isLike) { + return ++likeCount; + + } else { + checkLikeCountNotUnderflow(likeCount); + return --likeCount; + } + } + + private void checkLikeCountNotUnderflow(int likeCount) { + if (likeCount <= 0) { + throw new InvalidStateException(POST_LIKE_COUNT_UNDERFLOW); + } + } +} From 09f81bd2d33f9d6ed010ec3badd3e6aea0f103ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 23:33:40 +0900 Subject: [PATCH 45/47] =?UTF-8?q?[refactor]=20=EA=B2=8C=EC=8B=9C=EB=AC=BC?= =?UTF-8?q?=20=EC=88=98=20=EC=A6=9D=EA=B0=80=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=9D=B4=EC=9A=A9=ED=95=9C=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95=20(#1?= =?UTF-8?q?59)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/common/post/CountUpdatable.java | 4 +++- src/main/java/konkuk/thip/feed/domain/Feed.java | 16 +++------------- .../application/service/PostLikeService.java | 4 +++- .../java/konkuk/thip/record/domain/Record.java | 16 +++------------- src/main/java/konkuk/thip/vote/domain/Vote.java | 16 +++------------- 5 files changed, 15 insertions(+), 41 deletions(-) diff --git a/src/main/java/konkuk/thip/common/post/CountUpdatable.java b/src/main/java/konkuk/thip/common/post/CountUpdatable.java index c6ea2a645..614e68253 100644 --- a/src/main/java/konkuk/thip/common/post/CountUpdatable.java +++ b/src/main/java/konkuk/thip/common/post/CountUpdatable.java @@ -1,8 +1,10 @@ package konkuk.thip.common.post; +import konkuk.thip.post.domain.service.PostCountService; + public interface CountUpdatable { //TODO 패키지 구조 충돌안나게 한번에 옮기기 void increaseCommentCount(); void decreaseCommentCount(); - void updateLikeCount(boolean like); + void updateLikeCount(PostCountService postCountService, boolean isLike); Long getId(); } \ No newline at end of file diff --git a/src/main/java/konkuk/thip/feed/domain/Feed.java b/src/main/java/konkuk/thip/feed/domain/Feed.java index ea6896d50..7144c1bf8 100644 --- a/src/main/java/konkuk/thip/feed/domain/Feed.java +++ b/src/main/java/konkuk/thip/feed/domain/Feed.java @@ -4,6 +4,7 @@ import konkuk.thip.common.exception.BusinessException; import konkuk.thip.common.exception.InvalidStateException; import konkuk.thip.common.post.CountUpdatable; +import konkuk.thip.post.domain.service.PostCountService; import lombok.Builder; import lombok.Getter; import lombok.experimental.SuperBuilder; @@ -180,13 +181,8 @@ public void decreaseCommentCount() { } @Override - public void updateLikeCount(boolean like) { - if (like) { - likeCount++; - } else { - checkLikeCountNotUnderflow(); - likeCount--; - } + public void updateLikeCount(PostCountService postCountService, boolean isLike) { + likeCount = postCountService.updatePostLikeCount(isLike, likeCount); } private void checkCommentCountNotUnderflow() { @@ -195,12 +191,6 @@ private void checkCommentCountNotUnderflow() { } } - private void checkLikeCountNotUnderflow() { - if (likeCount <= 0) { - throw new InvalidStateException(POST_LIKE_COUNT_UNDERFLOW); - } - } - /** * 유저가 현재 피드를 조회할 수 있는지를 검증하는 메서드 */ diff --git a/src/main/java/konkuk/thip/post/application/service/PostLikeService.java b/src/main/java/konkuk/thip/post/application/service/PostLikeService.java index 9a3042cc8..620c6e2d5 100644 --- a/src/main/java/konkuk/thip/post/application/service/PostLikeService.java +++ b/src/main/java/konkuk/thip/post/application/service/PostLikeService.java @@ -9,6 +9,7 @@ import konkuk.thip.post.application.port.out.PostLikeCommandPort; import konkuk.thip.post.application.port.out.PostLikeQueryPort; import konkuk.thip.post.application.service.validator.PostLikeAuthorizationValidator; +import konkuk.thip.post.domain.service.PostCountService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -20,6 +21,7 @@ public class PostLikeService implements PostLikeUseCase { private final PostLikeCommandPort postLikeCommandPort; private final PostHandler postHandler; + private final PostCountService postCountService; private final PostLikeAuthorizationValidator postLikeAuthorizationValidator; @Override @@ -45,7 +47,7 @@ public PostIsLikeResult changeLikeStatusPost(PostIsLikeCommand command) { } // 4. 게시물 좋아요 수 업데이트 - post.updateLikeCount(command.isLike()); + post.updateLikeCount(postCountService,command.isLike()); postHandler.updatePost(command.postType(), post); return PostIsLikeResult.of(post.getId(), command.isLike()); diff --git a/src/main/java/konkuk/thip/record/domain/Record.java b/src/main/java/konkuk/thip/record/domain/Record.java index de6656338..77454bb56 100644 --- a/src/main/java/konkuk/thip/record/domain/Record.java +++ b/src/main/java/konkuk/thip/record/domain/Record.java @@ -3,6 +3,7 @@ import konkuk.thip.common.entity.BaseDomainEntity; import konkuk.thip.common.exception.InvalidStateException; import konkuk.thip.common.post.CountUpdatable; +import konkuk.thip.post.domain.service.PostCountService; import konkuk.thip.room.domain.RoomPost; import lombok.Builder; import lombok.Getter; @@ -86,13 +87,8 @@ public void decreaseCommentCount() { } @Override - public void updateLikeCount(boolean like) { - if (like) { - likeCount++; - } else { - checkLikeCountNotUnderflow(); - likeCount--; - } + public void updateLikeCount(PostCountService postCountService, boolean isLike) { + likeCount = postCountService.updatePostLikeCount(isLike, likeCount); } private void checkCommentCountNotUnderflow() { @@ -100,10 +96,4 @@ private void checkCommentCountNotUnderflow() { throw new InvalidStateException(COMMENT_COUNT_UNDERFLOW); } } - - private void checkLikeCountNotUnderflow() { - if (likeCount <= 0) { - throw new InvalidStateException(POST_LIKE_COUNT_UNDERFLOW); - } - } } diff --git a/src/main/java/konkuk/thip/vote/domain/Vote.java b/src/main/java/konkuk/thip/vote/domain/Vote.java index 952014960..32a988a05 100644 --- a/src/main/java/konkuk/thip/vote/domain/Vote.java +++ b/src/main/java/konkuk/thip/vote/domain/Vote.java @@ -3,6 +3,7 @@ import konkuk.thip.common.entity.BaseDomainEntity; import konkuk.thip.common.exception.InvalidStateException; import konkuk.thip.common.post.CountUpdatable; +import konkuk.thip.post.domain.service.PostCountService; import konkuk.thip.room.domain.RoomPost; import lombok.Builder; import lombok.Getter; @@ -80,13 +81,8 @@ public void decreaseCommentCount() { } @Override - public void updateLikeCount(boolean like) { - if (like) { - likeCount++; - } else { - checkLikeCountNotUnderflow(); - likeCount--; - } + public void updateLikeCount(PostCountService postCountService, boolean isLike) { + likeCount = postCountService.updatePostLikeCount(isLike, likeCount); } private void checkCommentCountNotUnderflow() { @@ -94,10 +90,4 @@ private void checkCommentCountNotUnderflow() { throw new InvalidStateException(COMMENT_COUNT_UNDERFLOW); } } - - private void checkLikeCountNotUnderflow() { - if (likeCount <= 0) { - throw new InvalidStateException(POST_LIKE_COUNT_UNDERFLOW); - } - } } From 1daf8135896b3b7151f7ce9e1231606ed54a63ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 7 Aug 2025 23:33:49 +0900 Subject: [PATCH 46/47] =?UTF-8?q?[refactor]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/feed/domain/FeedTest.java | 23 +++++++++++++------ .../konkuk/thip/record/domain/RecordTest.java | 23 +++++++++++++------ .../konkuk/thip/vote/domain/VoteTest.java | 23 +++++++++++++------ 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/test/java/konkuk/thip/feed/domain/FeedTest.java b/src/test/java/konkuk/thip/feed/domain/FeedTest.java index aee96483d..303ea835c 100644 --- a/src/test/java/konkuk/thip/feed/domain/FeedTest.java +++ b/src/test/java/konkuk/thip/feed/domain/FeedTest.java @@ -2,6 +2,8 @@ import konkuk.thip.common.exception.BusinessException; import konkuk.thip.common.exception.InvalidStateException; +import konkuk.thip.post.domain.service.PostCountService; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -16,6 +18,13 @@ @DisplayName("[단위] Feed 도메인 테스트") class FeedTest { + private PostCountService postCountService; + + @BeforeEach + void setUp() { + postCountService = new PostCountService(); + } + private final Long CREATOR_ID = 1L; private final Long OTHER_USER_ID = 2L; @@ -273,10 +282,10 @@ private Feed makeFeedWithPublicStatus(Boolean isPublic) { void updateLikeCount_likeTrue_increments() { Feed feed = createPublicFeed(); - feed.updateLikeCount(true); + feed.updateLikeCount(postCountService, true); assertEquals(1, feed.getLikeCount()); - feed.updateLikeCount(true); + feed.updateLikeCount(postCountService, true); assertEquals(2, feed.getLikeCount()); } @@ -285,14 +294,14 @@ void updateLikeCount_likeTrue_increments() { void updateLikeCount_likeFalse_decrements() { Feed feed = createPublicFeed(); // 먼저 likeCount 증가 셋업 - feed.updateLikeCount(true); - feed.updateLikeCount(true); + feed.updateLikeCount(postCountService, true); + feed.updateLikeCount(postCountService, true); assertEquals(2, feed.getLikeCount()); - feed.updateLikeCount(false); + feed.updateLikeCount(postCountService, false); assertEquals(1, feed.getLikeCount()); - feed.updateLikeCount(false); + feed.updateLikeCount(postCountService, false); assertEquals(0, feed.getLikeCount()); } @@ -303,7 +312,7 @@ void updateLikeCount_likeFalse_underflow_throws() { assertEquals(0, feed.getLikeCount()); InvalidStateException ex = assertThrows(InvalidStateException.class, () -> { - feed.updateLikeCount(false); + feed.updateLikeCount(postCountService, false); }); assertEquals(POST_LIKE_COUNT_UNDERFLOW, ex.getErrorCode()); diff --git a/src/test/java/konkuk/thip/record/domain/RecordTest.java b/src/test/java/konkuk/thip/record/domain/RecordTest.java index e5a337395..4dba6a5b0 100644 --- a/src/test/java/konkuk/thip/record/domain/RecordTest.java +++ b/src/test/java/konkuk/thip/record/domain/RecordTest.java @@ -1,6 +1,8 @@ package konkuk.thip.record.domain; import konkuk.thip.common.exception.InvalidStateException; +import konkuk.thip.post.domain.service.PostCountService; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -11,6 +13,13 @@ @DisplayName("[단위] Record 도메인 테스트") class RecordTest { + private PostCountService postCountService; + + @BeforeEach + void setUp() { + postCountService = new PostCountService(); + } + private final Long CREATOR_ID = 1L; private Record createWithCommentRecord() { @@ -130,10 +139,10 @@ void decreaseCommentCount_belowZero_throws() { void updateLikeCount_likeTrue_increments() { Record record = createWithCommentRecord(); - record.updateLikeCount(true); + record.updateLikeCount(postCountService,true); assertEquals(1, record.getLikeCount()); - record.updateLikeCount(true); + record.updateLikeCount(postCountService,true); assertEquals(2, record.getLikeCount()); } @@ -143,14 +152,14 @@ void updateLikeCount_likeFalse_decrements() { Record record = createWithCommentRecord(); // 먼저 likeCount 증가 셋업 - record.updateLikeCount(true); - record.updateLikeCount(true); + record.updateLikeCount(postCountService,true); + record.updateLikeCount(postCountService,true); assertEquals(2, record.getLikeCount()); - record.updateLikeCount(false); + record.updateLikeCount(postCountService,false); assertEquals(1, record.getLikeCount()); - record.updateLikeCount(false); + record.updateLikeCount(postCountService,false); assertEquals(0, record.getLikeCount()); } @@ -161,7 +170,7 @@ void updateLikeCount_likeFalse_underflow_throws() { assertEquals(0, record.getLikeCount()); InvalidStateException ex = assertThrows(InvalidStateException.class, () -> { - record.updateLikeCount(false); + record.updateLikeCount(postCountService,false); }); assertEquals(POST_LIKE_COUNT_UNDERFLOW, ex.getErrorCode()); diff --git a/src/test/java/konkuk/thip/vote/domain/VoteTest.java b/src/test/java/konkuk/thip/vote/domain/VoteTest.java index ecd7b42e7..1ea2b3729 100644 --- a/src/test/java/konkuk/thip/vote/domain/VoteTest.java +++ b/src/test/java/konkuk/thip/vote/domain/VoteTest.java @@ -1,6 +1,8 @@ package konkuk.thip.vote.domain; import konkuk.thip.common.exception.InvalidStateException; +import konkuk.thip.post.domain.service.PostCountService; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -11,6 +13,13 @@ @DisplayName("[단위] Vote 도메인 테스트") class VoteTest { + private PostCountService postCountService; + + @BeforeEach + void setUp() { + postCountService = new PostCountService(); + } + private final Long CREATOR_ID = 1L; private Vote createWithCommentVote() { @@ -130,10 +139,10 @@ void decreaseCommentCount_belowZero_throws() { void updateLikeCount_likeTrue_increments() { Vote vote = createWithCommentVote(); - vote.updateLikeCount(true); + vote.updateLikeCount(postCountService,true); assertEquals(1, vote.getLikeCount()); - vote.updateLikeCount(true); + vote.updateLikeCount(postCountService,true); assertEquals(2, vote.getLikeCount()); } @@ -143,14 +152,14 @@ void updateLikeCount_likeFalse_decrements() { Vote vote = createWithCommentVote(); // 먼저 likeCount 증가 셋업 - vote.updateLikeCount(true); - vote.updateLikeCount(true); + vote.updateLikeCount(postCountService,true); + vote.updateLikeCount(postCountService,true); assertEquals(2, vote.getLikeCount()); - vote.updateLikeCount(false); + vote.updateLikeCount(postCountService,false); assertEquals(1, vote.getLikeCount()); - vote.updateLikeCount(false); + vote.updateLikeCount(postCountService,false); assertEquals(0, vote.getLikeCount()); } @@ -161,7 +170,7 @@ void updateLikeCount_likeFalse_underflow_throws() { assertEquals(0, vote.getLikeCount()); InvalidStateException ex = assertThrows(InvalidStateException.class, () -> { - vote.updateLikeCount(false); + vote.updateLikeCount(postCountService,false); }); assertEquals(POST_LIKE_COUNT_UNDERFLOW, ex.getErrorCode()); From e6fcdbea361f6b27e3970d29109dc4deb628104a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Fri, 8 Aug 2025 01:25:51 +0900 Subject: [PATCH 47/47] =?UTF-8?q?[fix]=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=ED=97=88=EC=9A=A9=20=EC=A3=BC=EC=86=8C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/config/SecurityConfig.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/konkuk/thip/config/SecurityConfig.java b/src/main/java/konkuk/thip/config/SecurityConfig.java index beb014447..d187ea5f9 100644 --- a/src/main/java/konkuk/thip/config/SecurityConfig.java +++ b/src/main/java/konkuk/thip/config/SecurityConfig.java @@ -35,6 +35,10 @@ public class SecurityConfig { @Value("${server.web-domain-url}") private String webDomainUrl; + @Value("${server.https-url}") + private String httpsUrl; + + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; private final JwtAuthenticationFilter jwtAuthenticationFilter; private final CustomOAuth2UserService customOAuth2UserService; @@ -68,7 +72,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ) .exceptionHandling(handler -> handler.authenticationEntryPoint(jwtAuthenticationEntryPoint)) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - ; + ; return http.build(); } @@ -90,7 +94,8 @@ public CorsConfigurationSource corsConfigurationSource() { config.setAllowedOrigins(List.of( "http://localhost:5173", webUrl, - webDomainUrl + webDomainUrl, + httpsUrl )); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); config.setAllowedHeaders(Collections.singletonList("*")); @@ -104,5 +109,5 @@ public CorsConfigurationSource corsConfigurationSource() { return source; } - + } \ No newline at end of file